init_base

This commit is contained in:
NguyenAnhQuan 2025-10-21 01:14:13 +07:00
parent a5ddee13a6
commit bc6002f70c
41 changed files with 4282 additions and 1 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
VITE_APP_API=

15
.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
# Env
.env
.env.*
!.env.example
# Vscode
.vscode

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 4,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "es5"
}

View File

@ -1 +1,31 @@
# web-wallet # web-wallet
## Project Setup
```sh
yarn
```
### Compile and Hot-Reload for Development
```sh
yarn dev
```
### Type-Check, Compile and Minify for Production
```sh
yarn build-only
```
### Lint with [ESLint](https://eslint.org/)
```sh
yarn lint
```
### Format code
```sh
yarn format
```

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

14
index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Wallet</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "webtoon-admin",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"ant-design-vue": "^4.2.6",
"axios": "^1.6.8",
"dayjs": "^1.11.10",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"vue3-i18n": "^1.1.5"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.12.5",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.1.2",
"prettier": "^3.2.5",
"sass": "^1.75.0",
"typescript": "~5.4.0",
"vite": "^5.2.8",
"vite-plugin-vue-devtools": "^7.0.25",
"vue-tsc": "^2.0.11"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

20
src/App.vue Normal file
View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { LayoutVue } from '@/components/common'
const config = {
token: {
colorPrimary: '#ff7789',
borderRadius: 0,
},
}
</script>
<template>
<a-config-provider :theme="config" :autoInsertSpaceInButton="false">
<LayoutVue>
<router-view />
</LayoutVue>
</a-config-provider>
</template>
<style lang="scss" scoped></style>

50
src/api/config/index.ts Normal file
View File

@ -0,0 +1,50 @@
import axios from 'axios'
import router from '@/router'
import { STATUS_CODE_SUCCESS, ACCESS_TOKEN, STATUS_CODE_UNAUTHORIZED } from '@/helpers'
axios.defaults.withCredentials = false
export const API_URL = import.meta.env.VITE_APP_API
const instance = axios.create({
baseURL: API_URL,
})
instance.interceptors.request.use(
function (config: any) {
try {
const token = localStorage.getItem(ACCESS_TOKEN)
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
} catch (error) {
throw Error('')
}
return config
},
function (error) {
return Promise.reject(error)
}
)
instance.interceptors.response.use(
function (response) {
if (response?.status !== STATUS_CODE_SUCCESS) return Promise.reject(response?.data)
return response.data
},
function (error) {
if (error?.response?.status === STATUS_CODE_UNAUTHORIZED || error.code === 'ERR_NETWORK') {
localStorage.clear()
return router.push({ name: 'login' })
}
if (error?.response?.data) {
return Promise.reject(error?.response?.data)
}
return Promise.reject(error)
}
)
export const setLocaleApi = (locale: string) => {
instance.defaults.headers.common['lang'] = locale
}
export default instance

290
src/assets/scss/__base.scss Normal file
View File

