commit da83f3b2bb43243e3b3dd66b89da12eb073b4a93
Author: zc <2064281269@qq.com>
Date: Sat Aug 23 10:39:53 2025 +0800
build: init
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..7c4376e
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,18 @@
+/* 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'
+ ],
+ rules: {
+ 'vue/multi-word-component-names': 'off'
+ },
+ parserOptions: {
+ ecmaVersion: 'latest'
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..88e2486
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+# 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
+*.mjs
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..66e2335
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://json.schemastore.org/prettierrc",
+ "semi": false,
+ "tabWidth": 2,
+ "singleQuote": true,
+ "printWidth": 100,
+ "trailingComma": "none"
+}
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..93ea3e7
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+ "recommendations": [
+ "Vue.volar",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode"
+ ]
+}
diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz
new file mode 100644
index 0000000..ca6beef
Binary files /dev/null and b/.yarn/install-state.gz differ
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 0000000..3186f3f
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1 @@
+nodeLinker: node-modules
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4bd4c1a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# dc_loan_ui
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..2627cb1
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,4 @@
+npm config set registry https://registry.npmmirror.com &&
+node -v &&
+npm install --loglevel verbose &&
+npm run build
\ No newline at end of file
diff --git a/env.d.ts b/env.d.ts
new file mode 100644
index 0000000..f93e971
--- /dev/null
+++ b/env.d.ts
@@ -0,0 +1,10 @@
+///
+
+declare module '*.vue' {
+ import { DefineComponent } from 'vue'
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
+
+declare module '@wangeditor/editor-for-vue'
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..92f2352
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ DC信贷系统
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fa3a28c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "dc_loan_ui",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "run-p \"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": {
+ "@wangeditor/editor": "^5.1.23",
+ "@wangeditor/editor-for-vue": "^5.1.12",
+ "axios": "^1.6.8",
+ "element-plus": "^2.7.6",
+ "lz-utils-lib": "^1.0.51",
+ "pinia": "^2.1.7",
+ "trans-config": "^0.0.4",
+ "vue": "^3.4.21",
+ "vue-router": "^4.3.0",
+ "vue3-admin-ui": "^0.0.18"
+ },
+ "devDependencies": {
+ "@rushstack/eslint-patch": "^1.8.0",
+ "@tsconfig/node20": "^20.1.4",
+ "@types/crypto-js": "^4.2.2",
+ "@types/node": "^20.12.5",
+ "@types/nprogress": "^0.2.3",
+ "@types/path-browserify": "^1.0.2",
+ "@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",
+ "autoprefixer": "^10.4.19",
+ "crypto-js": "^4.2.0",
+ "eslint": "^8.57.0",
+ "eslint-plugin-vue": "^9.23.0",
+ "npm-run-all2": "^6.1.2",
+ "nprogress": "^0.2.0",
+ "pinia-plugin-persistedstate": "^3.2.1",
+ "postcss": "^8.4.38",
+ "prettier": "^3.2.5",
+ "sass": "^1.77.2",
+ "tailwindcss": "^3.4.4",
+ "typescript": "~5.4.0",
+ "vite": "^5.2.8",
+ "vite-plugin-svg-icons": "^2.0.1",
+ "vue-tsc": "^2.0.11",
+ "watermark-plus": "^1.6.1"
+ },
+ "volta": {
+ "node": "20.15.0",
+ "yarn": "4.3.0"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..203d7ba
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..6dd9cfa
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/src/api/index.ts b/src/api/index.ts
new file mode 100644
index 0000000..f05f4bc
--- /dev/null
+++ b/src/api/index.ts
@@ -0,0 +1,32 @@
+import request from '@/utils/request'
+import login from './login'
+
+const totalApiConfig = {
+ login
+}
+
+Object.values(totalApiConfig).forEach(apiConfig => {
+ Object.values(apiConfig).forEach(arr => {
+ const [url, other = {}] = arr as [string, Recordable]
+ Object.setPrototypeOf(arr, {
+ post: (data: any = {}) => {
+ let newUrl = url
+ if (other.id) {
+ newUrl += url.endsWith('/') ? data.id : `/${data.id}`
+ delete data.id
+ }
+ return request.post({ url: newUrl, data, ...other })
+ },
+ get: (data: any = {}) => {
+ let newUrl = url
+ if (other.id) {
+ newUrl += url.endsWith('/') ? data.id : `/${data.id}`
+ delete data.id
+ }
+ return request.get({ url: newUrl, params: data, ...other })
+ }
+ })
+ })
+})
+
+export default totalApiConfig
diff --git a/src/api/login.ts b/src/api/login.ts
new file mode 100644
index 0000000..d6aef6b
--- /dev/null
+++ b/src/api/login.ts
@@ -0,0 +1,6 @@
+const login = {
+ apiGetUserRoleInfo: ['/user/getRoleUserInfo'], // 获取用户信息及权限
+ apiLogout: ['/login/out'] // 退出登录
+}
+
+export default login
\ No newline at end of file
diff --git a/src/assets/icons/advert.svg b/src/assets/icons/advert.svg
new file mode 100644
index 0000000..5adcf43
--- /dev/null
+++ b/src/assets/icons/advert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/brand.svg b/src/assets/icons/brand.svg
new file mode 100644
index 0000000..e4b7cee
--- /dev/null
+++ b/src/assets/icons/brand.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/bug.svg b/src/assets/icons/bug.svg
new file mode 100644
index 0000000..05a150d
--- /dev/null
+++ b/src/assets/icons/bug.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/cascader.svg b/src/assets/icons/cascader.svg
new file mode 100644
index 0000000..e256024
--- /dev/null
+++ b/src/assets/icons/cascader.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/chart.svg b/src/assets/icons/chart.svg
new file mode 100644
index 0000000..27728fb
--- /dev/null
+++ b/src/assets/icons/chart.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/client.svg b/src/assets/icons/client.svg
new file mode 100644
index 0000000..ad4bc15
--- /dev/null
+++ b/src/assets/icons/client.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg
new file mode 100644
index 0000000..5b5057f
--- /dev/null
+++ b/src/assets/icons/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/close_all.svg b/src/assets/icons/close_all.svg
new file mode 100644
index 0000000..aa13cd7
--- /dev/null
+++ b/src/assets/icons/close_all.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/close_left.svg b/src/assets/icons/close_left.svg
new file mode 100644
index 0000000..e5708ea
--- /dev/null
+++ b/src/assets/icons/close_left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/close_other.svg b/src/assets/icons/close_other.svg
new file mode 100644
index 0000000..212e6c2
--- /dev/null
+++ b/src/assets/icons/close_other.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/close_right.svg b/src/assets/icons/close_right.svg
new file mode 100644
index 0000000..14d3cf3
--- /dev/null
+++ b/src/assets/icons/close_right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/coupon.svg b/src/assets/icons/coupon.svg
new file mode 100644
index 0000000..2f952b2
--- /dev/null
+++ b/src/assets/icons/coupon.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/dashboard.svg b/src/assets/icons/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/src/assets/icons/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/dict.svg b/src/assets/icons/dict.svg
new file mode 100644
index 0000000..22a8278
--- /dev/null
+++ b/src/assets/icons/dict.svg
@@ -0,0 +1,18 @@
+
diff --git a/src/assets/icons/dict_item.svg b/src/assets/icons/dict_item.svg
new file mode 100644
index 0000000..903109a
--- /dev/null
+++ b/src/assets/icons/dict_item.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/assets/icons/download.svg b/src/assets/icons/download.svg
new file mode 100644
index 0000000..c896951
--- /dev/null
+++ b/src/assets/icons/download.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/drag.svg b/src/assets/icons/drag.svg
new file mode 100644
index 0000000..4185d3c
--- /dev/null
+++ b/src/assets/icons/drag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg
new file mode 100644
index 0000000..d26101f
--- /dev/null
+++ b/src/assets/icons/edit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/exit-fullscreen.svg b/src/assets/icons/exit-fullscreen.svg
new file mode 100644
index 0000000..485c128
--- /dev/null
+++ b/src/assets/icons/exit-fullscreen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/eye-open.svg b/src/assets/icons/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/src/assets/icons/eye-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/eye.svg b/src/assets/icons/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/src/assets/icons/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/fullscreen.svg b/src/assets/icons/fullscreen.svg
new file mode 100644
index 0000000..0e86b6f
--- /dev/null
+++ b/src/assets/icons/fullscreen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/github.svg b/src/assets/icons/github.svg
new file mode 100644
index 0000000..db0a0d4
--- /dev/null
+++ b/src/assets/icons/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/goods-list.svg b/src/assets/icons/goods-list.svg
new file mode 100644
index 0000000..fcb971e
--- /dev/null
+++ b/src/assets/icons/goods-list.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/goods.svg b/src/assets/icons/goods.svg
new file mode 100644
index 0000000..60c1c73
--- /dev/null
+++ b/src/assets/icons/goods.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/guide.svg b/src/assets/icons/guide.svg
new file mode 100644
index 0000000..b271001
--- /dev/null
+++ b/src/assets/icons/guide.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/homepage.svg b/src/assets/icons/homepage.svg
new file mode 100644
index 0000000..48f4e24
--- /dev/null
+++ b/src/assets/icons/homepage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/lab.svg b/src/assets/icons/lab.svg
new file mode 100644
index 0000000..d4d60aa
--- /dev/null
+++ b/src/assets/icons/lab.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/language.svg b/src/assets/icons/language.svg
new file mode 100644
index 0000000..0082b57
--- /dev/null
+++ b/src/assets/icons/language.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/link.svg b/src/assets/icons/link.svg
new file mode 100644
index 0000000..d3f9e5a
--- /dev/null
+++ b/src/assets/icons/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/menu.svg b/src/assets/icons/menu.svg
new file mode 100644
index 0000000..92c364c
--- /dev/null
+++ b/src/assets/icons/menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/message.svg b/src/assets/icons/message.svg
new file mode 100644
index 0000000..ea1ddef
--- /dev/null
+++ b/src/assets/icons/message.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/money.svg b/src/assets/icons/money.svg
new file mode 100644
index 0000000..60f7acf
--- /dev/null
+++ b/src/assets/icons/money.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/monitor.svg b/src/assets/icons/monitor.svg
new file mode 100644
index 0000000..bc308cb
--- /dev/null
+++ b/src/assets/icons/monitor.svg
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/nested.svg b/src/assets/icons/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/src/assets/icons/nested.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/number.svg b/src/assets/icons/number.svg
new file mode 100644
index 0000000..ad5ce9a
--- /dev/null
+++ b/src/assets/icons/number.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/order.svg b/src/assets/icons/order.svg
new file mode 100644
index 0000000..8f2107e
--- /dev/null
+++ b/src/assets/icons/order.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/password.svg b/src/assets/icons/password.svg
new file mode 100644
index 0000000..6c64def
--- /dev/null
+++ b/src/assets/icons/password.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/peoples.svg b/src/assets/icons/peoples.svg
new file mode 100644
index 0000000..383b82d
--- /dev/null
+++ b/src/assets/icons/peoples.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/perm.svg b/src/assets/icons/perm.svg
new file mode 100644
index 0000000..b38d065
--- /dev/null
+++ b/src/assets/icons/perm.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/publish.svg b/src/assets/icons/publish.svg
new file mode 100644
index 0000000..e9b489c
--- /dev/null
+++ b/src/assets/icons/publish.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/qq.svg b/src/assets/icons/qq.svg
new file mode 100644
index 0000000..98da395
--- /dev/null
+++ b/src/assets/icons/qq.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/rabbitmq.svg b/src/assets/icons/rabbitmq.svg
new file mode 100644
index 0000000..65aa198
--- /dev/null
+++ b/src/assets/icons/rabbitmq.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/rate.svg b/src/assets/icons/rate.svg
new file mode 100644
index 0000000..aa3b14d
--- /dev/null
+++ b/src/assets/icons/rate.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/redis.svg b/src/assets/icons/redis.svg
new file mode 100644
index 0000000..2f1d62d
--- /dev/null
+++ b/src/assets/icons/redis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/refresh.svg b/src/assets/icons/refresh.svg
new file mode 100644
index 0000000..1f549f1
--- /dev/null
+++ b/src/assets/icons/refresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/role.svg b/src/assets/icons/role.svg
new file mode 100644
index 0000000..c484b13
--- /dev/null
+++ b/src/assets/icons/role.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/security.svg b/src/assets/icons/security.svg
new file mode 100644
index 0000000..bcd9d2e
--- /dev/null
+++ b/src/assets/icons/security.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/shopping.svg b/src/assets/icons/shopping.svg
new file mode 100644
index 0000000..8d2b4bf
--- /dev/null
+++ b/src/assets/icons/shopping.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/size.svg b/src/assets/icons/size.svg
new file mode 100644
index 0000000..ddb25b8
--- /dev/null
+++ b/src/assets/icons/size.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/skill.svg b/src/assets/icons/skill.svg
new file mode 100644
index 0000000..a3b7312
--- /dev/null
+++ b/src/assets/icons/skill.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/system.svg b/src/assets/icons/system.svg
new file mode 100644
index 0000000..63feb20
--- /dev/null
+++ b/src/assets/icons/system.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/theme.svg b/src/assets/icons/theme.svg
new file mode 100644
index 0000000..5982a2f
--- /dev/null
+++ b/src/assets/icons/theme.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/tree.svg b/src/assets/icons/tree.svg
new file mode 100644
index 0000000..d40a414
--- /dev/null
+++ b/src/assets/icons/tree.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/user.svg b/src/assets/icons/user.svg
new file mode 100644
index 0000000..e4c7b38
--- /dev/null
+++ b/src/assets/icons/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/uv.svg b/src/assets/icons/uv.svg
new file mode 100644
index 0000000..ca4c301
--- /dev/null
+++ b/src/assets/icons/uv.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/valid_code.svg b/src/assets/icons/valid_code.svg
new file mode 100644
index 0000000..39bf478
--- /dev/null
+++ b/src/assets/icons/valid_code.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/assets/icons/wechat.svg b/src/assets/icons/wechat.svg
new file mode 100644
index 0000000..35de4bc
--- /dev/null
+++ b/src/assets/icons/wechat.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/images/error.gif b/src/assets/images/error.gif
new file mode 100644
index 0000000..cd6e0d9
Binary files /dev/null and b/src/assets/images/error.gif differ
diff --git a/src/assets/images/home_bg.jpg b/src/assets/images/home_bg.jpg
new file mode 100644
index 0000000..e16bcdb
Binary files /dev/null and b/src/assets/images/home_bg.jpg differ
diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png
new file mode 100644
index 0000000..ccab497
Binary files /dev/null and b/src/assets/images/logo.png differ
diff --git a/src/assets/styles/element-plus.scss b/src/assets/styles/element-plus.scss
new file mode 100644
index 0000000..6ac7799
--- /dev/null
+++ b/src/assets/styles/element-plus.scss
@@ -0,0 +1,58 @@
+:root {
+ // 这里可以设置你自定义的颜色变量
+ // 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改
+ --el-color-primary-dark: #0d84ff;
+ // element plus 2.1.0 禁用文本色值和正常文本色值无法区分问题
+ --el-text-color-disabled: #ccc;
+}
+
+// 覆盖 element-plus 的样式
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+ font-weight: 400 !important;
+}
+
+.el-upload {
+ input[type='file'] {
+ display: none !important;
+ }
+}
+
+.el-upload__input {
+ display: none;
+}
+
+// dropdown
+.el-dropdown-menu {
+ a {
+ display: block;
+ }
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+ box-sizing: content-box;
+}
+
+// 选中行背景色值
+.el-table__body tr.current-row td {
+ background-color: #e1f3d8b5 !important;
+}
+
+// card 的header统一高度
+.el-card__header {
+ height: 60px !important;
+}
+
+// 表格表头和表体未对齐
+.el-table__header col[name='gutter'] {
+ display: table-cell !important;
+}
+
+.el-button--small {
+ padding: 7px 15px;
+ height: auto;
+}
+.el-select .select-trigger {
+ width: 100%;
+}
diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss
new file mode 100644
index 0000000..86dc681
--- /dev/null
+++ b/src/assets/styles/index.scss
@@ -0,0 +1,82 @@
+@import "https://at.alicdn.com/t/c/font_3492670_f2cnzrhtoo9.css";
+@import './variables.module.scss';
+@import './element-plus.scss';
+@import './sidebar.scss';
+@import './module.scss';
+
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ background-color: #f2f2f2;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+ font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
+ Microsoft YaHei, Arial, sans-serif;
+}
+
+svg,
+img {
+ display: inline;
+}
+
+label {
+ font-weight: 700;
+}
+
+html {
+ height: 100%;
+ box-sizing: border-box;
+}
+
+#app {
+ height: 100%;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+a:focus,
+a:active {
+ outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+ cursor: pointer;
+ color: inherit;
+ text-decoration: none;
+}
+
+div:focus {
+ outline: none;
+}
+
+.clearfix {
+ &:after {
+ visibility: hidden;
+ display: block;
+ font-size: 0;
+ content: ' ';
+ clear: both;
+ height: 0;
+ }
+}
+
+// main-container global css
+.app-container {
+ padding: 20px;
+}
+
+.search {
+ padding: 18px 0 0 10px;
+ margin-bottom: 10px;
+ box-shadow: var(--el-box-shadow-light);
+ border-radius: var(--el-card-border-radius);
+ border: 1px solid var(--el-card-border-color);
+}
diff --git a/src/assets/styles/mixin.scss b/src/assets/styles/mixin.scss
new file mode 100644
index 0000000..3ca7168
--- /dev/null
+++ b/src/assets/styles/mixin.scss
@@ -0,0 +1,28 @@
+@mixin clearfix {
+ &:after {
+ content: '';
+ display: table;
+ clear: both;
+ }
+}
+
+@mixin scrollBar {
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+}
+
+@mixin relative {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/assets/styles/module.scss b/src/assets/styles/module.scss
new file mode 100644
index 0000000..8cc097e
--- /dev/null
+++ b/src/assets/styles/module.scss
@@ -0,0 +1,47 @@
+.search-module {
+ padding: 10px;
+ background: #fff;
+ .el-input, .el-select {
+ width: 200px;
+ }
+}
+.table-module {
+ margin-top: 10px;
+ padding: 15px 10px 10px;
+ background: #fff;
+ .el-table {
+ margin-top: 10px;
+ }
+ .page-wrap {
+ height: 50px;
+ margin: 0 -15px;
+ position: relative;
+ .el-pagination {
+ position: absolute;
+ right: 15px;
+ top: 40%;
+ .btn-prev,
+ .btn-next,
+ .el-pager .number {
+ border: 1px solid #d9d9d9;
+ }
+ }
+ }
+}
+.dialog-module {
+ min-width: 700px;
+ .el-form-item {
+ margin-right: 20px;
+ width: calc(50% - 20px);
+ .el-input-number {
+ width: 100%;
+ }
+ .el-select {
+ width: 100%;
+ }
+ }
+ .item-100 {
+ margin-right: 20px;
+ width: 100% !important;
+ }
+}
diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss
new file mode 100644
index 0000000..ee3bafa
--- /dev/null
+++ b/src/assets/styles/sidebar.scss
@@ -0,0 +1,219 @@
+#app {
+ .main-container {
+ min-height: 100%;
+ transition: margin-left 0.28s;
+ margin-left: $sideBarWidth;
+ position: relative;
+ }
+
+ .sidebar-container {
+ transition: width 0.28s;
+ width: $sideBarWidth !important;
+ background-color: $menuBg;
+ height: 100%;
+ position: fixed;
+ font-size: 0px;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1001;
+ overflow: hidden;
+
+ // reset element-ui css
+ .horizontal-collapse-transition {
+ transition: 0s width ease-in-out, 0s padding-left ease-in-out,
+ 0s padding-right ease-in-out;
+ }
+
+ .scrollbar-wrapper {
+ overflow-x: hidden !important;
+ }
+
+ .el-scrollbar__bar.is-vertical {
+ right: 0px;
+ }
+
+ .el-scrollbar {
+ height: 100%;
+ }
+
+ &.has-logo {
+ .el-scrollbar {
+ height: calc(100% - 50px);
+ }
+ }
+
+ .is-horizontal {
+ display: none;
+ }
+
+ a {
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ }
+
+ .svg-icon {
+ margin-right: 16px;
+ }
+
+ .sub-el-icon {
+ margin-right: 12px;
+ margin-left: -2px;
+ }
+
+ .el-menu {
+ border: none;
+ height: 100%;
+ width: 100% !important;
+ }
+
+ // menu hover
+ .submenu-title-noDropdown,
+ .el-sub-menu__title {
+ &:hover {
+ background-color: $menuHover !important;
+ }
+ }
+
+ .is-active > .el-sub-menu__title {
+ color: $subMenuActiveText !important;
+ }
+
+ & .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .el-sub-menu .el-menu-item {
+ min-width: $sideBarWidth !important;
+ background-color: $subMenuBg !important;
+
+ &:hover {
+ background-color: $subMenuHover !important;
+ }
+ }
+ }
+
+ .hideSidebar {
+ .sidebar-container {
+ width: 60px !important;
+ .svg-icon {
+ margin-right: 0px;
+ }
+ }
+
+ .main-container {
+ margin-left: 60px;
+ }
+
+ .submenu-title-noDropdown {
+ padding: 0 !important;
+ position: relative;
+
+ .el-tooltip {
+ padding: 0 !important;
+
+ .svg-icon {
+ margin-left: 20px;
+ }
+
+ .sub-el-icon {
+ margin-left: 19px;
+ }
+ }
+ }
+
+ .el-sub-menu {
+ overflow: hidden;
+
+ & > .el-sub-menu__title {
+ .el-sub-menu__icon-arrow {
+ display: none;
+ margin-right: -12px!important;
+ }
+ }
+ }
+
+ .el-menu--collapse {
+ .el-sub-menu {
+ & > .el-sub-menu__title {
+ & > span {
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ visibility: hidden;
+ display: inline-block;
+ }
+ }
+ }
+ }
+ }
+
+ .el-menu--collapse .el-menu .el-sub-menu {
+ min-width: $sideBarWidth !important;
+ }
+
+ // mobile responsive
+ .mobile {
+ .main-container {
+ margin-left: 0px;
+ }
+
+ .sidebar-container {
+ transition: transform 0.28s;
+ width: $sideBarWidth !important;
+ }
+
+ &.hideSidebar {
+ .sidebar-container {
+ pointer-events: none;
+ transition-duration: 0.3s;
+ transform: translate3d(-$sideBarWidth, 0, 0);
+ }
+ }
+ }
+
+ .withoutAnimation {
+ .main-container,
+ .sidebar-container {
+ transition: none;
+ }
+ }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+ & > .el-menu {
+ .svg-icon {
+ margin-right: 16px;
+ }
+ .sub-el-icon {
+ margin-right: 12px;
+ margin-left: -2px;
+ }
+ }
+
+ .nest-menu .el-sub-menu > .el-sub-menu__title,
+ .el-menu-item {
+ &:hover {
+ // you can use $subMenuHover
+ background-color: $menuHover !important;
+ }
+ }
+
+ // the scroll bar appears when the subMenu is too long
+ > .el-menu--popup {
+ max-height: 100vh;
+ overflow-y: auto;
+
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+ }
+}
diff --git a/src/assets/styles/tailwind.css b/src/assets/styles/tailwind.css
new file mode 100644
index 0000000..be36aa6
--- /dev/null
+++ b/src/assets/styles/tailwind.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
\ No newline at end of file
diff --git a/src/assets/styles/variables.module.scss b/src/assets/styles/variables.module.scss
new file mode 100644
index 0000000..8e20bc3
--- /dev/null
+++ b/src/assets/styles/variables.module.scss
@@ -0,0 +1,25 @@
+// sidebar
+$menuText: #bfcbd9;
+$menuActiveText: #409eff;
+$subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951
+
+$menuBg: #304156;
+$menuHover: #263445;
+
+$subMenuBg: #1f2d3d;
+$subMenuHover: #001528;
+
+$sideBarWidth: 210px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+ menuText: $menuText;
+ menuActiveText: $menuActiveText;
+ subMenuActiveText: $subMenuActiveText;
+ menuBg: $menuBg;
+ menuHover: $menuHover;
+ subMenuBg: $subMenuBg;
+ subMenuHover: $subMenuHover;
+ sideBarWidth: $sideBarWidth;
+}
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..7bbf4fc
--- /dev/null
+++ b/src/components/Breadcrumb/index.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+ {{ item.meta.title }}
+ {{ item.meta.title }}
+
+
+
+
+
+
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..6704cae
--- /dev/null
+++ b/src/components/Hamburger/index.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..f89dc6f
--- /dev/null
+++ b/src/components/SvgIcon/index.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/directive/index.ts b/src/directive/index.ts
new file mode 100644
index 0000000..40162b0
--- /dev/null
+++ b/src/directive/index.ts
@@ -0,0 +1,27 @@
+import type { Directive, DirectiveBinding } from 'vue'
+import useStore from '@/stores'
+
+/**
+ * 按钮权限校验
+ */
+export const perm: Directive = {
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
+ const { value } = binding
+ const { user } = useStore()
+ if (value) {
+ const requiredPerms = value // DOM绑定需要的按钮权限标识
+
+ const hasPerm = user.perms?.some(perm => {
+ return requiredPerms.includes(perm)
+ })
+
+ if (!hasPerm) {
+ el.parentNode && el.parentNode.removeChild(el)
+ }
+ } else {
+ throw new Error(
+ "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
+ )
+ }
+ }
+}
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..e64f417
--- /dev/null
+++ b/src/layout/components/AppMain.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..ace5ea2
--- /dev/null
+++ b/src/layout/components/Navbar.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..d893307
--- /dev/null
+++ b/src/layout/components/Sidebar/Link.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..dc67a96
--- /dev/null
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..fdf66c6
--- /dev/null
+++ b/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+ {{ onlyOneChild.meta.title }}
+
+
+
+
+
+
+
+
+ {{ item.meta.title }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..f843f4f
--- /dev/null
+++ b/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue
new file mode 100644
index 0000000..db17cc8
--- /dev/null
+++ b/src/layout/components/TagsView/ScrollPane.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue
new file mode 100644
index 0000000..5144edb
--- /dev/null
+++ b/src/layout/components/TagsView/index.vue
@@ -0,0 +1,370 @@
+
+
+
+
+ {{ tag.meta.title }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..20c3f55
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..2d5893e
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,36 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
+import './assets/styles/tailwind.css'
+
+import '../types/global.d.ts'
+
+
+// 自定义UI组件
+import adminUIDesign from 'vue3-admin-ui'
+import 'vue3-admin-ui/dist/style.css'
+
+// 自定义样式
+import '@/assets/styles/index.scss'
+
+// 引入svg注册脚本
+import 'virtual:svg-icons-register'
+
+// 路由处理
+import '@/permission'
+import { perm } from '@/directive/index'
+
+import App from './App.vue'
+import router from './router'
+
+const app = createApp(App)
+app.directive('perm', perm)
+app.use(adminUIDesign)
+app.use(ElementPlus, { locale: zhCn, size: 'small', zIndex: 3000 })
+app.use(createPinia().use(piniaPluginPersistedstate))
+app.use(router)
+
+app.mount('#app')
diff --git a/src/mixins/index.ts b/src/mixins/index.ts
new file mode 100644
index 0000000..0af9c6c
--- /dev/null
+++ b/src/mixins/index.ts
@@ -0,0 +1,87 @@
+import { handleData } from 'lz-utils-lib'
+import { apiProtocolTempList } from '@/api/protocol/template'
+import api from '@/api'
+const { apiCommonConfig } = api.common
+
+/** 获取公共配置列表 */
+export const handleGetCommonConfig = async (typeList: any) => {
+ return await apiCommonConfig.post!({ typeList }).then((res: any) => {
+ const data = res.data.map((item: { type: string; configList: any[] }) => {
+ return {
+ type: item.type,
+ configList: handleData.formatOptions(item.configList, 'value', 'code')
+ }
+ })
+ return data
+ })
+}
+
+// 格式化公共配置数据
+export const formatCommonConfig = (data: any, typeCode: string, valueType = 'string') => {
+ let arr
+ if (valueType === 'number') {
+ arr = data.find(
+ (it: { type: string }) => it.type === typeCode
+ ).configList.map((item: any) => {
+ return { label: item.label, value: Number(item.value) }
+ })
+ } else {
+ arr = data.find(
+ (it: { type: string }) => it.type === typeCode
+ ).configList.map((item: any) => {
+ return { label: item.label, value: item.value }
+ })
+ }
+ return arr
+}
+
+/** 获取全部资方列表 */
+export const handleGetSupplierList = async () => {
+ return await api.common.getSupplierList.get!<[]>().then((res) => {
+ const supplierId = handleData.formatOptions(res.data, 'supplierName', 'id')
+ const supplierCode = handleData.formatOptions(res.data, 'supplierName', 'supplierCode')
+ return {
+ supplierId,
+ supplierCode
+ }
+ })
+}
+
+/** 获取广告全部资方列表 */
+export const handleGetAdvSupplierList = async () => {
+ return await api.common.getAdvSupplierList.get!<[]>().then((res) => {
+ const advId = handleData.formatOptions(res.data, 'advName', 'id')
+ const advCode = handleData.formatOptions(res.data, 'advName', 'advChannel')
+ const advIdByCode = handleData.formatOptions(res.data, 'id', 'advChannel')
+ return {
+ advId,
+ advCode,
+ advIdByCode
+ }
+ })
+}
+
+/** 获取用户的操作渠道 */
+export const handleGetUserChannels = async () => {
+ return await api.common.getUserChannelList.get!<[]>().then((res) => {
+ const userChannel = handleData.formatOptions(res.data, 'pkgName', 'pkgCode')
+ const userChannelCode = handleData.formatOptions(res.data, 'pkgCode', 'pkgCode')
+ return {
+ userChannel,
+ userChannelCode
+ }
+ })
+}
+
+/** 获取全部协议列表 */
+export const handleGetProtocolTempList = async () => {
+ return await apiProtocolTempList().then((res) => {
+ const data = res.data.map((it: { id: any; protocolName: any }) => {
+ return {
+ label: `${it.id} - ${it.protocolName}`,
+ value: it.id
+ }
+ })
+ return data
+ })
+}
diff --git a/src/permission.ts b/src/permission.ts
new file mode 100644
index 0000000..b87b08a
--- /dev/null
+++ b/src/permission.ts
@@ -0,0 +1,63 @@
+import router from '@/router'
+import useStore from '@/stores'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+NProgress.configure({ showSpinner: false })
+
+let loaded = false
+
+// 白名单路由
+const whiteList = ['/login', '/errorpage']
+
+router.beforeEach(async (to, from, next) => {
+ NProgress.start()
+ const { user, permission } = useStore()
+ const hasToken = user.tokenValue
+ if (hasToken) {
+ // 登录成功,跳转到首页
+ if (to.path === '/login') {
+ loaded = false
+ router.removeRoute('errorpage')
+ next()
+ NProgress.done()
+ } else {
+ if (!loaded) {
+ try {
+ const menuList = (await user.getUserRoleInfo()) as any[]
+ const accessRoutes: any = await permission.generateRoutes(menuList)
+ accessRoutes.forEach((route: any) => {
+ router.addRoute(route)
+ })
+ next({ ...to, replace: true })
+ loaded = true
+ } catch (error) {
+ console.warn(error)
+ // 移除 token 并跳转登录页
+ await user.onResetToken()
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
+ }
+ } else {
+ next()
+ }
+ }
+ } else {
+ // 未登录可以访问白名单页面(登录页面)
+ if (whiteList.indexOf(to.path) !== -1) {
+ if (to.path === '/login') {
+ loaded = false
+ router.removeRoute('errorpage')
+ }
+ next()
+ } else {
+ loaded = false
+ router.removeRoute('errorpage')
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
+ }
+ }
+})
+
+router.afterEach(() => {
+ NProgress.done()
+})
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..971d50a
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,24 @@
+import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
+import useStore from '@/stores'
+import SystemRouter from './system'
+
+// 创建路由
+const router = createRouter({
+ history: createWebHistory('/opui/'),
+ routes: SystemRouter as RouteRecordRaw[],
+ // 刷新时,滚动条位置还原
+ scrollBehavior: () => ({ left: 0, top: 0 })
+})
+
+// 重置路由
+export const resetRouter = () => {
+ const { permission } = useStore()
+ permission.routes.forEach((route) => {
+ const name = route.name
+ if (name && router.hasRoute(name)) {
+ router.removeRoute(name)
+ }
+ })
+}
+
+export default router
diff --git a/src/router/system.ts b/src/router/system.ts
new file mode 100644
index 0000000..53f012f
--- /dev/null
+++ b/src/router/system.ts
@@ -0,0 +1,26 @@
+import type { RouteRecordRaw } from 'vue-router'
+export const Layout = () => import('@/layout/index.vue')
+
+// 静态路由
+export const constantRoutes: Array = [
+ {
+ path: '/',
+ component: Layout,
+ children: [
+ {
+ path: '/home',
+ name: 'home',
+ meta: { title: '首页', hidden: false, icon: 'dc-icon-shouye' },
+ component: () => import('@/views/home/index.vue')
+ },
+ {
+ path: '/errorpage',
+ name: 'errorpage',
+ component: () => import('@/views/home/errorpage.vue'),
+ meta: { title: '错误页', hidden: true }
+ }
+ ]
+ }
+]
+
+export default constantRoutes
diff --git a/src/stores/counter.ts b/src/stores/counter.ts
new file mode 100644
index 0000000..b6757ba
--- /dev/null
+++ b/src/stores/counter.ts
@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+ const count = ref(0)
+ const doubleCount = computed(() => count.value * 2)
+ function increment() {
+ count.value++
+ }
+
+ return { count, doubleCount, increment }
+})
diff --git a/src/stores/index.ts b/src/stores/index.ts
new file mode 100644
index 0000000..62f0a73
--- /dev/null
+++ b/src/stores/index.ts
@@ -0,0 +1,15 @@
+import useAppStore from './modules/app'
+import useUserStore from './modules/user'
+import usePermissionStore from './modules/permission'
+import useSettingStore from './modules/settings/index'
+import useTagsViewStore from './modules/tagsView'
+
+const useStore = () => ({
+ app: useAppStore(),
+ user: useUserStore(),
+ permission: usePermissionStore(),
+ setting: useSettingStore(),
+ tagsView: useTagsViewStore()
+})
+
+export default useStore
diff --git a/src/stores/modules/app.ts b/src/stores/modules/app.ts
new file mode 100644
index 0000000..5244228
--- /dev/null
+++ b/src/stores/modules/app.ts
@@ -0,0 +1,44 @@
+import { defineStore } from 'pinia'
+import type { AppState } from './types'
+
+const sidebarStatus =
+ localStorage.getItem('sidebarStatus') && localStorage.getItem('sidebarStatus') === '1'
+ ? true
+ : false
+
+const useAppStore = defineStore({
+ id: 'app',
+ state: (): AppState => ({
+ device: 'desktop',
+ sidebar: {
+ opened: sidebarStatus ? sidebarStatus : true,
+ withoutAnimation: false
+ },
+ size: localStorage.getItem('size') || 'default'
+ }),
+ actions: {
+ toggleSidebar() {
+ this.sidebar.opened = !this.sidebar.opened
+ this.sidebar.withoutAnimation = false
+ if (this.sidebar.opened) {
+ localStorage.setItem('sidebarStatus', '1')
+ } else {
+ localStorage.setItem('sidebarStatus', '0')
+ }
+ },
+ closeSideBar(withoutAnimation: any) {
+ localStorage.setItem('sidebarStatus', '0')
+ this.sidebar.opened = false
+ this.sidebar.withoutAnimation = withoutAnimation
+ },
+ toggleDevice(device: string) {
+ this.device = device
+ },
+ setSize(size: string) {
+ this.size = size
+ localStorage.setItem('size', size)
+ }
+ }
+})
+
+export default useAppStore
diff --git a/src/stores/modules/permission.ts b/src/stores/modules/permission.ts
new file mode 100644
index 0000000..d94eed5
--- /dev/null
+++ b/src/stores/modules/permission.ts
@@ -0,0 +1,92 @@
+import { defineStore } from 'pinia'
+import type { RouteRecordRaw } from 'vue-router'
+import type { PermissionState } from './types'
+import { constantRoutes } from '@/router/system'
+
+const modules = import.meta.glob('../../views/**/**.vue')
+export const Layout = () => import('@/layout/index.vue')
+
+export const filterAsyncRoutes = (routes: any[], parentPath?: string): any[] => {
+ const res: any[] = []
+ routes &&
+ routes.forEach((route) => {
+ const path =
+ parentPath && route.path.startsWith(parentPath)
+ ? route.path.slice(parentPath.length + 1)
+ : route.path
+ const tmp = {
+ name: route.path,
+ path: path,
+ menuId: route.id,
+ type: route.resourceType,
+ component: () => {},
+ meta: {
+ hidden: !!route.visible,
+ title: route.resourceName,
+ icon: route.icon,
+ keepAlive: route.isCache,
+ keepAliveName: getPureName(route.path),
+ link: route.link
+ },
+ children: route.childList
+ }
+ if (tmp.type === 0) {
+ tmp.component = Layout
+ } else {
+ const component = modules[`../../views${route.path}.vue`] as any
+ if (component) {
+ tmp.component = component
+ } else {
+ tmp.component = modules['../../views/home/errorpage.vue']
+ }
+ }
+ res.push(tmp)
+ if (tmp.children) {
+ tmp.children = filterAsyncRoutes(tmp.children, path)
+ }
+ })
+ return res
+}
+
+const getPureName = (path: string) => {
+ const arr = path.split('/')
+ return arr.reduce((str: string, cur: string, index: number) => {
+ if (arr.length === index + 1 && cur === 'index') {
+ return str
+ }
+ if (!str && cur) {
+ return (str = cur)
+ } else if (str && cur) {
+ return (str += '-' + cur)
+ }
+ return str
+ }, '')
+}
+
+const usePermissionStore = defineStore({
+ id: 'permission',
+ state: (): PermissionState => ({
+ routes: [],
+ addRoutes: []
+ }),
+ actions: {
+ setRoutes(routes: RouteRecordRaw[]) {
+ this.addRoutes = routes
+ this.routes = constantRoutes.concat(routes)
+ },
+ generateRoutes(menuList: any[]) {
+ return new Promise((resolve) => {
+ const routes = filterAsyncRoutes(menuList)
+ routes.push({
+ path: '/:pathMatch(.*)*',
+ name: 'error',
+ redirect: '/error'
+ })
+ this.setRoutes(routes)
+ resolve(routes)
+ })
+ }
+ }
+})
+
+export default usePermissionStore
diff --git a/src/stores/modules/settings/index.ts b/src/stores/modules/settings/index.ts
new file mode 100644
index 0000000..b93362b
--- /dev/null
+++ b/src/stores/modules/settings/index.ts
@@ -0,0 +1,46 @@
+import { defineStore } from 'pinia'
+import type { SettingState } from '../types'
+import defaultSettings from './settings'
+
+const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
+const el = document.documentElement
+
+export const useSettingStore = defineStore({
+ id: 'setting',
+ state: (): SettingState => ({
+ theme:
+ localStorage.getItem('theme') || getComputedStyle(el).getPropertyValue(`--el-color-primary`),
+ showSettings: showSettings,
+ tagsView:
+ localStorage.getItem('tagsView') !== null ? localStorage.getItem('tagsView') : tagsView,
+ fixedHeader: fixedHeader,
+ sidebarLogo: sidebarLogo
+ }),
+ actions: {
+ async changeSetting(payload: { key: string; value: any }) {
+ const { key, value } = payload
+ switch (key) {
+ case 'theme':
+ this.theme = value
+ break
+ case 'showSettings':
+ this.showSettings = value
+ break
+ case 'fixedHeader':
+ this.fixedHeader = value
+ break
+ case 'tagsView':
+ this.tagsView = value
+ localStorage.setItem('tagsView', value)
+ break
+ case 'sidebarLogo':
+ this.sidebarLogo = value
+ break
+ default:
+ break
+ }
+ }
+ }
+})
+
+export default useSettingStore
diff --git a/src/stores/modules/settings/settings.ts b/src/stores/modules/settings/settings.ts
new file mode 100644
index 0000000..65c2da0
--- /dev/null
+++ b/src/stores/modules/settings/settings.ts
@@ -0,0 +1,20 @@
+interface DefaultSettings {
+ title: string
+ showSettings: boolean
+ tagsView: boolean
+ fixedHeader: boolean
+ sidebarLogo: boolean
+ errorLog: string
+}
+
+const defaultSettings: DefaultSettings = {
+ title: 'dc-loan-ui',
+ showSettings: true,
+ tagsView: true,
+ fixedHeader: false,
+ // 是否显示Logo
+ sidebarLogo: true,
+ errorLog: 'production'
+}
+
+export default defaultSettings
diff --git a/src/stores/modules/tagsView.ts b/src/stores/modules/tagsView.ts
new file mode 100644
index 0000000..1a6d8a7
--- /dev/null
+++ b/src/stores/modules/tagsView.ts
@@ -0,0 +1,177 @@
+import { defineStore } from 'pinia'
+import type { TagsViewState } from './types'
+
+const useTagsViewStore = defineStore({
+ id: 'tagsView',
+ state: (): TagsViewState => ({
+ visitedViews: [],
+ cachedViews: [] // keepAlive 缓存页面
+ }),
+ actions: {
+ addVisitedView(view: any) {
+ if (this.visitedViews.some((v) => v.path === view.path)) return
+ if (view.meta && view.meta.affix) {
+ this.visitedViews.unshift(
+ Object.assign({}, view, {
+ title: view.meta?.title || 'no-name'
+ })
+ )
+ } else {
+ this.visitedViews.push(
+ Object.assign({}, view, {
+ title: view.meta?.title || 'no-name'
+ })
+ )
+ }
+ },
+ addCachedView(view: any) {
+ if (this.cachedViews.includes(view?.meta?.keepAliveName)) return
+ if (view.meta.keepAlive) {
+ this.cachedViews.push(view.meta.keepAliveName)
+ }
+ },
+ delVisitedView(view: any) {
+ return new Promise((resolve) => {
+ for (const [i, v] of this.visitedViews.entries()) {
+ if (v.path === view.path) {
+ this.visitedViews.splice(i, 1)
+ break
+ }
+ }
+ resolve([...this.visitedViews])
+ })
+ },
+ delCachedView(view: any) {
+ return new Promise((resolve) => {
+ const index = this.cachedViews.indexOf(view.meta.keepAliveName)
+ index > -1 && this.cachedViews.splice(index, 1)
+ resolve([...this.cachedViews])
+ })
+ },
+ delOtherVisitedViews(view: any) {
+ return new Promise((resolve) => {
+ this.visitedViews = this.visitedViews.filter((v) => {
+ return v.meta?.affix || v.path === view.path
+ })
+ resolve([...this.visitedViews])
+ })
+ },
+ delOtherCachedViews(view: any) {
+ return new Promise((resolve) => {
+ const index = this.cachedViews.indexOf(view.name)
+ if (index > -1) {
+ this.cachedViews = this.cachedViews.slice(index, index + 1)
+ } else {
+ // if index = -1, there is no cached tags
+ this.cachedViews = []
+ }
+ resolve([...this.cachedViews])
+ })
+ },
+
+ updateVisitedView(view: any) {
+ for (let v of this.visitedViews) {
+ if (v.path === view.path) {
+ v = Object.assign(v, view)
+ break
+ }
+ }
+ },
+ addView(view: any) {
+ this.addVisitedView(view)
+ this.addCachedView(view)
+ },
+ delView(view: any) {
+ return new Promise((resolve) => {
+ this.delVisitedView(view)
+ this.delCachedView(view)
+ resolve({
+ visitedViews: [...this.visitedViews],
+ cachedViews: [...this.cachedViews]
+ })
+ })
+ },
+ delOtherViews(view: any) {
+ return new Promise((resolve) => {
+ this.delOtherVisitedViews(view)
+ this.delOtherCachedViews(view)
+ resolve({
+ visitedViews: [...this.visitedViews],
+ cachedViews: [...this.cachedViews]
+ })
+ })
+ },
+ delLeftViews(view: any) {
+ return new Promise((resolve) => {
+ const currIndex = this.visitedViews.findIndex((v) => v.path === view.path)
+ if (currIndex === -1) {
+ return
+ }
+ this.visitedViews = this.visitedViews.filter((item, index) => {
+ // affix:true 固定tag,例如“首页”
+ if (index >= currIndex || (item.meta && item.meta.affix)) {
+ return true
+ }
+
+ const cacheIndex = this.cachedViews.indexOf(item.name as string)
+ if (cacheIndex > -1) {
+ this.cachedViews.splice(cacheIndex, 1)
+ }
+ return false
+ })
+ resolve({
+ visitedViews: [...this.visitedViews]
+ })
+ })
+ },
+ delRightViews(view: any) {
+ return new Promise((resolve) => {
+ const currIndex = this.visitedViews.findIndex((v) => v.path === view.path)
+ if (currIndex === -1) {
+ return
+ }
+ this.visitedViews = this.visitedViews.filter((item, index) => {
+ // affix:true 固定tag,例如“首页”
+ if (index <= currIndex || (item.meta && item.meta.affix)) {
+ return true
+ }
+
+ const cacheIndex = this.cachedViews.indexOf(item.name as string)
+ if (cacheIndex > -1) {
+ this.cachedViews.splice(cacheIndex, 1)
+ }
+ return false
+ })
+ resolve({
+ visitedViews: [...this.visitedViews]
+ })
+ })
+ },
+ delAllViews() {
+ return new Promise((resolve) => {
+ const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix)
+ this.visitedViews = affixTags
+ this.cachedViews = []
+ resolve({
+ visitedViews: [...this.visitedViews],
+ cachedViews: [...this.cachedViews]
+ })
+ })
+ },
+ delAllVisitedViews() {
+ return new Promise((resolve) => {
+ const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix)
+ this.visitedViews = affixTags
+ resolve([...this.visitedViews])
+ })
+ },
+ delAllCachedViews() {
+ return new Promise((resolve) => {
+ this.cachedViews = []
+ resolve([...this.cachedViews])
+ })
+ }
+ }
+})
+
+export default useTagsViewStore
diff --git a/src/stores/modules/types.ts b/src/stores/modules/types.ts
new file mode 100644
index 0000000..5f64679
--- /dev/null
+++ b/src/stores/modules/types.ts
@@ -0,0 +1,42 @@
+import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
+
+export interface AppState {
+ device: string
+ sidebar: {
+ opened: boolean
+ withoutAnimation: boolean
+ }
+ size: string
+}
+
+export interface PermissionState {
+ routes: RouteRecordRaw[]
+ addRoutes: RouteRecordRaw[]
+}
+
+export interface SettingState {
+ theme: string
+ tagsView: any
+ fixedHeader: boolean
+ showSettings: boolean
+ sidebarLogo: boolean
+}
+
+export interface UserState {
+ tokenValue: string
+ tokenName: string
+ userPhone: string
+ userNickName: string
+ userId: number
+ perms: string[]
+ tenantId: number
+}
+
+export interface TagView extends Partial {
+ title?: string
+}
+
+export interface TagsViewState {
+ visitedViews: TagView[]
+ cachedViews: string[]
+}
diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts
new file mode 100644
index 0000000..0b9fa4d
--- /dev/null
+++ b/src/stores/modules/user.ts
@@ -0,0 +1,74 @@
+import { defineStore } from 'pinia'
+import type { UserState } from './types'
+import { resetRouter } from '@/router'
+import api from '@/api'
+const { apiGetUserRoleInfo, apiLogout } = api.login
+
+const useUserStore = defineStore('user', {
+ state: (): UserState => {
+ return {
+ tokenName: '',
+ tokenValue: '',
+ userPhone: '',
+ userNickName: '',
+ userId: NaN,
+ perms: [],
+ tenantId: NaN
+ }
+ },
+ actions: {
+ async RESET_STATE() {
+ this.$reset()
+ },
+ // 登录
+ onLogin(data: { tokenValue: string; tokenName: string }) {
+ this.tokenValue = data.tokenValue
+ this.tokenName = data.tokenName
+ },
+ // 获取用户信息(昵称、头像、角色集合、权限集合)
+ getUserRoleInfo() {
+ return new Promise((resolve) => {
+ apiGetUserRoleInfo.get!({ tenantCode: 'loan_system' }).then((res: any) => {
+ const {
+ userInfo: { userPhone, userNickName, tenantId, id },
+ menuResourceList,
+ buttonResourceList
+ } = res.data
+ this.userPhone = userPhone
+ this.userNickName = userNickName
+ this.userId = id
+ this.tenantId = tenantId
+ this.perms.splice(
+ 0,
+ this.perms.length,
+ ...(buttonResourceList ? buttonResourceList.map((item: any) => item.resourceCode) : [])
+ )
+ resolve(menuResourceList)
+ })
+ })
+ },
+ // 退出登录
+ onLogout() {
+ return new Promise((resolve) => {
+ apiLogout.get!().then(() => {
+ this.RESET_STATE()
+ resetRouter()
+ resolve(null)
+ })
+ })
+ },
+ // 清除 Token
+ onResetToken() {
+ return new Promise((resolve) => {
+ this.RESET_STATE()
+ resolve(null)
+ })
+ }
+ },
+ persist: {
+ key: 'login',
+ storage: localStorage
+ }
+})
+
+export default useUserStore
diff --git a/src/utils/config.ts b/src/utils/config.ts
new file mode 100644
index 0000000..9805a25
--- /dev/null
+++ b/src/utils/config.ts
@@ -0,0 +1,87 @@
+import { ElMessage } from 'element-plus'
+import { reg } from 'lz-utils-lib'
+
+/**
+ * @name 判断当前key是否为空值
+ * @param value
+ * @returns boolean true是,false否
+ */
+export const formatEmptyKeys = (obj: any) => {
+ for (const key in obj) {
+ if (
+ !Object.prototype.hasOwnProperty.call(obj, key) ||
+ obj[key] === null ||
+ obj[key] === undefined ||
+ obj[key] === ''
+ ) {
+ return true
+ }
+ }
+ return false
+}
+
+/**
+ * @name 验证对象中只能存在一个key
+ * @param value
+ * @returns boolean true是,false否
+ */
+export const formatValueObject = (obj: { [x: string]: string }) => {
+ let hasValue = false
+ for (const value of Object.values(obj)) {
+ if (!['', undefined].includes(value)) {
+ if (hasValue) {
+ return false // 发现多个有值的属性
+ }
+ hasValue = true
+ }
+ }
+ return hasValue // 确保只有一个有值的属性
+}
+
+
+/**
+ * @name 验证对象中是否有key为空
+ * @param key
+ * @returns boolean true是空,false否
+ */
+export const formatObjHasEmptyKey = (obj: any) => {
+ for (const key in obj) {
+ if (obj[key] === '') {
+ delete obj[key]
+ }
+ }
+ return !Object.keys(obj).length || Object.keys(obj).some(key => obj[key] === '')
+}
+
+/**
+ * 验证参数中不能为空
+ */
+interface SearchParams {
+ mobile?: string;
+ idNumber?: string;
+ uuid?: string;
+ type?: number;
+}
+export const validateSearchParams = (params: SearchParams): boolean => {
+ if (!formatValueObject(params as { [x: string]: string })) {
+ ElMessage.warning('请输入一项查询项')
+ return false
+ }
+
+ if (params.mobile && !reg.isMobile(params.mobile)) {
+ ElMessage.warning('请输入正确的手机号')
+ return false
+ }
+
+ if (params.idNumber && !/^[1-9]\d{5}(18|19|20|21|22)?\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}(\d|[Xx])$/.test(params.idNumber)) {
+ ElMessage.warning('请输入正确的身份证号')
+ return false
+ }
+
+ if (params.uuid && !/^\d+$/.test(params.uuid)) {
+ ElMessage.warning('请输入正确的客户号')
+ return false
+ }
+
+ return true
+}
\ No newline at end of file
diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts
new file mode 100644
index 0000000..3a8d25b
--- /dev/null
+++ b/src/utils/crypto.ts
@@ -0,0 +1,27 @@
+import CryptoJS from 'crypto-js'
+
+const hostList = ['ltmarketui.yijiesudai.com']
+const keyVal = !hostList.includes(window.location.host) ? 'qhTkg7cRo8Q7X2Zg' : '3eIn1OL2X7H3yW1b'
+const ivVal = !hostList.includes(window.location.host) ? 'yFjzJdKLMgmJxUkX' : 'Jvb3PyimIaDRpNi8'
+
+const key = CryptoJS.enc.Utf8.parse(keyVal)
+const iv = CryptoJS.enc.Utf8.parse(ivVal)
+
+// 加密
+export const Encrypt = (pass: any) => {
+ const password = CryptoJS.enc.Utf8.parse(pass)
+ return CryptoJS.AES.encrypt(password, key, {
+ mode: CryptoJS.mode.CBC,
+ iv: iv,
+ padding: CryptoJS.pad.Pkcs7
+ }).toString()
+}
+
+// 解密
+export const Decrypt = (pass: any) => {
+ return CryptoJS.AES.decrypt(pass, key, {
+ mode: CryptoJS.mode.CBC,
+ iv: iv,
+ padding: CryptoJS.pad.Pkcs7
+ }).toString(CryptoJS.enc.Utf8)
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..5e3fe5c
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,6 @@
+import { onInit, messageBox } from './page/index'
+import atrans from './page/config'
+
+export const pageConfig = atrans
+export const handleInit = onInit
+export const handleMessageBox = messageBox
diff --git a/src/utils/page/config.ts b/src/utils/page/config.ts
new file mode 100644
index 0000000..71a59a2
--- /dev/null
+++ b/src/utils/page/config.ts
@@ -0,0 +1,122 @@
+import atrans from 'trans-config'
+import type {
+ AtransConfig,
+ SearchModuleFields,
+ TableModuleFields,
+ DialogModuleFields,
+ AtransItemRes,
+ DialogExtraFields
+} from 'trans-config'
+import * as page from '@/utils/page'
+import useStore from '@/stores'
+const { user } = useStore()
+
+export type AtransSearchRes = SearchModuleFields &
+ ExtendSearchModulesFields & {
+ [key: string]: AtransItemRes & ExtendItemConfig
+ }
+
+export type AtransTableRes = TableModuleFields &
+ ExtendTableModuleFields & {
+ [key: string]: AtransItemRes & ExtendItemConfig
+ }
+
+export type AtransDialogRes = DialogModuleFields & {
+ [key: string]: AtransItemRes & ExtendDialogExtraFields
+}
+
+export type Atrans$DialogRes = DialogExtraFields & {
+ config: DialogModuleFields & {
+ [key: string]: AtransItemRes & ExtendDialogExtraFields
+ }
+}
+
+export type AtransResult = {
+ search: AtransSearchRes
+ search1: AtransSearchRes
+ search2: AtransSearchRes
+ search3: AtransSearchRes
+ table: AtransTableRes
+ table1: AtransTableRes
+ table2: AtransTableRes
+ table3: AtransTableRes
+ dialog: AtransDialogRes
+ dialog1: AtransDialogRes
+ dialog2: AtransDialogRes
+ dialog3: AtransDialogRes
+ $dialog: Atrans$DialogRes
+ [key: string]: any
+}
+
+const atransFn = atrans.create({
+ version: 'common',
+ moduleFields: {
+ search: {
+ $search: page.search
+ },
+ table: {
+ $permissions: user.perms,
+ $onGetData: page.getTableData,
+ $stripe: true,
+ $changePage: page.onChangeCurrent,
+ $typeIndexFn: page.tableIndex
+ }
+ },
+ extraFields: {
+ dialog: {
+ $dialog: {
+ submit: page.onDialogSubmit,
+ $confirmText: '确定',
+ $confirmShow: true,
+ $cancelText: '取消',
+ $cancelShow: true,
+ $onlyRead: false
+ }
+ }
+ }
+})
+
+/**
+ * name: 自定义
+ * search/dialog/table的item配置项自定义类型
+ */
+type ExtendItemConfig = {
+ clicks?: ((val: any) => void)[]
+ shows?: ((val: any) => void)[]
+ disableds?: ((val: any) => void)[]
+ $attr?: {
+ disabled: boolean
+ placeholder: string
+ data: any[]
+ }
+}
+
+/**
+ * name: 自定义
+ * search模块配置自定义类型
+ */
+type ExtendSearchModulesFields = {}
+
+/**
+ * name: 自定义
+ * table模块配置自定义类型
+ */
+type ExtendTableModuleFields = {
+ $stripe: boolean
+}
+
+/**
+ * name: 自定义
+ * dialog模块配置自定义类型
+ */
+type ExtendDialogExtraFields = {
+ $attr: {
+ data: any[]
+ disabled: boolean
+ }
+ $on: {
+ onChange: (val: any) => void
+ }
+}
+
+export default atransFn as unknown as (config: AtransConfig) => AtransResult
diff --git a/src/utils/page/index.ts b/src/utils/page/index.ts
new file mode 100644
index 0000000..5b96db2
--- /dev/null
+++ b/src/utils/page/index.ts
@@ -0,0 +1,159 @@
+import { unref, toRefs, type ToRefs, type Ref } from 'vue'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import type { DialogExtraFields, TableModuleFields } from 'trans-config'
+import type { AtransResult } from './config'
+
+/**
+ * name search组件搜索方法
+ * @param value 筛选条件
+ * @param tableConfig table配置
+ */
+export const search = (value: object, tableConfig: TableModuleFields) => {
+ const table = unref(tableConfig)
+ table.$searchValue = value
+ table.$onGetData(table, 1)
+}
+
+/**
+ * name 表格请求接口
+ * @param tableConfig table配置
+ * @param curPage 搜索哪一页数据
+ * @returns Promise,可以.then继续执行之后的操作,如对修改表格数据
+ */
+export const getTableData = (
+ tableConfig: TableModuleFields,
+ curPage?: number,
+ searchConfig?: any
+) => {
+ const table = unref(tableConfig)
+ if (searchConfig) {
+ const search = unref(searchConfig)
+ const value = { ...(search.$default || {}), ...(search.$data || search) }
+ Object.keys(value).forEach((key) => {
+ if (value[key] === '') delete value[key]
+ if (/\w+,\w+/.test(key)) {
+ const tmpKeys = key.split(',')
+ const tmp = value[key] || []
+ value[tmpKeys[0]] = tmp[0]
+ value[tmpKeys[1]] = tmp[1]
+ delete value[key]
+ }
+ })
+ table.$searchValue = { ...(value || search) }
+ }
+ curPage && table.$pages.current && (table.$pages.current = curPage)
+
+ table.$loading = true
+ const params = { ...table.$searchValue }
+ if (table.$pages.current !== 0) {
+ Object.assign(params, {
+ currentPage: curPage || table.$pages.current,
+ pageSize: table.$pages.pageSize
+ })
+ }
+ return table.$api(params).then((res) => {
+ const result = res as {
+ data: {}[]
+ code: string
+ resultMsg: string
+ totalRows: number
+ }
+ table.$loading = false
+ table.$data = result.data
+ table.$pages.total = result.totalRows
+ }).catch(() => {
+ table.$loading = false
+ })
+}
+
+/**
+ *
+ * @param table table配置
+ * @param curPage 跳到哪一页
+ */
+export const onChangeCurrent = (table: TableModuleFields, curPage: number) => {
+ table.$onGetData(table, curPage)
+}
+
+/**
+ *
+ * @param value 对话框form表单数据
+ * @param dialog dialog配置,拿到提交表单的api接口
+ * @param table table配置,请求对话框接口后刷新表格
+ * @returns Promise,可以.then继续执行之后的操作
+ */
+export const onDialogSubmit = (
+ value: object,
+ dialog: DialogExtraFields,
+ table: TableModuleFields
+) => {
+ return dialog.api?.(value).then(() => {
+ dialog.show = false
+ table?.$onGetData(table)
+ })
+}
+
+/**
+ * name: 表格索引根据页码自增
+ * @param tableConfig table配置
+ * @param index
+ */
+export const tableIndex = (tableConfig: TableModuleFields, index: number) => {
+ const table = unref(tableConfig)
+ return table.$pages.pageSize * (table.$pages.current - 1) + index + 1
+}
+
+/**
+ * 初始化页面方法
+ * @param config 自定义配置项
+ * @param tableApi table表格请求接口
+ * @param btns 表格配置的按钮事件
+ * @returns toRefs后的配置
+ */
+export const onInit = (
+ config: Ref,
+ tableApi?: (query: any) => Promise