From 921d454640997a33d7d17682ef8cff4053fd9257 Mon Sep 17 00:00:00 2001 From: minglipro Date: Wed, 20 Aug 2025 09:08:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建项目结构和基础配置 - 添加用户登录和车号查询功能 - 实现消息提示和加载指令 - 配置路由和全局状态管理 --- .gitattributes | 1 + .gitignore | 32 ++++++ .prettierrc.json | 28 +++++ .vscode/extensions.json | 6 + env.d.ts | 1 + index.html | 30 +++++ package.json | 48 ++++++++ src/apis/auth/index.ts | 16 +++ src/apis/auth/type.ts | 29 +++++ src/apis/bacth/index.ts | 4 + src/apis/bacth/type.ts | 26 +++++ src/apis/message/inedex.ts | 9 ++ src/apis/message/type.ts | 8 ++ src/apis/system/index.ts | 9 ++ src/apis/system/type.ts | 0 src/component/Icon.vue | 32 ++++++ src/layout/App.vue | 30 +++++ src/layout/index.vue | 143 +++++++++++++++++++++++ src/main.ts | 15 +++ src/page/chehaoca/index.vue | 110 ++++++++++++++++++ src/page/index/index.vue | 32 ++++++ src/page/user/index.vue | 15 +++ src/page/user/login.vue | 94 +++++++++++++++ src/page/user/register.vue | 120 ++++++++++++++++++++ src/page/user/user.vue | 39 +++++++ src/plugins/alova/index.ts | 145 ++++++++++++++++++++++++ src/plugins/directive/index.ts | 10 ++ src/plugins/directive/vEnter/index.ts | 42 +++++++ src/plugins/directive/vLoad/index.ts | 60 ++++++++++ src/plugins/directive/vLoad/index.vue | 72 ++++++++++++ src/plugins/directive/vLoad/load.scss | 40 +++++++ src/plugins/index.ts | 5 + src/plugins/pinia/index.ts | 11 ++ src/plugins/pinia/store/SettingStore.ts | 17 +++ src/plugins/pinia/store/UserStore.ts | 50 ++++++++ src/plugins/pinia/store/index.ts | 2 + src/plugins/router/index.ts | 56 +++++++++ src/resource/css/card.scss | 17 +++ src/resource/css/index.scss | 17 +++ src/resource/img/icon.png | Bin 0 -> 6462 bytes src/types/RouteMeta.d.ts | 8 ++ src/utils/AesUtils.ts | 88 ++++++++++++++ src/utils/UseApiLoading.ts | 23 ++++ src/utils/UseRefBool.ts | 26 +++++ src/utils/golob.ts | 18 +++ src/utils/message.ts | 31 +++++ tsconfig.app.json | 26 +++++ tsconfig.json | 11 ++ tsconfig.node.json | 19 ++++ vite.config.ts | 23 ++++ 50 files changed, 1694 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 .vscode/extensions.json create mode 100644 env.d.ts create mode 100644 index.html create mode 100644 package.json create mode 100644 src/apis/auth/index.ts create mode 100644 src/apis/auth/type.ts create mode 100644 src/apis/bacth/index.ts create mode 100644 src/apis/bacth/type.ts create mode 100644 src/apis/message/inedex.ts create mode 100644 src/apis/message/type.ts create mode 100644 src/apis/system/index.ts create mode 100644 src/apis/system/type.ts create mode 100644 src/component/Icon.vue create mode 100644 src/layout/App.vue create mode 100644 src/layout/index.vue create mode 100644 src/main.ts create mode 100644 src/page/chehaoca/index.vue create mode 100644 src/page/index/index.vue create mode 100644 src/page/user/index.vue create mode 100644 src/page/user/login.vue create mode 100644 src/page/user/register.vue create mode 100644 src/page/user/user.vue create mode 100644 src/plugins/alova/index.ts create mode 100644 src/plugins/directive/index.ts create mode 100644 src/plugins/directive/vEnter/index.ts create mode 100644 src/plugins/directive/vLoad/index.ts create mode 100644 src/plugins/directive/vLoad/index.vue create mode 100644 src/plugins/directive/vLoad/load.scss create mode 100644 src/plugins/index.ts create mode 100644 src/plugins/pinia/index.ts create mode 100644 src/plugins/pinia/store/SettingStore.ts create mode 100644 src/plugins/pinia/store/UserStore.ts create mode 100644 src/plugins/pinia/store/index.ts create mode 100644 src/plugins/router/index.ts create mode 100644 src/resource/css/card.scss create mode 100644 src/resource/css/index.scss create mode 100644 src/resource/img/icon.png create mode 100644 src/types/RouteMeta.d.ts create mode 100644 src/utils/AesUtils.ts create mode 100644 src/utils/UseApiLoading.ts create mode 100644 src/utils/UseRefBool.ts create mode 100644 src/utils/golob.ts create mode 100644 src/utils/message.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce8c118 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# 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 + +*.lock* diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..70eabd0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": true, + "arrowParens": "always", + "rangeStart": 0, + "vueIndentScriptAndStyle": false, + "endOfLine": "auto", + "semi": true, + "usePrettierrc": true, + "requirePragma": false, + "bracketSameLine": true, + "htmlWhitespaceSensitivity": "ignore", + "overrides": [ + { + "files": "*.json", + "options": { + "tabWidth": 4 + } + } + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..aecab6c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "Vue.volar", + "esbenp.prettier-vscode" + ] +} diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..510d6a8 --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + + + + + 利达外调服务平台 + + +
+
+ 加载中... +
+
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..574c6d5 --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "lida-app", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "format": "prettier --write src/" + }, + "dependencies": { + "@idux/cdk": "^2.6.3", + "@idux/components": "^2.6.3", + "@idux/pro": "^2.6.3", + "@types/vue-router": "^2.0.0", + "@vueuse/core": "^13.6.0", + "alova": "^3.3.4", + "ant-design-vue": "^4.2.6", + "base64-arraybuffer": "^1.0.2", + "js-md5": "^0.8.3", + "marked": "^16.1.2", + "naive-ui": "^2.42.0", + "pinia": "^3.0.3", + "pinia-plugin-persistedstate": "^4.5.0", + "sass-embedded": "^1.90.0", + "vue": "^3.5.18", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@iconify/vue": "^5.0.0", + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.16.5", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.7.0", + "npm-run-all2": "^8.0.4", + "prettier": "3.6.2", + "typescript": "~5.8.0", + "vite": "^7.0.6", + "vite-plugin-vue-devtools": "^8.0.0", + "vue-tsc": "^3.0.4" + } +} diff --git a/src/apis/auth/index.ts b/src/apis/auth/index.ts new file mode 100644 index 0000000..c5a3eff --- /dev/null +++ b/src/apis/auth/index.ts @@ -0,0 +1,16 @@ +import alova, { type AlovaResponse } from '@/plugins/alova'; +import type { LoginBody, RegisterBody, ResUserInfo } from '@/apis/auth/type.ts'; + +export const login = (lb: LoginBody) => + alova.Post>('/auth/login', lb, { + meta: { notbefore: true }, + }); +export const logout = () => alova.Delete('/auth/logout'); +export const register = (rb: RegisterBody) => { + const { passre, ...rbs } = rb; + return alova.Post>('/auth/register', rbs, { meta: { notbefore: true } }); +}; + +export const info = () => { + return alova.Get>('/auth/info', { meta: { notbefore: true } }); +}; diff --git a/src/apis/auth/type.ts b/src/apis/auth/type.ts new file mode 100644 index 0000000..9f2875f --- /dev/null +++ b/src/apis/auth/type.ts @@ -0,0 +1,29 @@ +export interface LoginBody { + passWold: string; + userName: string; +} + +export interface RegisterBody { + tel: string; + car: string; + pass: string; + passre: string; +} + +export interface User { + id: string; + carNumber: string; + telNumber: string; + passWold: '***'; + type: 1; + createTime: string; + createBy: string; + updateTime: string; + updateBy: string; + userName: string; +} + +export interface ResUserInfo { + user: User; + login: boolean; +} diff --git a/src/apis/bacth/index.ts b/src/apis/bacth/index.ts new file mode 100644 index 0000000..4ebb3e3 --- /dev/null +++ b/src/apis/bacth/index.ts @@ -0,0 +1,4 @@ +import alova, { type AlovaResponse } from '@/plugins/alova'; +import type { CarDataResType } from '@/apis/bacth/type.ts'; + +export const getMyCar = () => alova.Get>('/batch/mycar'); diff --git a/src/apis/bacth/type.ts b/src/apis/bacth/type.ts new file mode 100644 index 0000000..f88c2d1 --- /dev/null +++ b/src/apis/bacth/type.ts @@ -0,0 +1,26 @@ +export interface CarDataResType { + carNumber: string; + getOk: boolean; + carData: CarData; +} + +export interface CarData { + id: string; + key_info_of_binding_media: string; + car_weight: number; + remark: string; + pick_up_weight: string; + bind_obj_id: string; + cargo_transport_batch_number: string; + company: string; + goods_name: string; + op_user_id: string; + receive_company: string; + p_result: number; + status: number; + create_time: string; + declar_time: string; + update_time: string; + delete_time: 0; + receipt_remark: string; +} diff --git a/src/apis/message/inedex.ts b/src/apis/message/inedex.ts new file mode 100644 index 0000000..5e6a7c3 --- /dev/null +++ b/src/apis/message/inedex.ts @@ -0,0 +1,9 @@ +import alova, { type AlovaResponse } from '@/plugins/alova'; +import type { AppMessage } from '@/apis/message/type.ts'; + +const BaseUrl = '/system/message'; +export function getMessage() { + return alova.Get>(`${BaseUrl}/newest`, { + meta: { notbefore: true }, + }); +} diff --git a/src/apis/message/type.ts b/src/apis/message/type.ts new file mode 100644 index 0000000..7225366 --- /dev/null +++ b/src/apis/message/type.ts @@ -0,0 +1,8 @@ +export interface AppMessage { + id: string; + data: string; + createTime: string; + createBy: string; + updateTime: string; + updateBy: string; +} diff --git a/src/apis/system/index.ts b/src/apis/system/index.ts new file mode 100644 index 0000000..a82eebe --- /dev/null +++ b/src/apis/system/index.ts @@ -0,0 +1,9 @@ +import alova, { type AlovaResponse } from '@/plugins/alova'; + +export function getInfo() { + return alova.Get>('/system/info', { + meta: { + notbefore: true, + }, + }); +} diff --git a/src/apis/system/type.ts b/src/apis/system/type.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/component/Icon.vue b/src/component/Icon.vue new file mode 100644 index 0000000..c0c93bd --- /dev/null +++ b/src/component/Icon.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/layout/App.vue b/src/layout/App.vue new file mode 100644 index 0000000..dfdcbe8 --- /dev/null +++ b/src/layout/App.vue @@ -0,0 +1,30 @@ + + + + diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..8bb52b3 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..f808156 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,15 @@ +import { createApp } from 'vue'; +import '@res/css/index.scss'; +import App from '@/layout/App.vue'; +import { directive, pinia, router, UserStore } from '@/plugins'; + +const app = createApp(App); + +app.use(router); +app.use(pinia); +app.use(directive); + +app.mount('main[main]'); + +const userStore = UserStore(); +userStore.getLogin(); diff --git a/src/page/chehaoca/index.vue b/src/page/chehaoca/index.vue new file mode 100644 index 0000000..ebf18b5 --- /dev/null +++ b/src/page/chehaoca/index.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/page/index/index.vue b/src/page/index/index.vue new file mode 100644 index 0000000..0d2a2df --- /dev/null +++ b/src/page/index/index.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/page/user/index.vue b/src/page/user/index.vue new file mode 100644 index 0000000..59e20c4 --- /dev/null +++ b/src/page/user/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/page/user/login.vue b/src/page/user/login.vue new file mode 100644 index 0000000..5842863 --- /dev/null +++ b/src/page/user/login.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/page/user/register.vue b/src/page/user/register.vue new file mode 100644 index 0000000..c5d6488 --- /dev/null +++ b/src/page/user/register.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/page/user/user.vue b/src/page/user/user.vue new file mode 100644 index 0000000..b4e631a --- /dev/null +++ b/src/page/user/user.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/plugins/alova/index.ts b/src/plugins/alova/index.ts new file mode 100644 index 0000000..ee513e9 --- /dev/null +++ b/src/plugins/alova/index.ts @@ -0,0 +1,145 @@ +import { createAlova, Method } from 'alova'; +import adapterFetch from 'alova/fetch'; +import { router, UserStore } from '@/plugins'; +import message from '@/utils/message'; +import AesUtils from '@/utils/AesUtils'; +import golob from '@/utils/golob.ts'; + +export enum ResponseCodes { + ERROR_INTERNAL_SERVER = 'E00500', + ERROR_PAYMENT_REQUIRED = 'E00402', + ERROR_NOT_FOUND = 'E00404', + ERROR_METHOD_NOT_ALLOWED = 'E00405', + ERROR_UNAUTHORIZED = 'E00401', + ERROR_FORBIDDEN = 'E00403', + OK = 'S00200', +} + +export interface HaikewulianResponseType { + msg: string; + code: 200 | 401 | 500; + total: string; + data: T; + rows: K[]; +} + +export interface AlovaResponseType { + headers: Headers; + ok: boolean; + redirected: boolean; + status: number; + statusText: string; + type: ResponseType; + url: string; + body: ReadableStream | null; + bodyUsed: boolean; + method: Method; + + clone(): AlovaResponseType; + + arrayBuffer(): Promise; + + blob(): Promise; + + formData(): Promise; + + json(): T; + + text(): string; +} + +export type defResponse = { + Message: string; + Total: string; + Data: T; + Rows: K[]; + Code: ResponseCodes; + Page: PageType; +}; + +export interface PageType { + rows: K[]; + total: number; + pages: number; + pageIndex: number; + pagesize: number; +} + +export type AlovaResponse = AlovaResponseType>; + +const loginisundifind = () => { + const login_out = () => { + localStorage.setItem('Login_redirect', router.currentRoute.value.fullPath); + setTimeout(() => router.push('/user/login'), 10); + }; + const useauthStore = UserStore(); + if (useauthStore.ishaslogin) { + useauthStore.ClearLogin(); + golob.showLogingOut.open(); + } else { + useauthStore.ClearLogin(); + login_out(); + } +}; + +const alovaInstance = createAlova({ + baseURL: '/api/v3/app', + cacheFor: null, + requestAdapter: adapterFetch(), + timeout: 120000, + beforeRequest(method) { + const useauthStore = UserStore(); + if (useauthStore.ishaslogin) { + method.config.headers['Admin-Token'] = useauthStore.GetToken; + } else { + try { + if (method.meta.notbefore) return Promise.resolve(); + } catch (e) { + if (window.location.href.replace(window.location.origin, '') !== '/login') { + return Promise.reject(e); + } + return Promise.reject('Not logged in'); + } + } + }, + responded: { + onSuccess: async (response: AlovaResponseType & Response, method) => { + if (response.status !== 200) { + message.error(method.url + ' 请求失败 ' + response.status + ' ' + response.statusText); + throw new Error(response.statusText); + } + const data = response; + const text = await response.text(); + let json: defResponse | null = null; + json = JSON.parse(text); + if (json?.Data) { + json.Data = JSON.parse(await AesUtils.decrypt(json.Data, response.headers.get('server-time'))); + } + if (json?.Rows) { + json.Rows = JSON.parse(await AesUtils.decrypt(json.Rows, response.headers.get('server-time'))); + } + if (json?.Page) { + json.Page = JSON.parse(await AesUtils.decrypt(json.Page, response.headers.get('server-time'))); + } + if (json?.Code !== ResponseCodes.OK) { + if (json?.Code === ResponseCodes.ERROR_UNAUTHORIZED) { + loginisundifind(); + } else { + message.error(json.Message); + } + throw new Error(json.Message); + } + data.json = () => json; + data.text = () => text; + data.method = method; + return data; + }, + onError: (err, method) => { + message.error(err.message); + const useauthStore = UserStore(); + if (!useauthStore.ishaslogin) router.push('/login'); + }, + }, +}); + +export default alovaInstance; diff --git a/src/plugins/directive/index.ts b/src/plugins/directive/index.ts new file mode 100644 index 0000000..af0bf99 --- /dev/null +++ b/src/plugins/directive/index.ts @@ -0,0 +1,10 @@ +import VEnter from './vEnter'; +import VLoad from './vLoad'; +import type { App } from 'vue'; + +export default { + install(app: App) { + app.directive('loading', VLoad); + app.directive('enter', VEnter); + }, +}; diff --git a/src/plugins/directive/vEnter/index.ts b/src/plugins/directive/vEnter/index.ts new file mode 100644 index 0000000..32fd7bb --- /dev/null +++ b/src/plugins/directive/vEnter/index.ts @@ -0,0 +1,42 @@ +import type { Directive } from 'vue'; + +type objfunc = { + key: HTMLElement; + funs: (e: KeyboardEvent) => void; + time: Date; +}; +const data: objfunc[] = []; + +function deldata(el: HTMLElement) { + return data.findIndex((data) => data.key === el); +} + +const VEnter: Directive = { + mounted(el: HTMLElement, binding, vnode) { + const { value } = binding; + const handler = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + if (data.sort((a, b) => (a.time < b.time ? 1 : -1))[0].key === el) { + value(e); + } + } + }; + data.push({ + key: el, + funs: handler, + time: new Date(), + }); + document.body.addEventListener('keydown', handler); + }, + beforeUnmount(el: HTMLElement, binding, vnode) { + const index = deldata(el); + const datas = data[index]; + if (index !== -1) { + document.body.removeEventListener('keydown', datas.funs); + data.splice(index, 1); + } + }, +}; +export default VEnter; diff --git a/src/plugins/directive/vLoad/index.ts b/src/plugins/directive/vLoad/index.ts new file mode 100644 index 0000000..50ce221 --- /dev/null +++ b/src/plugins/directive/vLoad/index.ts @@ -0,0 +1,60 @@ +import { createVNode, render, type VNode } from 'vue'; +import type { Directive, DirectiveBinding } from 'vue'; +import LoadComponent from './index.vue'; +import './load.scss'; + +type datamapitem = { + timeout?: any; + resizeObserver?: ResizeObserver; + Vnode: VNode; +}; +const datamap = new Map(); +const lodaon = (el: HTMLElement, binding: DirectiveBinding) => { + const item: HTMLElement | null = el.querySelector('#loading'); + if (item) { + const datas = datamap.get(el); + if (datas) { + clearTimeout(datas?.timeout); + datas.timeout = null; + datamap.set(el, datas); + item.style.opacity = '1'; + } + return; + } + const Mynode = createVNode(LoadComponent, {}); + datamap.set(el, { + Vnode: Mynode, + }); + render(Mynode, el); +}; +const lodaoff = (el: HTMLElement, binding: DirectiveBinding) => { + const item: HTMLElement | null = el.querySelector('#loading'); + if (!item) return; + item.style.opacity = '0'; + const tmout = setTimeout(() => { + render(null, el); + datamap.delete(el); + }, 300); + const datas = datamap.get(el); + if (datas) { + datas.timeout = tmout; + datamap.set(el, datas); + } +}; +const load = (el: HTMLElement, binding: DirectiveBinding) => { + if (binding.value) { + lodaon(el, binding); + } else { + lodaoff(el, binding); + } +}; +const VLoad: Directive = { + mounted: (el, binding) => load(el, binding), + updated: (el, binding) => load(el, binding), + beforeUnmount: (el, binding) => { + render(null, el); + datamap.delete(el); + }, +}; + +export default VLoad; diff --git a/src/plugins/directive/vLoad/index.vue b/src/plugins/directive/vLoad/index.vue new file mode 100644 index 0000000..fd2505c --- /dev/null +++ b/src/plugins/directive/vLoad/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/plugins/directive/vLoad/load.scss b/src/plugins/directive/vLoad/load.scss new file mode 100644 index 0000000..bad89a0 --- /dev/null +++ b/src/plugins/directive/vLoad/load.scss @@ -0,0 +1,40 @@ +.loginfather { + position: relative; +} + +.loading-full { + position: absolute; + left: 0; + top: 0; + height: 100vh; + width: 100vw; + + .el-loading-spinner { + width: 50px; + height: 50px; + color: #e70303; + + .circular { + color: #e70303; + } + } +} + +.Loading_out { + opacity: 0; +} + +html[theme="dark"] { + .ix-spin-spinner { + --ix-spin-mask-bg-color: rgba(235, 235, 235, 0.2) !important; + } +} + +html .ix-spin-spinner { + --ix-spin-mask-bg-color: rgba(20, 20, 20, 0.3) !important; +} + +.ix-spin-spinner { + background-color: var(--ix-spin-mask-bg-color) !important; + backdrop-filter: blur(1px); +} diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..f835867 --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,5 @@ +export * from './router'; +export * from './pinia'; +import directive from './directive'; + +export { directive }; diff --git a/src/plugins/pinia/index.ts b/src/plugins/pinia/index.ts new file mode 100644 index 0000000..fa8c992 --- /dev/null +++ b/src/plugins/pinia/index.ts @@ -0,0 +1,11 @@ +import { createPinia } from 'pinia'; + +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; + +const pinia = createPinia(); + +pinia.use(piniaPluginPersistedstate); + +export { pinia }; + +export * from './store'; diff --git a/src/plugins/pinia/store/SettingStore.ts b/src/plugins/pinia/store/SettingStore.ts new file mode 100644 index 0000000..dc8fa2f --- /dev/null +++ b/src/plugins/pinia/store/SettingStore.ts @@ -0,0 +1,17 @@ +import { defineStore } from 'pinia'; + +import { useColorMode } from '@vueuse/core'; + +const SettingStore = defineStore('Setting', { + state: () => { + const theme = useColorMode({ + attribute: 'theme', + }); + return { theme: theme.system }; + }, + getters: {}, + actions: {}, + persist: true, +}); + +export { SettingStore }; diff --git a/src/plugins/pinia/store/UserStore.ts b/src/plugins/pinia/store/UserStore.ts new file mode 100644 index 0000000..d6b45bd --- /dev/null +++ b/src/plugins/pinia/store/UserStore.ts @@ -0,0 +1,50 @@ +import { defineStore } from 'pinia'; +import { info, logout } from '@/apis/auth'; +import { ref } from 'vue'; +import type { User } from '@/apis/auth/type.ts'; + +const UserStore = defineStore('User', { + state: () => { + const userData = ref({ + carNumber: '', + createBy: '', + createTime: '', + id: '', + passWold: '***', + telNumber: '', + type: 1, + updateBy: '', + updateTime: '', + userName: '', + }); + const userDataRemberData = ref({ user: '', pass: '', renb: false }); + return { ishaslogin: false, token: '', userData, userDataRemberData }; + }, + actions: { + getLogin() { + info().then((a) => { + console.log(a.json()); + this.ishaslogin = a.json().Data.login; + Object.assign(this.userData, a.json().Data.user); + }); + }, + ClearLogin() { + this.ishaslogin = false; + this.token = ''; + }, + Logout() { + logout().finally(() => { + this.ClearLogin(); + window.location.replace('/user/login'); + }); + }, + }, + getters: { + GetToken(): string { + return 'Bearer ' + this.token; + }, + }, + persist: true, +}); + +export { UserStore }; diff --git a/src/plugins/pinia/store/index.ts b/src/plugins/pinia/store/index.ts new file mode 100644 index 0000000..58ba048 --- /dev/null +++ b/src/plugins/pinia/store/index.ts @@ -0,0 +1,2 @@ +export * from './SettingStore'; +export * from './UserStore'; diff --git a/src/plugins/router/index.ts b/src/plugins/router/index.ts new file mode 100644 index 0000000..da38c40 --- /dev/null +++ b/src/plugins/router/index.ts @@ -0,0 +1,56 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + name: 'layout', + path: '/', + component: () => import('@/layout/index.vue'), + children: [ + { + name: 'index', + path: '/', + component: () => import('@/page/index/index.vue'), + meta: { name: 'index' }, + }, + { + name: 'chehaoca', + path: '/chehaoca', + component: () => import('@/page/chehaoca/index.vue'), + meta: { name: 'chehaoca' }, + }, + { + name: 'users', + path: '/user', + component: () => import('@/page/user/index.vue'), + meta: { name: 'user' }, + children: [ + { + name: 'login', + path: '/user/login', + component: () => import('@/page/user/login.vue'), + meta: { name: 'user' }, + }, + { + name: 'register', + path: '/user/register', + component: () => import('@/page/user/register.vue'), + meta: { name: 'user' }, + }, + { + name: 'user', + path: '/user', + component: () => import('@/page/user/user.vue'), + meta: { name: 'user' }, + }, + ], + }, + ], + }, +]; + +const router = createRouter({ + history: createWebHistory(), + routes: routes, +}); + +export { router }; diff --git a/src/resource/css/card.scss b/src/resource/css/card.scss new file mode 100644 index 0000000..7e054fd --- /dev/null +++ b/src/resource/css/card.scss @@ -0,0 +1,17 @@ +html[theme="dark"] { + --card-border-color: #404040; +} + +html { + --card-border-color: #cdcdcd; +} + + +.card { + border-radius: 5px; + border: 1px solid var(--card-border-color); +} + +.card-line { + border-top: 1px solid var(--card-border-color); +} diff --git a/src/resource/css/index.scss b/src/resource/css/index.scss new file mode 100644 index 0000000..f06c245 --- /dev/null +++ b/src/resource/css/index.scss @@ -0,0 +1,17 @@ +@use "card"; + +html[theme="dark"] { + background-color: #202020; + color: #d8d8d8; +} + +*[f-c-c] { + display: flex; + justify-content: center; + align-items: center; +} + +a { + color: #1bbb44; + text-decoration: none; +} diff --git a/src/resource/img/icon.png b/src/resource/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee510fcf00a8cb7cf641c27fe79b4181e4f9c8d GIT binary patch literal 6462 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 literal 0 HcmV?d00001 diff --git a/src/types/RouteMeta.d.ts b/src/types/RouteMeta.d.ts new file mode 100644 index 0000000..b401f4c --- /dev/null +++ b/src/types/RouteMeta.d.ts @@ -0,0 +1,8 @@ +import 'vue-router'; +export {}; + +declare module 'vue-router' { + interface RouteMeta { + name: string; + } +} diff --git a/src/utils/AesUtils.ts b/src/utils/AesUtils.ts new file mode 100644 index 0000000..bd08928 --- /dev/null +++ b/src/utils/AesUtils.ts @@ -0,0 +1,88 @@ +import { decode as base64Decode, encode as base64Encode } from 'base64-arraybuffer'; +import { md5 } from 'js-md5'; + +export default class AesUtils { + private static readonly ALGORITHM = 'AES-GCM'; + private static readonly GCM_IV_LENGTH = 12; + private static readonly GCM_TAG_LENGTH = 16; + + public static async encrypt(sSrc: string, sKey: string): Promise { + if (!sKey) return null; + + try { + // 生成密钥 + const key = await this.createSecretKey(sKey); + + // 生成随机IV (12字节) + const iv = crypto.getRandomValues(new Uint8Array(this.GCM_IV_LENGTH)); + + // 执行加密 + const encrypted = await crypto.subtle.encrypt( + { + name: this.ALGORITHM, + iv: iv, + tagLength: this.GCM_TAG_LENGTH * 8, + }, + key, + new TextEncoder().encode(sSrc), + ); + + // 将IV和加密数据转换为Base64 + const ivBase64 = base64Encode(iv.buffer); + const encryptedBase64 = base64Encode(encrypted); + + // 组合并再次Base64编码(与Java实现匹配) + const combined = `${ivBase64}:${encryptedBase64}`; + const combinedBuffer = new TextEncoder().encode(combined); + + // 确保传递的是纯ArrayBuffer + return base64Encode(combinedBuffer.buffer); + } catch (e) { + console.error('加密错误:', e); + return null; + } + } + + public static async decrypt(sSrc: string, sKey: string): Promise { + if (!sKey) return null; + + try { + // 解码外层Base64 + const decodedBuffer = base64Decode(sSrc); + const decoded = new TextDecoder().decode(decodedBuffer); + + // 分割IV和加密数据 + const parts = decoded.split(':', 2); + if (parts.length !== 2) return null; + + const iv = base64Decode(parts[0]); + const encryptedData = base64Decode(parts[1]); + + if (iv.byteLength !== this.GCM_IV_LENGTH) return null; + + const key = await this.createSecretKey(sKey); + + // 执行解密 + const decrypted = await crypto.subtle.decrypt( + { + name: this.ALGORITHM, + iv: new Uint8Array(iv), + tagLength: this.GCM_TAG_LENGTH * 8, + }, + key, + encryptedData, + ); + + return new TextDecoder().decode(decrypted); + } catch (e) { + console.error('解密错误:', e); + return null; + } + } + + private static async createSecretKey(sKey: string): Promise { + // 使用js-md5库生成与Java一致的MD5哈希 + const md5Hash = md5.arrayBuffer(sKey); + return crypto.subtle.importKey('raw', md5Hash, { name: this.ALGORITHM }, false, ['encrypt', 'decrypt']); + } +} diff --git a/src/utils/UseApiLoading.ts b/src/utils/UseApiLoading.ts new file mode 100644 index 0000000..e6a0519 --- /dev/null +++ b/src/utils/UseApiLoading.ts @@ -0,0 +1,23 @@ +import UseRefBool, { type UseRefBoolType } from '@/utils/UseRefBool.ts'; + +/** + * 执行一个异步函数并返回一个包含状态控制和结果的元组 + * @param t 要执行的异步函数,该函数不接受参数并返回Promise + * @returns 返回一个只读元组,包含: + * - UseRefBoolType: 用于控制和监视异步操作状态的引用对象 + * - Promise: 包装后的Promise,其结果类型与传入函数的返回值类型相同 + */ +export default function Promise>(t: T): readonly [UseRefBoolType, T] { + const us = UseRefBool(); + return [ + us, + (...args: P[]) => + new Promise((resolve, reject) => { + us[1].open(); + t(...args) + .then(resolve) + .catch((...err) => reject(...err)) // 修复点:显式调用 reject + .finally(() => us[1].close()); + }) as T, + ] as const; +} diff --git a/src/utils/UseRefBool.ts b/src/utils/UseRefBool.ts new file mode 100644 index 0000000..9cd6a0e --- /dev/null +++ b/src/utils/UseRefBool.ts @@ -0,0 +1,26 @@ +import { type Ref, ref } from 'vue'; + +function UseRefBool( + v = false, +): readonly [Ref, { open: () => void; close: () => void; reset: () => void; requter: () => void }] { + const s = ref(v); + return [ + s, + { + open() { + s.value = true; + }, + close() { + s.value = false; + }, + reset() { + s.value = v; + }, + requter() { + s.value = !s.value; + }, + }, + ] as const; +} +export default UseRefBool; +export type UseRefBoolType = ReturnType; diff --git a/src/utils/golob.ts b/src/utils/golob.ts new file mode 100644 index 0000000..c938537 --- /dev/null +++ b/src/utils/golob.ts @@ -0,0 +1,18 @@ +import type { LoadingBarApiInjection } from 'naive-ui/es/loading-bar/src/LoadingBarProvider'; +import UseRefBool from '@/utils/UseRefBool'; +import type { MessageInstance } from 'ant-design-vue/es/message/interface'; + +class Golob { + public message: MessageInstance | undefined; + public loadingBar: LoadingBarApiInjection | undefined; + public showLogingOut: ReturnType = UseRefBool(); +} + +const golob = new Golob(); +export default golob; +window.golob = golob; +declare global { + interface Window { + golob: typeof golob; + } +} diff --git a/src/utils/message.ts b/src/utils/message.ts new file mode 100644 index 0000000..f9b6e52 --- /dev/null +++ b/src/utils/message.ts @@ -0,0 +1,31 @@ +import type { MessageInstance } from 'ant-design-vue/es/message/interface'; +import golob from '@/utils/golob'; + +export default { + successOK() { + golob.message?.success('操作成功'); + }, + info(...data) { + golob.message?.info(...data); + }, + success(...data) { + golob.message?.success(...data); + }, + error(...data) { + golob.message?.error(...data); + }, + warning(...data) { + golob.message?.warning(...data); + }, + loading(...data) { + golob.message?.loading(...data); + }, + open(...data) { + golob.message?.open(...data); + }, + destroy(...data) { + golob.message?.destroy(...data); + }, +} as MessageInstance & { + successOK: () => void; +}; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..d449809 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "env.d.ts", + "src/**/*", + "src/**/*.vue" + ], + "exclude": [ + "src/**/__tests__/*" + ], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ], + "@com/*": [ + "src/components/*" + ], + "@res/*": [ + "src/resource/*" + ] + } + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..a83dfc9 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..848fdf3 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import { fileURLToPath, URL } from 'node:url'; + +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueDevTools from 'vite-plugin-vue-devtools'; + +// https://vite.dev/config/ +export default defineConfig({ + server: { + host: '0.0.0.0', + proxy: { + '/api': 'http://localhost:7546', + }, + }, + plugins: [vue(), vueDevTools()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + '@res': fileURLToPath(new URL('./src/resource', import.meta.url)), + '@com': fileURLToPath(new URL('./src/component', import.meta.url)), + }, + }, +});