@ -0,0 +1,290 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;200;300;400;500;600;700;800;900&display=swap');
*,
*::before,
*::after {
margin: 0;
padding: 0;
font-family: 'Noto Sans JP';
}
img {
max-width: 100%;
height: auto;
}
body {
min-height: 100vh;
color: var(--vt-c-gray-2);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family: 'Noto Sans JP';
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: var(--vt-c-main-gray-v1);
}
&::-webkit-scrollbar-thumb {
background-color: var(--vt-c-main-gray-v2);
border-radius: 10px;
transition: all 0.2s ease-in-out;
}
&::-webkit-scrollbar-track {
border-radius: 10px;
}
}
*,
*::before,
*::after {
position: unset;
}
.box {
// padding: 20px;
&.full {
min-height: calc(100% - 20px);
}
&.no-border {
border: none;
}
.box-head {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 66px;
padding: 0 20px;
background-color: var(--vt-c-white-mute);
.box-search {
display: flex;
gap: 35px;
align-items: center;
.ant-input-search {
width: 322px;
.ant-input {
border: none;
background: unset;
outline: unset;
box-shadow: unset;
}
.ant-input-group-addon {
background: unset;
.ant-input-search-button {
background: unset;
border: unset;
box-shadow: unset;
.anticon {
svg {
fill: var(--vt-c-gray-2);
}
}
}
}
}
}
.title {
font-weight: 900;
font-size: 18px;
line-height: 26px;
color: var(--vt-c-gray-2);
.title-page {
color: var(--vt-c-gray-v9);
}
&.manga {
color: var(--vt-c-gray-v9);
}
}
.box-btn {
@include center_flex;
width: 140px;
height: 40px;
border-radius: 6px;
color: var(--vt-c-main);
background-color: var(--vt-c-white);
box-shadow: var(--vt-box-shadow);
border: 1px solid var(--vt-c-main);
font-weight: 700;
font-size: 14px;
}
}
.box-body {
padding: 0 20px;
overflow-y: auto;
height: calc(100vh - 135px);
position: relative;
&.no-pagination {
height: calc(100vh - 66px);
}
&:has(.form-create-chap) {
.ant-tabs {
overflow-y: auto;
height: calc(100vh - 235px);
}
}
&.no_padding {
padding: 0;
}
.btn-save-setting {
&:disabled {
border-color: var(--vt-c-gray-1);
background-color: var(--vt-c-gray-1);
}
}
}
}
.btn-base {
margin-right: 12px;
&.btn-delete {
background-color: var(--vt-c-red-v3);
color: var(--vt-c-white);
&:hover {
border-color: unset !important;
color: var(--vt-c-white);
}
}
}
.ant-tag {
&.custom {
width: 72px;
height: 28px;
border: none;
@include center_flex;
}
}
.ant-picker {
width: 100%;
&.has-value {
border-color: var(--vt-c-main);
}
}
.rounded-btn {
border-radius: 100px !important;
}
.bold-label {
label {
font-weight: 700;
}
}
.ant-input-number {
width: 100%;
}
.full-width {
width: 100%;
}
.ant-input[disabled] {
background-color: var(--vt-c-background-dark-1);
color: var(--vt-c-text-dark-3);
}
.ant-tabs {
&.custom {
.ant-tabs-nav {
background-color: var(--vt-c-white-mute);
padding: 0 20px;
}
.ant-tabs-content-holder {
padding: 0 20px;
}
.ant-tabs-nav-list {
.ant-tabs-tab {
min-width: 68px;
justify-content: center;
padding: 0 16px 6px;
.ant-tabs-tab-btn {
font-size: 16px;
font-weight: 700;
line-height: 23.17px;
}
&:not(.ant-tabs-tab-active) {
color: var(--vt-c-gray-v9);
}
}
}
.ant-tabs-ink-bar {
height: 3px;
}
.ant-tabs-content {
&:has(.tab-report) {
height: calc(100vh - 180px);
overflow-y: scroll;
}
&:has(.tab-ranking) {
height: calc(100vh - 180px);
overflow-y: auto;
}
}
}
&.manga-tabs {
.ant-tabs-content-holder {
margin-bottom: 150px;
}
}
&.master-tabs {
.ant-tabs-content-holder {
margin-bottom: 70px;
}
}
}
.ant-form-item {
.ant-form-item-explain-error {
font-size: 12px;
}
}
.ant-spin-spinning {
width: 100%;
}
.flex-center {
@include center_flex;
}
.flex-col-center {
display: flex;
flex-direction: column;
align-items: center;
}

View File

@ -0,0 +1,170 @@
@mixin screen($size) {
$desktop: '(min-width: 1281px)';
$tablet: '(min-width: 768px) and (max-width: 1280px)';
$mobile: '(max-width: 767px)';
@if $size == desktop {
@media only screen and #{$desktop} {
@content;
}
} @else if $size == tablet {
@media only screen and #{$tablet} {
@content;
}
} @else if $size == mobile {
@media only screen and #{$mobile} {
@content;
}
} @else {
@media only screen and #{$size} {
@content;
}
}
}
@mixin text-shorten($numLines: 1) {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@supports (-webkit-line-clamp: $numLines) {
overflow: hidden;
text-overflow: ellipsis;
white-space: initial;
display: -webkit-box;
-webkit-line-clamp: $numLines;
-webkit-box-orient: vertical;
}
}
$start: 0;
$end: 30;
@for $i from $start through $end {
.pl-#{$i} {
padding-left: #{$i}px;
}
.pr-#{$i} {
padding-right: #{$i}px;
}
.pb-#{$i} {
padding-bottom: #{$i}px;
}
.pt-#{$i} {
padding-top: #{$i}px;
}
.ml-#{$i} {
margin-left: #{$i}px;
}
.mr-#{$i} {
margin-right: #{$i}px;
}
.mb-#{$i} {
margin-bottom: #{$i}px;
}
.mt-#{$i} {
margin-top: #{$i}px;
}
.mx-#{$i} {
margin: 0 #{$i}px;
}
.fz-#{$i} {
font-size: #{$i}px;
}
.px-#{$i} {
padding: 0 #{$i}px;
}
.py-#{$i} {
padding: #{$i}px 0;
}
.text-shorten-#{$i} {
@include text-shorten(#{$i});
}
}
$maxFontWeight: 1000;
$positions: top, left, right, bottom, none;
@each $position in $positions {
@if $position == none {
.bh-#{$position} {
border-style: none !important;
}
} @else {
.bh-#{$position} {
border-#{$position}-style: hidden !important;
}
}
}
$positionsText: start, end, right, left, center;
@each $pt in $positionsText {
.text-#{$pt} {
text-align: #{$pt};
}
}
$fw: 100;
@while $fw < $maxFontWeight {
.fw-#{$fw} {
font-weight: $fw;
}
$fw: $fw + 100;
}
@mixin center_flex {
display: flex;
align-items: center;
justify-content: center;
}
@mixin center_pos {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@mixin line_clamp($line) {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: $line;
-webkit-box-orient: vertical;
}
@mixin position($pos: absolute, $left: 0, $right: 0, $top: 0, $bottom: 0) {
position: $pos;
top: $top;
left: $left;
right: $right;
bottom: $bottom;
}
// function
@function sum($numbers...) {
$sum: 0;
@each $number in $numbers {
$sum: $sum + $number;
}
@return $sum;
}
@function calc_v2($size) {
@return calc(100% - $size);
}
@mixin baseBtn(
$bg: var(--vt-c-main),
$width: 92px,
$height: 36px,
$borderRadius: var(--vt-br-btn)
) {
display: flex;
justify-content: center;
align-items: center;
width: $width;
height: $height;
background-color: $bg;
border-radius: $borderRadius;
box-shadow: unset;
}

View File

@ -0,0 +1,107 @@
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-white-v2: #d9d9d9;
--vt-c-white-v5: #fafafa;
--vt-c-white-v7: #f0f5f8;
--vt-c-white-v8: #fbfbfb;
--vt-c-black: #181818;
--vt-c-black-v1: #5d6679;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-black-bold: #000000;
--vt-c-black-bold-v2: #131523;
--vt-c-black-bold-v3: #252c32;
--vt-c-black-bold-v3: #00000026;
--vt-c-black-bold-v4: #00000080;
--vt-c-black-bold-v5: #00000008;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
--vt-c-text-dark-3: rgba(0, 0, 0, 0.88);
--vt-c-background-dark-1: rgba(0, 0, 0, 0.04);
--vt-box-shadow-1: 0px 0px 8px rgba(78, 37, 0, 0.78);
--vt-box-shadow-2: 0px 4px 4px rgba(0, 0, 0, 0.25);
--vt-box-shadow-3: 0px 0px 5px 0px var(--vt-c-black-bold-v5) inset;
--vt-c-main: #ff7789;
--vt-c-main-title: #519fb0;
--vt-c-main-light: #6659ff;
--vt-c-main-bg: #eff1f7;
--vt-c-main-bg-v1: #f4f7ff;
--vt-c-main-black: #051121;
--vt-c-main-clear: #e6e8ef;
--vt-c-main-link: #4335ef;
--vt-c-main-red: red;
--vt-c-main-gray: #7e84a3;
--vt-c-main-gray-v1: #fff3;
--vt-c-main-gray-v2: #0003;
--vt-c-gray-v4: #a9a5a3;
--vt-c-gray-v5: #666666;
--vt-c-gray-v6: #999999;
--vt-c-gray-v9: #888888;
--vt-c-gray-v10: #444444;
--vt-c-gray-v11: #6d6d6d;
--vt-c-gray-v12: #b9bdc7;
--vt-c-gray-v17: #e6e6e6;
--vt-c-blue: #3375f3;
--vt-c-blue-v3: #162dff;
--vt-c-blue-v4: #67abba;
--vt-c-blue-v5: #20aee5;
--vt-c-red-v3: #ff0f0f;
--vt-c-violet: #5671fb;
--vt-c-pink: #ffe6ea;
--vt-c-pink-2: #ff6d6d;
--vt-c-pink-3: #e05266;
--vt-c-green: #1e8e3e;
--vt-c-green-2: #93cb9c;
--vt-c-green-3: #429d7c;
--vt-c-turquoise: #3fb3ce;
--color-background: #f5f7fb;
--color-background-event: #3fb3ce;
--color-background-sidebar: #e6e6e6;
--color-background-soft: var(--vt-c-white-soft);
--color-border: #b8b7b7;
--color-border-v1: #cdd4e7;
--color-border-shadow: #dfdfdf40;
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--font-size: 14px;
--vt-font-btn: 92px;
--vt-br-btn: 3px;
--section-gap: 160px;
--vt-c-gray-1: #e9e9e9;
--vt-c-gray-2: #444444;
--vt-c-gray-3: #adadad66;
--vt-c-gray-4: #f9f9f9;
--vt-c-gray-5: #b4b4b4;
--vt-c-gray-6: #d9d9d9;
--vt-c-gray-7: #ececec;
--vt-c-gray-8: #636363;
--vt-c-gray-9: #aaaaaa;
--vt-c-green-dark: #435855;
--vt-c-orange: #f3a964;
--vt-box-shadow: 0px 0px 4px 0px var(--vt-c-gray-3);
--vt-box-shadow-active: 0px 0px 4px 0px var(--vt-c-main);
}

View File

@ -0,0 +1,3 @@
@import '__variables';
@import '__mixin';
@import '__base';

View File

@ -0,0 +1,12 @@
<script lang="ts" setup></script>
<template>
<a-layout>
<a-layout class="ant-layout-body">
<!-- <AppHeaderVue /> -->
<a-layout-content>
<slot />
</a-layout-content>
</a-layout>
</a-layout>
</template>

View File

@ -0,0 +1,3 @@
import LayoutVue from './LayoutVue.vue'
export { LayoutVue }

View File

@ -0,0 +1,8 @@
export const STATUS_CODE_SUCCESS = 200
export const STATUS_CODE_BAD_REQUEST = 400
export const STATUS_CODE_UNAUTHORIZED = 401
export const STATUS_CODE_FORBIDDEN = 403
export const STATUS_CODE_NOT_FOUND = 404
export const STATUS_CODE_VALIDATE_ERROR = 422
export const STATUS_CODE_SERVER_ERROR = 500
export const HTTP_TOO_MANY_REQUESTS = 429

View File

@ -0,0 +1,16 @@
import dayjs from 'dayjs'
export const PAGE_FIRST = 1
export const PER_PAGE = 40
export const MAX_STRING = 255
export const CURRENT_DAY = dayjs(new Date()).format('DD')
export const CURRENT_MONTH = dayjs(new Date()).format('MM')
export const CURRENT_YEAR = dayjs(new Date()).format('YYYY')
export const MONTHS = Array.from({ length: 12 }, (item, i) => {
return dayjs(new Date(0, i)).format('MM')
})
export const FORMAT_DAY = (day: any, format = 'YYYY-MM-DD') => {
return dayjs(new Date(day)).format(format)
}

View File

@ -0,0 +1,7 @@
export const ACCESS_TOKEN = 'access_token'
export const USER = 'user'
export const getToken = () => localStorage.getItem(ACCESS_TOKEN)
export const getAdmin = () => localStorage.getItem(USER)

3
src/helpers/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './constants/code'
export * from './constants/constants'
export * from './constants/localStorage'

12
src/lang/en/base.ts Normal file
View File

@ -0,0 +1,12 @@
export default {
logout: 'Logout',
login: 'Login',
mail_address: 'Email',
password: 'Password',
create: 'Create',
edit: 'Edit',
update: 'Update',
detail: 'Detail',
submit: 'Submit',
cancel: 'Cancel',
}

7
src/lang/en/index.ts Normal file
View File

@ -0,0 +1,7 @@
import validation from './validation'
import base from './base'
export const en = {
...base,
validation,
}

150
src/lang/en/validation.ts Normal file
View File

@ -0,0 +1,150 @@
export default {
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
accepted: 'The {0} must be accepted.',
accepted_if: 'The {0} must be accepted when {1} is {2}.',
active_url: 'The {0} is not a valid URL.',
after: 'The {0} must be a date after {1}.',
after_or_equal: 'The {0} must be a date after or equal to {1}.',
alpha: 'The {0} must only contain letters.',
alpha_dash: 'The {0} must only contain letters, numbers, dashes and underscores.',
alpha_num: 'The {0} must only contain letters and numbers.',
array: 'The {0} must be an array.',
ascii: 'The {0} must only contain single-byte alphanumeric characters and symbols.',
before: 'The {0} must be a date before {1}.',
before_or_equal: 'The {0} must be a date before or equal to {1}.',
between: {
array: 'The {0} must have between {1} and {2} items.',
file: 'The {0} must be between {1} and {2} kilobytes.',
numeric: 'The {0} must be between {1} and {2}.',
string: 'The {0} must be between {1} and {2} characters.',
},
boolean: 'The {0} field must be true or false.',
confirmed: 'The {0} confirmation does not match.',
current_password: 'The password is incorrect.',
date: 'The {0} is not a valid date.',
date_equals: 'The {0} must be a date equal to {1}.',
date_format: 'The {0} does not match the format {1}.',
decimal: 'The {0} must have {1} decimal places.',
declined: 'The {0} must be declined.',
declined_if: 'The {0} must be declined when {1} is {2}.',
different: 'The {0} and {1} must be different.',
digits: 'The {0} must be {1} digits.',
digits_between: 'The {0} must be between {1} and {2} digits.',
dimensions: 'The {0} has invalid image dimensions.',
distinct: 'The {0} field has a duplicate value.',
doesnt_end_with: 'The {0} may not end with one of the following: {1}.',
doesnt_start_with: 'The {0} may not start with one of the following: {1}.',
email: 'The {0} must be a valid email address.',
ends_with: 'The {0} must end with one of the following: {1}.',
enum: 'The selected {0} is invalid.',
exists: 'The selected {0} is invalid.',
file: 'The {0} must be a file.',
filled: 'The {0} field must have a value.',
gt: {
array: 'The {0} must have more than {1} items.',
file: 'The {0} must be greater than {1} kilobytes.',
numeric: 'The {0} must be greater than {1}.',
string: 'The {0} must be greater than {1} characters.',
},
gte: {
array: 'The {0} must have {1} items or more.',
file: 'The {0} must be greater than or equal to {1} kilobytes.',
numeric: 'The {0} must be greater than or equal to {1}.',
string: 'The {0} must be greater than or equal to {1} characters.',
},
image: 'The {0} must be an image.',
in: 'The selected {0} is invalid.',
in_array: 'The {0} field does not exist in {1}.',
integer: 'The {0} must be an integer.',
ip: 'The {0} must be a valid IP address.',
ipv4: 'The {0} must be a valid IPv4 address.',
ipv6: 'The {0} must be a valid IPv6 address.',
json: 'The {0} must be a valid JSON string.',
lowercase: 'The {0} must be lowercase.',
lt: {
array: 'The {0} must have less than {1} items.',
file: 'The {0} must be less than {1} kilobytes.',
numeric: 'The {0} must be less than {1}.',
string: 'The {0} must be less than {1} characters.',
},
lte: {
array: 'The {0} must not have more than {1} items.',
file: 'The {0} must be less than or equal to {1} kilobytes.',
numeric: 'The {0} must be less than or equal to {1}.',
string: 'The {0} must be less than or equal to {1} characters.',
},
mac_address: 'The {0} must be a valid MAC address.',
max: {
array: 'The {0} must not have more than {1} items.',
file: 'The {0} must not be greater than {1} kilobytes.',
numeric: 'The {0} must not be greater than {1}.',
string: 'The {0} must not be greater than {1} characters.',
},
max_digits: 'The {0} must not have more than {1} digits.',
mimes: 'The {0} must be a file of type: {1}s.',
mimetypes: 'The {0} must be a file of type: {1}s.',
min: {
array: 'The {0} must have at least {1} items.',
file: 'The {0} must be at least {1} kilobytes.',
numeric: 'The {0} must be at least {1}.',
string: 'The {0} must be at least {1} characters.',
},
min_digits: 'The {0} must have at least {1} digits.',
missing: 'The {0} field must be missing.',
missing_if: 'The {0} field must be missing when {1} is {2}.',
missing_unless: 'The {0} field must be missing unless {1} is {2}.',
missing_with: 'The {0} field must be missing when {1}s is present.',
missing_with_all: 'The {0} field must be missing when {1}s are present.',
multiple_of: 'The {0} must be a multiple of {1}.',
not_in: 'The selected {0} is invalid.',
not_regex: 'The {0} format is invalid.',
numeric: 'The {0} must be a number.',
password: {
letters: 'The {0} must contain at least one letter.',
mixed: 'The {0} must contain at least one uppercase and one lowercase letter.',
numbers: 'The {0} must contain at least one number.',
symbols: 'The {0} must contain at least one symbol.',
uncompromised: 'The given {0} has appeared in a data leak. Please choose a different {1}.',
},
present: 'The {0} field must be present.',
prohibited: 'The {0} field is prohibited.',
prohibited_if: 'The {0} field is prohibited when {1} is {2}.',
prohibited_unless: 'The {0} field is prohibited unless {1} is in {2}s.',
prohibits: 'The {0} field prohibits {1} from being present.',
regex: 'The {0} format is invalid.',
required: 'The {0} field is required.',
required_array_keys: 'The {0} field must contain entries for: {1}s.',
required_if: 'The {0} field is required when {1} is {2}.',
required_if_accepted: 'The {0} field is required when {1} is accepted.',
required_unless: 'The {0} field is required unless {1} is in {2}s.',
required_with: 'The {0} field is required when {1}s is present.',
required_with_all: 'The {0} field is required when {1}s are present.',
required_without: 'The {0} field is required when {1}s is not present.',
required_without_all: 'The {0} field is required when none of {1}s are present.',
same: 'The {0} and {1} must match.',
size: {
array: 'The {0} must contain {1} items.',
file: 'The {0} must be {1} kilobytes.',
numeric: 'The {0} must be {1}.',
string: 'The {0} must be {1} characters.',
},
starts_with: 'The {0} must start with one of the following: {1}s.',
string: 'The {0} must be a string.',
timezone: 'The {0} must be a valid timezone.',
unique: 'The {0} has already been taken.',
uploaded: 'The {0} failed to upload.',
uppercase: 'The {0} must be uppercase.',
url: 'The {0} must be a valid URL.',
ulid: 'The {0} must be a valid ULID.',
uuid: 'The {0} must be a valid UUID.',
}

16
src/lang/index.ts Normal file
View File

@ -0,0 +1,16 @@
import { createI18n } from 'vue3-i18n'
import { en } from './en'
import { jp } from './jp'
const messages = {
en: en,
jp: jp,
}
const i18n = createI18n({
locale: 'jp',
fallbackLocale: 'jp',
messages,
} as any)
export default i18n

1
src/lang/jp/base.ts Normal file
View File

@ -0,0 +1 @@
export default {}

9
src/lang/jp/index.ts Normal file
View File

@ -0,0 +1,9 @@
import base from './base'
import validation from './validation'
import notFound from './notFound'
export const jp = {
...base,
validation,
notFound,
}

4
src/lang/jp/notFound.ts Normal file
View File

@ -0,0 +1,4 @@
export default {
title: 'ご指定のページはございませんでした。',
back: ' トップページへ',
}

115
src/lang/jp/validation.ts Normal file
View File

@ -0,0 +1,115 @@
export default {
active_url: '{0}は有効なURLではありません',
after: '{0}は{1}より後の日付にしてください',
after_or_equal: '{0}は{1}以降の日付にしてください',
alpha: '{0}は英字のみ有効です',
alpha_dash: '{0}は「英字」「数字」「-(ダッシュ)」「_(下線)」のみ有効です',
alpha_num: '{0}は「英字」「数字」のみ有効です',
array: '{0}は配列タイプのみ有効です',
before: '{0}は{1}より前の日付にしてください',
before_or_equal: '{0}は{1}以前の日付にしてください',
between: {
numeric: '{0}は {1} {2} までの数値まで有効です',
file: '{0}は {1} {2} キロバイトまで有効です',
string: '{0}は {1} {2} 文字まで有効です',
array: '{0}は {1} {2} 個まで有効です',
},
boolean: '{0}の値は true もしくは false のみ有効です',
confirmed: '{0}を確認用と一致させてください',
date: '{0}を有効な日付形式にしてください',
date_format: '{0}を{1}書式と一致させてください',
different: '{0}を{1}と違うものにしてください',
digits: '{0}は:digits桁のみ有効です',
digits_between: '{0}は{1} {2}桁のみ有効です',
dimensions: '{0}ルールに合致する画像サイズのみ有効です',
distinct: '{0}に重複している値があります',
email: '{0}の書式のみ有効です',
exists: '{0}無効な値です',
file: '{0}アップロード出来ないファイルです',
filled: '{0}値を入力してください',
gt: {
numeric: '{0}は{1}より大きい必要があります。',
file: '{0}は{1}キロバイトより大きい必要があります。',
string: '{0}は{1}文字より多い必要があります。',
array: '{0}には{1} 個より多くの項目が必要です。',
},
gte: {
numeric: '{0}は{1}以上である必要があります。',
file: '{0}は{1}キロバイト以上である必要があります。',
string: '{0}は{1}文字以上である必要があります。',
array: '{0}には{1}個以上の項目が必要です。',
},
image: {
type: '画像は「jpg」「png」「jpeg」のみ有効です',
max: '{0}は{1}MB以下のファイルのみ有効です',
},
in: '{0}無効な値です',
in_array: '{0}は{1}と一致する必要があります',
integer: '{0}は整数のみ有効です',
ip: '{0}IPアドレスの書式のみ有効です',
ipv4: '{0}IPアドレス(IPv4)の書式のみ有効です',
ipv6: '{0}IPアドレス(IPv6)の書式のみ有効です',
json: '{0}正しいJSON文字列のみ有効です',
lt: {
numeric: '{0}は{1}未満である必要があります。',
file: '{0}は{1}キロバイト未満である必要があります。',
string: '{0}は{1}文字未満である必要があります。',
array: '{0}は{1}未満の項目を持つ必要があります。',
},
lte: {
numeric: '{0}は{1}以下である必要があります。',
file: '{0}は{1}キロバイト以下である必要があります。',
string: '{0}は{1}文字以下である必要があります。',
array: '{0}は{1}以上の項目を持つ必要があります。',
},
max: {
numeric: '{0}は{1}以下のみ有効です',
file: '{0}は{1}KB以下のファイルのみ有効です',
string: '{0}は{1}文字以下のみ有効です',
array: '{0}は{1}個以下のみ有効です',
tags: 'ジャンルは1作品{0}個までです。',
},
mimes: '{0}は {1}s タイプのみ有効です',
mimetypes: '{0}は {1}s タイプのみ有効です',
min: {
numeric: '{0}は{1}以上のみ有効です',
file: '{0}は{1}KB以上のファイルのみ有効です',
string: '{0}は{1}文字以上のみ有効です',
array: '{0}は{1}個以上のみ有効です',
},
not_in: '{0}無効な値です',
not_regex: 'The {0} format is invalid.',
numeric: '{0}は数字のみ有効です',
present: '{0} が存在しません',
regex: '{0} 無効な値です',
required: '{0}は必須です',
required_if: '{0}は{1}が{1}には必須です',
required_unless: '{0}は{1}が{1}sでなければ必須です',
required_with: '{0}は{1}sが入力されている場合は必須です',
required_with_all: '{0}は{1}sが入力されている場合は必須です',
required_without: '{0}は{1}sが入力されていない場合は必須です',
required_without_all: '{0}は{1}sが入力されていない場合は必須です',
same: '{0}は{1}と同じ場合のみ有効です',
size: {
numeric: '{0}は{1}のみ有効です',
file: '{0}は{1}KBのみ有効です',
string: '{0}は{1}文字のみ有効です',
array: '{0}は{1}個のみ有効です',
},
string: '{0}は文字列のみ有効です',
timezone: '{0}正しいタイムゾーンのみ有効です',
unique: '{0}は既に存在します',
already_exist: '{0}は既に登録されています',
uploaded: '{0}アップロードに失敗しました',
url: '{0}は正しいURL書式のみ有効です',
tel_format: '電話番号は英数字とハイフンのみ入力してください。',
point_length: 'ポイントは {0} 文字までしか入力できません',
point_value: '入力データの制限を超えました',
password: {
format: 'パスワードは半角英数字記号8文字以上入力してください。',
confirm: 'パスワードとパスワード(再確認)が一致しません。',
format_with_label: '{0}は半角英数字記号8文字以上入力してください。',
},
check_one: '少なくとも 1 つのアクティビティ タイプを選択してください',
date_future: '{0}には、今日より前の日付を指定してください',
}

24
src/main.ts Normal file
View File

@ -0,0 +1,24 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import i18n from '@/lang'
import App from './App.vue'
import Antd from 'ant-design-vue'
import router from './router'
import 'ant-design-vue/dist/reset.css'
import './assets/scss/main.scss'
const app = createApp(App)
app.use(i18n)
app.use(createPinia())
app.use(router)
app.use(Antd)
// Hide 'DOMNodeInserted'
const originalAddEventListener = Element.prototype.addEventListener
Element.prototype.addEventListener = function (type: any, listener: any, options: any) {
if (type === 'DOMNodeInserted') return // Ignore this event type
return originalAddEventListener.call(this, type, listener, options)
}
app.mount('#app')

13
src/router/index.ts Normal file
View File

@ -0,0 +1,13 @@
import { createRouter, createWebHistory } from 'vue-router'
import { routes } from './route'
const router = createRouter({
history: createWebHistory(),
routes,
})
router.beforeEach((to, from, next) => {
next()
})
export default router

37
src/router/route.ts Normal file
View File

@ -0,0 +1,37 @@
import * as Page from '@/views'
import { getToken } from '@/helpers'
const ifAuthenticated = (to: any, from: any, next: any) => {
if (getToken()) {
next()
return
}
next('/login')
}
const ifNotAuthenticated = (to: any, from: any, next: any) => {
if (!getToken()) {
next()
return
}
next('/')
}
export const routes: any = [
{
path: '/',
redirect: '/home',
children: [
{
path: 'home',
name: 'home',
component: Page.Home,
},
{
path: ':pathMatch(.*)*',
component: Page.NotFound,
name: 'page-not-found',
},
],
},
]

1
src/stores/index.ts Normal file
View File

@ -0,0 +1 @@
export {}

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<div class="box">Home page</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,73 @@
<script setup lang="ts">
import { useI18n } from 'vue3-i18n'
const { t } = useI18n()
</script>
<template>
<section class="page_404">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-10 col-sm-offset-1 text-center">
<div class="four_zero_four_bg">
<h1 class="text-center">404</h1>
</div>
<div class="contain_box_404">
<h3 class="h2">
{{ t('notFound.title') }}
</h3>
<a href="/reservations" class="link_404">
{{ t('notFound.back') }}
</a>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.page_404 {
padding: 40px 0;
background: var(--vt-c-white);
height: 100%;
text-align: center;
}
.page_404 img {
width: 100%;
}
.four_zero_four_bg {
background-repeat: no-repeat;
height: 400px;
background-position: center;
display: flex;
align-items: center;
justify-content: center;
}
.four_zero_four_bg h1 {
font-size: 80px;
margin-top: 50px;
}
.four_zero_four_bg h3 {
font-size: 80px;
}
.link_404 {
color: var(--vt-c-white);
padding: 10px 20px;
background: var(--vt-c-main);
margin: 20px 0;
display: inline-block;
}
.contain_box_404 {
margin-top: -50px;
}
</style>

2
src/views/index.ts Normal file
View File

@ -0,0 +1,2 @@
export const Home = () => import('@/views/Home/HomeView.vue')
export const NotFound = () => import('@/views/NotFound/NotFoundView.vue')

14
tsconfig.app.json Normal file
View File

@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

13
tsconfig.node.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": ["vite.config.*", "vitest.config.*"],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

24
vite.config.ts Normal file
View File

@ -0,0 +1,24 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import VueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 3008,
},
plugins: [
vue(),
vueJsx(),
VueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

2909
yarn.lock Normal file

File diff suppressed because it is too large Load Diff