feat: axios
parent
d430f6d272
commit
3a39b2afb7
13
.env
13
.env
|
@ -1,8 +1,15 @@
|
|||
# 端口号
|
||||
VITE_PORT = 3100
|
||||
VITE_PORT = 80
|
||||
|
||||
# 网站标题
|
||||
VITE_GLOB_APP_TITLE = Vben Admin
|
||||
VITE_GLOB_APP_TITLE = 芋道管理系统
|
||||
|
||||
# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
|
||||
VITE_GLOB_APP_SHORT_NAME = vue_vben_admin
|
||||
VITE_GLOB_APP_SHORT_NAME = Vben
|
||||
|
||||
# 租户开关
|
||||
VITE_GLOB_APP_TENANT_ENABLE = true
|
||||
|
||||
# 验证码的开关
|
||||
VITE_GLOB_APP_CAPTCHA_ENABLE = true
|
||||
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
# 本地开发环境
|
||||
NODE_ENV=development
|
||||
|
||||
# 资源公共路径,需要以 /开头和结尾
|
||||
VITE_PUBLIC_PATH = /
|
||||
|
||||
# 本地开发代理,可以解决跨域及多地址代理
|
||||
# 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题
|
||||
# 可以有多个,注意多个不能换行,否则代理将会失效
|
||||
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
|
||||
# VITE_PROXY=[["/api","https://xingyuv.com/test"]]
|
||||
VITE_PROXY = [["/dev-api","http://localhost:48080"],["/upload","http://localhost:48080/admin-api/infra/file/upload"]]
|
||||
# VITE_PROXY=[["/api","http://vben.xingyuv.com/test"]]
|
||||
|
||||
# 是否删除Console.log
|
||||
VITE_DROP_CONSOLE = false
|
||||
|
||||
# 接口地址
|
||||
# 如果没有跨域问题,直接在这里配置即可
|
||||
VITE_GLOB_API_URL = /basic-api
|
||||
VITE_GLOB_API_URL = "http://localhost:48080/admin-api"
|
||||
|
||||
# 文件上传接口 可选
|
||||
VITE_GLOB_UPLOAD_URL = /upload
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# 本地开发环境
|
||||
NODE_ENV=development
|
||||
|
||||
# 资源公共路径,需要以 /开头和结尾
|
||||
VITE_PUBLIC_PATH = /
|
||||
|
||||
# 本地开发代理,可以解决跨域及多地址代理
|
||||
# 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题
|
||||
# 可以有多个,注意多个不能换行,否则代理将会失效
|
||||
VITE_PROXY = [["/dev-api","http://api-dashboard.yudao.iocoder.cn"],["/upload","http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload"]]
|
||||
# VITE_PROXY=[["/api","http://vben.xingyuv.com/test"]]
|
||||
|
||||
# 是否删除Console.log
|
||||
VITE_DROP_CONSOLE = false
|
||||
|
||||
# 接口地址
|
||||
# 如果没有跨域问题,直接在这里配置即可
|
||||
VITE_GLOB_API_URL = "http://localhost:48080/admin-api"
|
||||
|
||||
# 文件上传接口 可选
|
||||
VITE_GLOB_UPLOAD_URL = /upload
|
||||
|
||||
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
|
||||
VITE_GLOB_API_URL_PREFIX =
|
|
@ -13,13 +13,13 @@ VITE_BUILD_COMPRESS = 'gzip'
|
|||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
|
||||
|
||||
# 接口地址 可以由nginx做转发或者直接写实际地址
|
||||
VITE_GLOB_API_URL = /basic-api
|
||||
VITE_GLOB_API_URL = /admin-api
|
||||
|
||||
# 文件上传地址 可以由nginx做转发或者直接写实际地址
|
||||
VITE_GLOB_UPLOAD_URL = /upload
|
||||
|
||||
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
|
||||
VITE_GLOB_API_URL_PREFIX =
|
||||
VITE_GLOB_API_URL_PREFIX =
|
||||
|
||||
# 打包是否开启pwa功能
|
||||
VITE_USE_PWA = false
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
NODE_ENV=production
|
||||
|
||||
# 资源公共路径,需要以 / 开头和结尾
|
||||
VITE_PUBLIC_PATH = /
|
||||
|
||||
|
@ -14,7 +15,7 @@ VITE_BUILD_COMPRESS = 'none'
|
|||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
|
||||
|
||||
# 接口地址 可以由nginx做转发或者直接写实际地址
|
||||
VITE_GLOB_API_URL = /basic-api
|
||||
VITE_GLOB_API_URL = /admin-api
|
||||
|
||||
# 文件上传地址 可以由nginx做转发或者直接写实际地址
|
||||
VITE_GLOB_UPLOAD_URL = /upload
|
||||
|
|
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vben-admin",
|
||||
"version": "1.0.2",
|
||||
"name": "ruoyi-ui-admin-vben",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "xingyu4j",
|
||||
"email": "xingyu4j@vip.qq.com",
|
||||
|
@ -34,8 +34,6 @@
|
|||
"@ant-design/colors": "^7.0.0",
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"@iconify/iconify": "^3.1.0",
|
||||
"@logicflow/core": "^1.2.1",
|
||||
"@logicflow/extension": "^1.2.1",
|
||||
"@vue/runtime-core": "^3.2.47",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@zxcvbn-ts/core": "^2.2.1",
|
||||
|
@ -55,7 +53,6 @@
|
|||
"qrcode": "^1.5.1",
|
||||
"qs": "^6.11.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"showdown": "^2.1.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tinymce": "^5.10.7",
|
||||
"vditor": "^3.9.1",
|
||||
|
@ -82,7 +79,6 @@
|
|||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/showdown": "^2.0.0",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||
"@typescript-eslint/parser": "^5.55.0",
|
||||
|
@ -91,6 +87,7 @@
|
|||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue/compiler-sfc": "^3.2.47",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"consola": "^2.15.3",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-git": "^1.6.0",
|
||||
|
@ -106,7 +103,6 @@
|
|||
"inquirer": "^9.1.4",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^13.2.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-html": "^1.5.0",
|
||||
|
@ -122,7 +118,6 @@
|
|||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-order": "^6.0.2",
|
||||
"terser": "^5.16.6",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.2",
|
||||
"unplugin-vue-setup-extend-plus": "^0.4.9",
|
||||
"vite": "^4.2.0",
|
||||
|
|
166
pnpm-lock.yaml
166
pnpm-lock.yaml
|
@ -8,8 +8,6 @@ specifiers:
|
|||
'@iconify/iconify': ^3.1.0
|
||||
'@iconify/json': ^2.2.36
|
||||
'@kirklin/vite-plugin-vben-theme': ^0.1.1
|
||||
'@logicflow/core': ^1.2.1
|
||||
'@logicflow/extension': ^1.2.1
|
||||
'@purge-icons/generated': ^0.9.0
|
||||
'@types/codemirror': ^5.60.5
|
||||
'@types/crypto-js': ^4.1.1
|
||||
|
@ -21,7 +19,6 @@ specifiers:
|
|||
'@types/nprogress': ^0.2.0
|
||||
'@types/qrcode': ^1.5.0
|
||||
'@types/qs': ^6.9.7
|
||||
'@types/showdown': ^2.0.0
|
||||
'@types/sortablejs': ^1.15.1
|
||||
'@typescript-eslint/eslint-plugin': ^5.55.0
|
||||
'@typescript-eslint/parser': ^5.55.0
|
||||
|
@ -36,6 +33,7 @@ specifiers:
|
|||
autoprefixer: ^10.4.14
|
||||
axios: ^1.3.4
|
||||
codemirror: ^5.65.3
|
||||
consola: ^2.15.3
|
||||
conventional-changelog-cli: ^2.2.2
|
||||
cropperjs: ^1.5.13
|
||||
cross-env: ^7.0.3
|
||||
|
@ -57,7 +55,6 @@ specifiers:
|
|||
less: ^4.1.3
|
||||
lint-staged: ^13.2.0
|
||||
lodash-es: ^4.17.21
|
||||
npm-run-all: ^4.1.5
|
||||
nprogress: ^0.2.0
|
||||
path-to-regexp: ^6.2.1
|
||||
picocolors: ^1.0.0
|
||||
|
@ -73,7 +70,6 @@ specifiers:
|
|||
rimraf: ^4.4.0
|
||||
rollup: ^3.19.1
|
||||
rollup-plugin-visualizer: ^5.9.0
|
||||
showdown: ^2.1.0
|
||||
sortablejs: ^1.15.0
|
||||
stylelint: ^14.16.1
|
||||
stylelint-config-prettier: ^9.0.5
|
||||
|
@ -83,7 +79,6 @@ specifiers:
|
|||
stylelint-order: ^6.0.2
|
||||
terser: ^5.16.6
|
||||
tinymce: ^5.10.7
|
||||
ts-node: ^10.9.1
|
||||
typescript: ^5.0.2
|
||||
unplugin-vue-setup-extend-plus: ^0.4.9
|
||||
vditor: ^3.9.1
|
||||
|
@ -110,8 +105,6 @@ dependencies:
|
|||
'@ant-design/colors': 7.0.0
|
||||
'@ant-design/icons-vue': 6.1.0_vue@3.2.47
|
||||
'@iconify/iconify': 3.1.0
|
||||
'@logicflow/core': 1.2.1
|
||||
'@logicflow/extension': 1.2.1
|
||||
'@vue/runtime-core': 3.2.47
|
||||
'@vueuse/core': 9.13.0_vue@3.2.47
|
||||
'@zxcvbn-ts/core': 2.2.1
|
||||
|
@ -131,7 +124,6 @@ dependencies:
|
|||
qrcode: 1.5.1
|
||||
qs: 6.11.1
|
||||
resize-observer-polyfill: 1.5.1
|
||||
showdown: 2.1.0
|
||||
sortablejs: 1.15.0
|
||||
tinymce: 5.10.7
|
||||
vditor: 3.9.1
|
||||
|
@ -158,7 +150,6 @@ devDependencies:
|
|||
'@types/nprogress': 0.2.0
|
||||
'@types/qrcode': 1.5.0
|
||||
'@types/qs': 6.9.7
|
||||
'@types/showdown': 2.0.0
|
||||
'@types/sortablejs': 1.15.1
|
||||
'@typescript-eslint/eslint-plugin': 5.55.0_qsnvknysi52qtaxqdyqyohkcku
|
||||
'@typescript-eslint/parser': 5.55.0_j4766f7ecgqbon3u7zlxn5zszu
|
||||
|
@ -167,6 +158,7 @@ devDependencies:
|
|||
'@vitejs/plugin-vue-jsx': 3.0.1_vite@4.2.0+vue@3.2.47
|
||||
'@vue/compiler-sfc': 3.2.47
|
||||
autoprefixer: 10.4.14_postcss@8.4.21
|
||||
consola: 2.15.3
|
||||
conventional-changelog-cli: 2.2.2
|
||||
cross-env: 7.0.3
|
||||
cz-git: 1.6.0
|
||||
|
@ -182,7 +174,6 @@ devDependencies:
|
|||
inquirer: 9.1.4
|
||||
less: 4.1.3
|
||||
lint-staged: 13.2.0
|
||||
npm-run-all: 4.1.5
|
||||
picocolors: 1.0.0
|
||||
postcss: 8.4.21
|
||||
postcss-html: 1.5.0
|
||||
|
@ -198,7 +189,6 @@ devDependencies:
|
|||
stylelint-config-standard: 29.0.0_stylelint@14.16.1
|
||||
stylelint-order: 6.0.3_stylelint@14.16.1
|
||||
terser: 5.16.6
|
||||
ts-node: 10.9.1_sxidjv3cojnrggmso45tj7hldi
|
||||
typescript: 5.0.2
|
||||
unplugin-vue-setup-extend-plus: 0.4.9
|
||||
vite: 4.2.0_kfn5zdpk76mco3hnivyqwkouli
|
||||
|
@ -2062,23 +2052,6 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@logicflow/core/1.2.1:
|
||||
resolution: {integrity: sha512-m6/K7I85jFfCWcrNEQQpchomUWM4ZUjcOhHjsVhejnfpoEx7jke3tSJlh41LyT9hIUEIy7nxnphp5L4YB+InCA==}
|
||||
dependencies:
|
||||
'@types/mousetrap': 1.6.11
|
||||
mousetrap: 1.6.5
|
||||
preact: 10.12.1
|
||||
dev: false
|
||||
|
||||
/@logicflow/extension/1.2.1:
|
||||
resolution: {integrity: sha512-C006Y73sqe0x5ArWzTWc2YglhDNs+Ck/eXJnnaZ89uYqyVDdLgr/6RBjL8JAV41Lg7EQMB1yqmfUW53tCPTRtw==}
|
||||
dependencies:
|
||||
'@logicflow/core': 1.2.1
|
||||
ids: 1.0.0
|
||||
lodash-es: 4.17.21
|
||||
preact: 10.12.1
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -2423,10 +2396,6 @@ packages:
|
|||
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
|
||||
dev: true
|
||||
|
||||
/@types/mousetrap/1.6.11:
|
||||
resolution: {integrity: sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ==}
|
||||
dev: false
|
||||
|
||||
/@types/node/10.17.60:
|
||||
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
|
||||
dev: true
|
||||
|
@ -2467,10 +2436,6 @@ packages:
|
|||
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
|
||||
dev: true
|
||||
|
||||
/@types/showdown/2.0.0:
|
||||
resolution: {integrity: sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA==}
|
||||
dev: true
|
||||
|
||||
/@types/sortablejs/1.15.1:
|
||||
resolution: {integrity: sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ==}
|
||||
dev: true
|
||||
|
@ -3609,11 +3574,6 @@ packages:
|
|||
engines: {node: '>= 12'}
|
||||
dev: true
|
||||
|
||||
/commander/9.5.0:
|
||||
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
|
||||
engines: {node: ^12.20.0 || >=14}
|
||||
dev: false
|
||||
|
||||
/common-tags/1.8.2:
|
||||
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
@ -3929,17 +3889,6 @@ packages:
|
|||
- encoding
|
||||
dev: true
|
||||
|
||||
/cross-spawn/6.0.5:
|
||||
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
|
||||
engines: {node: '>=4.8'}
|
||||
dependencies:
|
||||
nice-try: 1.0.5
|
||||
path-key: 2.0.1
|
||||
semver: 5.7.1
|
||||
shebang-command: 1.2.0
|
||||
which: 1.3.1
|
||||
dev: true
|
||||
|
||||
/cross-spawn/7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -5561,10 +5510,6 @@ packages:
|
|||
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
|
||||
dev: true
|
||||
|
||||
/ids/1.0.0:
|
||||
resolution: {integrity: sha512-Zvtq1xUto4LttpstyOlFum8lKx+i1OmRfg+6A9drFS9iSZsDPMHG4Sof/qwNR4kCU7jBeWFPrY2ocHxiz7cCRw==}
|
||||
dev: false
|
||||
|
||||
/ieee754/1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: true
|
||||
|
@ -6424,11 +6369,6 @@ packages:
|
|||
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
||||
dev: true
|
||||
|
||||
/memorystream/0.3.1:
|
||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dev: true
|
||||
|
||||
/meow/8.1.2:
|
||||
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -6600,10 +6540,6 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/mousetrap/1.6.5:
|
||||
resolution: {integrity: sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==}
|
||||
dev: false
|
||||
|
||||
/ms/2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
dev: true
|
||||
|
@ -6679,10 +6615,6 @@ packages:
|
|||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
dev: true
|
||||
|
||||
/nice-try/1.0.5:
|
||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||
dev: true
|
||||
|
||||
/no-case/3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
dependencies:
|
||||
|
@ -6754,22 +6686,6 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/npm-run-all/4.1.5:
|
||||
resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
|
||||
engines: {node: '>= 4'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
chalk: 2.4.2
|
||||
cross-spawn: 6.0.5
|
||||
memorystream: 0.3.1
|
||||
minimatch: 3.1.2
|
||||
pidtree: 0.3.1
|
||||
read-pkg: 3.0.0
|
||||
shell-quote: 1.8.0
|
||||
string.prototype.padend: 3.1.4
|
||||
dev: true
|
||||
|
||||
/npm-run-path/4.0.1:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -7027,11 +6943,6 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/path-key/2.0.1:
|
||||
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/path-key/3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -7086,12 +6997,6 @@ packages:
|
|||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/pidtree/0.3.1:
|
||||
resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/pidtree/0.6.0:
|
||||
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
@ -7259,10 +7164,6 @@ packages:
|
|||
posthtml-render: 1.4.0
|
||||
dev: true
|
||||
|
||||
/preact/10.12.1:
|
||||
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
|
||||
dev: false
|
||||
|
||||
/prelude-ls/1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
@ -7766,13 +7667,6 @@ packages:
|
|||
resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
|
||||
dev: false
|
||||
|
||||
/shebang-command/1.2.0:
|
||||
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
shebang-regex: 1.0.0
|
||||
dev: true
|
||||
|
||||
/shebang-command/2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -7780,27 +7674,11 @@ packages:
|
|||
shebang-regex: 3.0.0
|
||||
dev: true
|
||||
|
||||
/shebang-regex/1.0.0:
|
||||
resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/shebang-regex/3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/shell-quote/1.8.0:
|
||||
resolution: {integrity: sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==}
|
||||
dev: true
|
||||
|
||||
/showdown/2.1.0:
|
||||
resolution: {integrity: sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
commander: 9.5.0
|
||||
dev: false
|
||||
|
||||
/side-channel/1.0.4:
|
||||
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
|
||||
dependencies:
|
||||
|
@ -8039,15 +7917,6 @@ packages:
|
|||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/string.prototype.padend/3.1.4:
|
||||
resolution: {integrity: sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.2.0
|
||||
es-abstract: 1.21.1
|
||||
dev: true
|
||||
|
||||
/string.prototype.trimend/1.0.6:
|
||||
resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==}
|
||||
dependencies:
|
||||
|
@ -8503,37 +8372,6 @@ packages:
|
|||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_sxidjv3cojnrggmso45tj7hldi:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.9
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.3
|
||||
'@types/node': 18.15.3
|
||||
acorn: 8.8.2
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.0.2
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/tslib/1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
import { withInstall } from '@/utils'
|
||||
import flowChart from './src/FlowChart.vue'
|
||||
|
||||
export const FlowChart = withInstall(flowChart)
|
|
@ -1,142 +0,0 @@
|
|||
<template>
|
||||
<div class="h-full" :class="prefixCls">
|
||||
<FlowChartToolbar :prefixCls="prefixCls" v-if="toolbar" @view-data="handlePreview" />
|
||||
<div ref="lfElRef" class="h-full"></div>
|
||||
<BasicModal @register="register" title="流程数据" width="50%">
|
||||
<JsonPreview :data="graphData" />
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="FlowChart">
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref, unref, watch } from 'vue'
|
||||
import type { Definition } from '@logicflow/core'
|
||||
import LogicFlow from '@logicflow/core'
|
||||
import FlowChartToolbar from './FlowChartToolbar.vue'
|
||||
import { BpmnElement, DndPanel, Menu, SelectionSelect, Snapshot } from '@logicflow/extension'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { createFlowChartContext } from './useFlowContext'
|
||||
import { toLogicFlowData } from './adpterForTurbo'
|
||||
import { BasicModal, useModal } from '@/components/Modal'
|
||||
import { JsonPreview } from '@/components/CodeEditor'
|
||||
import { configDefaultDndPanel } from './config'
|
||||
import '@logicflow/core/dist/style/index.css'
|
||||
import '@logicflow/extension/lib/style/index.css'
|
||||
|
||||
const props = defineProps({
|
||||
flowOptions: {
|
||||
type: Object as PropType<Definition>,
|
||||
default: () => ({})
|
||||
},
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
toolbar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
patternItems: {
|
||||
type: Array
|
||||
}
|
||||
})
|
||||
const lfElRef = ref(null)
|
||||
const graphData = ref({})
|
||||
|
||||
const lfInstance = ref(null) as Ref<LogicFlow | null>
|
||||
|
||||
const { prefixCls } = useDesign('flow-chart')
|
||||
const appStore = useAppStore()
|
||||
const [register, { openModal }] = useModal()
|
||||
createFlowChartContext({
|
||||
logicFlow: lfInstance as unknown as LogicFlow
|
||||
})
|
||||
|
||||
const getFlowOptions = computed(() => {
|
||||
const { flowOptions } = props
|
||||
|
||||
const defaultOptions: Partial<Definition> = {
|
||||
grid: true,
|
||||
background: {
|
||||
color: appStore.getDarkMode === 'light' ? '#f7f9ff' : '#151515'
|
||||
},
|
||||
keyboard: {
|
||||
enabled: true
|
||||
},
|
||||
...flowOptions
|
||||
}
|
||||
return defaultOptions as Definition
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
onRender()
|
||||
}
|
||||
)
|
||||
|
||||
// TODO
|
||||
// watch(
|
||||
// () => appStore.getDarkMode,
|
||||
// () => {
|
||||
// init();
|
||||
// }
|
||||
// );
|
||||
|
||||
watch(
|
||||
() => unref(getFlowOptions),
|
||||
(options) => {
|
||||
unref(lfInstance)?.updateEditConfig(options)
|
||||
}
|
||||
)
|
||||
|
||||
// init logicFlow
|
||||
async function init() {
|
||||
await nextTick()
|
||||
|
||||
const lfEl = unref(lfElRef)
|
||||
if (!lfEl) {
|
||||
return
|
||||
}
|
||||
LogicFlow.use(DndPanel)
|
||||
|
||||
// Canvas configuration
|
||||
LogicFlow.use(Snapshot)
|
||||
// Use the bpmn plug-in to introduce bpmn elements, which can be used after conversion in turbo
|
||||
LogicFlow.use(BpmnElement)
|
||||
// Start the right-click menu
|
||||
LogicFlow.use(Menu)
|
||||
LogicFlow.use(SelectionSelect)
|
||||
|
||||
lfInstance.value = new LogicFlow({
|
||||
...unref(getFlowOptions),
|
||||
container: lfEl
|
||||
})
|
||||
const lf = unref(lfInstance)!
|
||||
lf?.setDefaultEdgeType('line')
|
||||
await onRender()
|
||||
lf?.setPatternItems(props.patternItems || configDefaultDndPanel(lf))
|
||||
}
|
||||
|
||||
async function onRender() {
|
||||
await nextTick()
|
||||
const lf = unref(lfInstance)
|
||||
if (!lf) {
|
||||
return
|
||||
}
|
||||
const lFData = toLogicFlowData(props.data)
|
||||
lf.render(lFData)
|
||||
}
|
||||
|
||||
function handlePreview() {
|
||||
const lf = unref(lfInstance)
|
||||
if (!lf) {
|
||||
return
|
||||
}
|
||||
graphData.value = unref(lf).getGraphData()
|
||||
openModal()
|
||||
}
|
||||
|
||||
onMounted(init)
|
||||
</script>
|
|
@ -1,153 +0,0 @@
|
|||
<template>
|
||||
<div :class="`${prefixCls}-toolbar`" class="flex items-center px-2 py-1">
|
||||
<template v-for="item in toolbarItemList" :key="item.type">
|
||||
<Tooltip placement="bottom" v-bind="item.disabled ? { visible: false } : {}">
|
||||
<template #title>{{ item.tooltip }}</template>
|
||||
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)">
|
||||
<Icon :icon="item.icon" :class="item.disabled ? 'cursor-not-allowed disabeld' : 'cursor-pointer'" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Divider v-if="item.separate" type="vertical" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="FlowChartToolbar">
|
||||
import type { ToolbarConfig } from './types'
|
||||
|
||||
import { nextTick, onUnmounted, ref, unref, watchEffect } from 'vue'
|
||||
import { Divider, Tooltip } from 'ant-design-vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
import { useFlowChartContext } from './useFlowContext'
|
||||
import { ToolbarTypeEnum } from './enum'
|
||||
|
||||
defineProps({
|
||||
prefixCls: String
|
||||
})
|
||||
const emit = defineEmits(['view-data'])
|
||||
const toolbarItemList = ref<ToolbarConfig[]>([
|
||||
{
|
||||
type: ToolbarTypeEnum.ZOOM_IN,
|
||||
icon: 'codicon:zoom-out',
|
||||
tooltip: '缩小'
|
||||
},
|
||||
{
|
||||
type: ToolbarTypeEnum.ZOOM_OUT,
|
||||
icon: 'codicon:zoom-in',
|
||||
tooltip: '放大'
|
||||
},
|
||||
{
|
||||
type: ToolbarTypeEnum.RESET_ZOOM,
|
||||
icon: 'codicon:screen-normal',
|
||||
tooltip: '重置比例'
|
||||
},
|
||||
{ separate: true },
|
||||
{
|
||||
type: ToolbarTypeEnum.UNDO,
|
||||
icon: 'ion:arrow-undo-outline',
|
||||
tooltip: '后退',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
type: ToolbarTypeEnum.REDO,
|
||||
icon: 'ion:arrow-redo-outline',
|
||||
tooltip: '前进',
|
||||
disabled: true
|
||||
},
|
||||
{ separate: true },
|
||||
{
|
||||
type: ToolbarTypeEnum.SNAPSHOT,
|
||||
icon: 'ion:download-outline',
|
||||
tooltip: '下载'
|
||||
},
|
||||
{
|
||||
type: ToolbarTypeEnum.VIEW_DATA,
|
||||
icon: 'carbon:document-view',
|
||||
tooltip: '查看数据'
|
||||
}
|
||||
])
|
||||
|
||||
const { logicFlow } = useFlowChartContext()
|
||||
|
||||
function onHistoryChange({ data: { undoAble, redoAble } }) {
|
||||
const itemsList = unref(toolbarItemList)
|
||||
const undoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.UNDO)
|
||||
const redoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.REDO)
|
||||
if (undoIndex !== -1) {
|
||||
unref(toolbarItemList)[undoIndex].disabled = !undoAble
|
||||
}
|
||||
if (redoIndex !== -1) {
|
||||
unref(toolbarItemList)[redoIndex].disabled = !redoAble
|
||||
}
|
||||
}
|
||||
|
||||
const onControl = (item) => {
|
||||
const lf = unref(logicFlow)
|
||||
if (!lf) {
|
||||
return
|
||||
}
|
||||
switch (item.type) {
|
||||
case ToolbarTypeEnum.ZOOM_IN:
|
||||
lf.zoom()
|
||||
break
|
||||
case ToolbarTypeEnum.ZOOM_OUT:
|
||||
lf.zoom(true)
|
||||
break
|
||||
case ToolbarTypeEnum.RESET_ZOOM:
|
||||
lf.resetZoom()
|
||||
break
|
||||
case ToolbarTypeEnum.UNDO:
|
||||
lf.undo()
|
||||
break
|
||||
case ToolbarTypeEnum.REDO:
|
||||
lf.redo()
|
||||
break
|
||||
case ToolbarTypeEnum.SNAPSHOT:
|
||||
lf.getSnapshot()
|
||||
break
|
||||
case ToolbarTypeEnum.VIEW_DATA:
|
||||
emit('view-data')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(async () => {
|
||||
if (unref(logicFlow)) {
|
||||
await nextTick()
|
||||
unref(logicFlow)?.on('history:change', onHistoryChange)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unref(logicFlow)?.off('history:change', onHistoryChange)
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-flow-chart-toolbar';
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.lf-dnd {
|
||||
background: #080808;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
height: 36px;
|
||||
background-color: @app-content-background;
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
|
||||
.disabeld {
|
||||
color: @disabled-color;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
margin-right: 10px;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,75 +0,0 @@
|
|||
const TurboType = {
|
||||
SEQUENCE_FLOW: 1,
|
||||
START_EVENT: 2,
|
||||
END_EVENT: 3,
|
||||
USER_TASK: 4,
|
||||
SERVICE_TASK: 5,
|
||||
EXCLUSIVE_GATEWAY: 6
|
||||
}
|
||||
|
||||
function convertFlowElementToEdge(element) {
|
||||
const { incoming, outgoing, properties, key } = element
|
||||
const { text, startPoint, endPoint, pointsList, logicFlowType } = properties
|
||||
const edge = {
|
||||
id: key,
|
||||
type: logicFlowType,
|
||||
sourceNodeId: incoming[0],
|
||||
targetNodeId: outgoing[0],
|
||||
text,
|
||||
startPoint,
|
||||
endPoint,
|
||||
pointsList,
|
||||
properties: {}
|
||||
}
|
||||
const excludeProperties = ['startPoint', 'endPoint', 'pointsList', 'text', 'logicFlowType']
|
||||
Object.keys(element.properties).forEach((property) => {
|
||||
if (excludeProperties.indexOf(property) === -1) {
|
||||
edge.properties[property] = element.properties[property]
|
||||
}
|
||||
})
|
||||
return edge
|
||||
}
|
||||
|
||||
function convertFlowElementToNode(element) {
|
||||
const { properties, key } = element
|
||||
const { x, y, text, logicFlowType } = properties
|
||||
const node = {
|
||||
id: key,
|
||||
type: logicFlowType,
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
properties: {}
|
||||
}
|
||||
const excludeProperties = ['x', 'y', 'text', 'logicFlowType']
|
||||
Object.keys(element.properties).forEach((property) => {
|
||||
if (excludeProperties.indexOf(property) === -1) {
|
||||
node.properties[property] = element.properties[property]
|
||||
}
|
||||
})
|
||||
return node
|
||||
}
|
||||
|
||||
export function toLogicFlowData(data) {
|
||||
const lfData: {
|
||||
// TODO type
|
||||
nodes: any[]
|
||||
edges: any[]
|
||||
} = {
|
||||
nodes: [],
|
||||
edges: []
|
||||
}
|
||||
const list = data.flowElementList
|
||||
list &&
|
||||
list.length > 0 &&
|
||||
list.forEach((element) => {
|
||||
if (element.type === TurboType.SEQUENCE_FLOW) {
|
||||
const edge = convertFlowElementToEdge(element)
|
||||
lfData.edges.push(edge)
|
||||
} else {
|
||||
const node = convertFlowElementToNode(element)
|
||||
lfData.nodes.push(node)
|
||||
}
|
||||
})
|
||||
return lfData
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
export const nodeList = [
|
||||
{
|
||||
text: '开始',
|
||||
type: 'start',
|
||||
class: 'node-start'
|
||||
},
|
||||
{
|
||||
text: '矩形',
|
||||
type: 'rect',
|
||||
class: 'node-rect'
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
text: '用户',
|
||||
class: 'node-user'
|
||||
},
|
||||
{
|
||||
type: 'push',
|
||||
text: '推送',
|
||||
class: 'node-push'
|
||||
},
|
||||
{
|
||||
type: 'download',
|
||||
text: '位置',
|
||||
class: 'node-download'
|
||||
},
|
||||
{
|
||||
type: 'end',
|
||||
text: '结束',
|
||||
class: 'node-end'
|
||||
}
|
||||
]
|
||||
|
||||
export const BpmnNode = [
|
||||
{
|
||||
type: 'bpmn:startEvent',
|
||||
text: '开始',
|
||||
class: 'bpmn-start'
|
||||
},
|
||||
{
|
||||
type: 'bpmn:endEvent',
|
||||
text: '结束',
|
||||
class: 'bpmn-end'
|
||||
},
|
||||
{
|
||||
type: 'bpmn:exclusiveGateway',
|
||||
text: '网关',
|
||||
class: 'bpmn-exclusiveGateway'
|
||||
},
|
||||
{
|
||||
type: 'bpmn:userTask',
|
||||
text: '用户',
|
||||
class: 'bpmn-user'
|
||||
}
|
||||
]
|
||||
|
||||
export function configDefaultDndPanel(lf) {
|
||||
return [
|
||||
{
|
||||
text: '选区',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP++2026ETdpv10iy7WFbqFyyW6GBywLCv5gI+Dw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3+QJ+AKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAW+Gu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLda+DUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxm+v7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII=',
|
||||
callback: () => {
|
||||
lf.updateEditConfig({
|
||||
stopMoveGraph: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'circle',
|
||||
text: '开始',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg=='
|
||||
},
|
||||
{
|
||||
type: 'rect',
|
||||
text: '用户任务',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
|
||||
cls: 'important-node'
|
||||
},
|
||||
{
|
||||
type: 'rect',
|
||||
text: '系统任务',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
|
||||
cls: 'import_icon'
|
||||
},
|
||||
{
|
||||
type: 'diamond',
|
||||
text: '条件判断',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunu+L7JvmUTU3AgmTfvffPNN++9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8l+FsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuO+Ni6bBxVUCvS5d9aa04+so4pHW5jLTywuXAL7jJ+D06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9Tk+RiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/+QtlOUYJj9HG5pVFEU7zqIYDVsw2s+AJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsY+wMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCC+EsFdS8DCyuCn2munFpL7ctHKp+Xc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJN+MMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII='
|
||||
},
|
||||
{
|
||||
type: 'circle',
|
||||
text: '结束',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC'
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
export enum ToolbarTypeEnum {
|
||||
ZOOM_IN = 'zoomIn',
|
||||
ZOOM_OUT = 'zoomOut',
|
||||
RESET_ZOOM = 'resetZoom',
|
||||
|
||||
UNDO = 'undo',
|
||||
REDO = 'redo',
|
||||
|
||||
SNAPSHOT = 'snapshot',
|
||||
VIEW_DATA = 'viewData'
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { NodeConfig } from '@logicflow/core'
|
||||
import { ToolbarTypeEnum } from './enum'
|
||||
|
||||
export interface NodeItem extends NodeConfig {
|
||||
icon: string
|
||||
}
|
||||
|
||||
export interface ToolbarConfig {
|
||||
type?: string | ToolbarTypeEnum
|
||||
tooltip?: string | boolean
|
||||
icon?: string
|
||||
disabled?: boolean
|
||||
separate?: boolean
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import type LogicFlow from '@logicflow/core'
|
||||
|
||||
import { provide, inject } from 'vue'
|
||||
|
||||
const key = Symbol('flow-chart')
|
||||
|
||||
type Instance = {
|
||||
logicFlow: LogicFlow
|
||||
}
|
||||
|
||||
export function createFlowChartContext(instance: Instance) {
|
||||
provide(key, instance)
|
||||
}
|
||||
|
||||
export function useFlowChartContext(): Instance {
|
||||
return inject(key) as Instance
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import { withInstall } from '@/utils/index'
|
||||
import verify from './src/Verify.vue'
|
||||
|
||||
export const Verify = withInstall(verify)
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,250 @@
|
|||
<template>
|
||||
<div style="position: relative">
|
||||
<div class="verify-img-out">
|
||||
<div
|
||||
class="verify-img-panel"
|
||||
:style="{
|
||||
width: setSize.imgWidth,
|
||||
height: setSize.imgHeight,
|
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
|
||||
'margin-bottom': vSpace + 'px'
|
||||
}"
|
||||
>
|
||||
<div class="verify-refresh" style="z-index: 3" @click="refresh" v-show="showRefresh">
|
||||
<i class="iconfont icon-refresh"></i>
|
||||
</div>
|
||||
<img
|
||||
:src="'data:image/png;base64,' + pointBackImgBase"
|
||||
ref="canvas"
|
||||
alt=""
|
||||
style="width: 100%; height: 100%; display: block"
|
||||
@click="bindingClick ? canvasClick($event) : undefined"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-for="(tempPoint, index) in tempPoints"
|
||||
:key="index"
|
||||
class="point-area"
|
||||
:style="{
|
||||
'background-color': '#1abd6c',
|
||||
color: '#fff',
|
||||
'z-index': 9999,
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
'text-align': 'center',
|
||||
'line-height': '20px',
|
||||
'border-radius': '50%',
|
||||
position: 'absolute',
|
||||
top: parseInt(tempPoint.y - 10) + 'px',
|
||||
left: parseInt(tempPoint.x - 10) + 'px'
|
||||
}"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 'height': this.barSize.height, -->
|
||||
<div
|
||||
class="verify-bar-area"
|
||||
:style="{
|
||||
width: setSize.imgWidth,
|
||||
color: barAreaColor,
|
||||
'border-color': barAreaBorderColor,
|
||||
'line-height': barSize.height
|
||||
}"
|
||||
>
|
||||
<span class="verify-msg">{{ text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel" setup>
|
||||
/**
|
||||
* VerifyPoints
|
||||
* @description 点选
|
||||
* */
|
||||
import { resetSize } from './../utils/util'
|
||||
import { aesEncrypt } from './../utils/ase'
|
||||
import { getCaptcha, checkCaptcha } from '@/api/base/login'
|
||||
import { onMounted, reactive, ref, nextTick, toRefs, getCurrentInstance } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const props = defineProps({
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
//间隔
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
let secretKey = ref(''), //后端返回的ase加密秘钥
|
||||
checkNum = ref(3), //默认需要点击的字数
|
||||
fontPos = reactive([]), //选中的坐标信息
|
||||
checkPosArr = reactive([]), //用户点击的坐标
|
||||
num = ref(1), //点击的记数
|
||||
pointBackImgBase = ref(''), //后端获取到的背景图片
|
||||
poinTextList = reactive([]), //后端返回的点击字体顺序
|
||||
backToken = ref(''), //后端返回的token值
|
||||
setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
}),
|
||||
tempPoints = reactive([]),
|
||||
text = ref(''),
|
||||
barAreaColor = ref(undefined),
|
||||
barAreaBorderColor = ref(undefined),
|
||||
showRefresh = ref(true),
|
||||
bindingClick = ref(true)
|
||||
|
||||
const init = () => {
|
||||
//加载页面
|
||||
fontPos.splice(0, fontPos.length)
|
||||
checkPosArr.splice(0, checkPosArr.length)
|
||||
num.value = 1
|
||||
getPictrue()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
const canvas = ref(null)
|
||||
const canvasClick = (e) => {
|
||||
checkPosArr.push(getMousePos(canvas, e))
|
||||
if (num.value == checkNum.value) {
|
||||
num.value = createPoint(getMousePos(canvas, e))
|
||||
//按比例转换坐标值
|
||||
let arr = pointTransfrom(checkPosArr, setSize)
|
||||
checkPosArr.length = 0
|
||||
checkPosArr.push(...arr)
|
||||
//等创建坐标执行完
|
||||
setTimeout(() => {
|
||||
// var flag = this.comparePos(this.fontPos, this.checkPosArr);
|
||||
//发送后端请求
|
||||
var captchaVerification = secretKey.value
|
||||
? aesEncrypt(backToken.value + '---' + JSON.stringify(checkPosArr), secretKey.value)
|
||||
: backToken.value + '---' + JSON.stringify(checkPosArr)
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value ? aesEncrypt(JSON.stringify(checkPosArr), secretKey.value) : JSON.stringify(checkPosArr),
|
||||
token: backToken.value
|
||||
}
|
||||
checkCaptcha(data).then((response) => {
|
||||
const res = response.data
|
||||
if (res.repCode == '0000') {
|
||||
barAreaColor.value = '#4cae4c'
|
||||
barAreaBorderColor.value = '#5cb85c'
|
||||
text.value = t('component.captcha.success')
|
||||
bindingClick.value = false
|
||||
if (mode.value == 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
} else {
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
barAreaColor.value = '#d9534f'
|
||||
barAreaBorderColor.value = '#d9534f'
|
||||
text.value = t('component.captcha.fail')
|
||||
setTimeout(() => {
|
||||
refresh()
|
||||
}, 700)
|
||||
}
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
if (num.value < checkNum.value) {
|
||||
num.value = createPoint(getMousePos(canvas, e))
|
||||
}
|
||||
}
|
||||
//获取坐标
|
||||
const getMousePos = function (obj, e) {
|
||||
var x = e.offsetX
|
||||
var y = e.offsetY
|
||||
return { x, y }
|
||||
}
|
||||
//创建坐标点
|
||||
const createPoint = function (pos) {
|
||||
tempPoints.push(Object.assign({}, pos))
|
||||
return num.value + 1
|
||||
}
|
||||
const refresh = async function () {
|
||||
tempPoints.splice(0, tempPoints.length)
|
||||
barAreaColor.value = '#000'
|
||||
barAreaBorderColor.value = '#ddd'
|
||||
bindingClick.value = true
|
||||
fontPos.splice(0, fontPos.length)
|
||||
checkPosArr.splice(0, checkPosArr.length)
|
||||
num.value = 1
|
||||
await getPictrue()
|
||||
showRefresh.value = true
|
||||
}
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
const getPictrue = async () => {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
const res = await getCaptcha(data)
|
||||
if (res.data.repCode == '0000') {
|
||||
pointBackImgBase.value = res.data.repData.originalImageBase64
|
||||
backToken.value = res.data.repData.token
|
||||
secretKey.value = res.data.repData.secretKey
|
||||
poinTextList.value = res.data.repData.wordList
|
||||
text.value = t('component.captcha.point') + '【' + poinTextList.value.join(',') + '】'
|
||||
} else {
|
||||
text.value = res.data.repMsg
|
||||
}
|
||||
}
|
||||
//坐标转换函数
|
||||
const pointTransfrom = function (pointArr, imgSize) {
|
||||
var newPointArr = pointArr.map((p) => {
|
||||
let x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))
|
||||
let y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))
|
||||
return { x, y }
|
||||
})
|
||||
return newPointArr
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,357 @@
|
|||
<template>
|
||||
<div style="position: relative">
|
||||
<div v-if="type === '2'" class="verify-img-out" :style="{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }">
|
||||
<div class="verify-img-panel" :style="{ width: setSize.imgWidth, height: setSize.imgHeight }">
|
||||
<img :src="'data:image/png;base64,' + backImgBase" alt="" style="width: 100%; height: 100%; display: block" />
|
||||
<div class="verify-refresh" @click="refresh" v-show="showRefresh">
|
||||
<i class="iconfont icon-refresh"></i>
|
||||
</div>
|
||||
<transition name="tips">
|
||||
<span class="verify-tips" v-if="tipWords" :class="passFlag ? 'suc-bg' : 'err-bg'">
|
||||
{{ tipWords }}
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 公共部分 -->
|
||||
<div class="verify-bar-area" :style="{ width: setSize.imgWidth, height: barSize.height, 'line-height': barSize.height }">
|
||||
<span class="verify-msg" v-text="text"></span>
|
||||
<div
|
||||
class="verify-left-bar"
|
||||
:style="{
|
||||
width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
|
||||
height: barSize.height,
|
||||
'border-color': leftBarBorderColor,
|
||||
transaction: transitionWidth
|
||||
}"
|
||||
>
|
||||
<span class="verify-msg" v-text="finishText"></span>
|
||||
<div
|
||||
class="verify-move-block"
|
||||
@touchstart="start"
|
||||
@mousedown="start"
|
||||
:style="{
|
||||
width: barSize.height,
|
||||
height: barSize.height,
|
||||
'background-color': moveBlockBackgroundColor,
|
||||
left: moveBlockLeft,
|
||||
transition: transitionLeft
|
||||
}"
|
||||
>
|
||||
<i :class="['verify-icon iconfont', iconClass]" :style="{ color: iconColor }"></i>
|
||||
<div
|
||||
v-if="type === '2'"
|
||||
class="verify-sub-block"
|
||||
:style="{
|
||||
width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',
|
||||
height: setSize.imgHeight,
|
||||
top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
|
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight
|
||||
}"
|
||||
>
|
||||
<img :src="blockBackImgBase" alt="" style="width: 100%; height: 100%; display: block; -webkit-user-drag: none" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel" setup>
|
||||
/**
|
||||
* VerifySlide
|
||||
* @description 滑块
|
||||
* */
|
||||
import { aesEncrypt } from './../utils/ase'
|
||||
import { resetSize } from './../utils/util'
|
||||
import { getCaptcha, checkCaptcha } from '@/api/base/login'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { computed, onMounted, reactive, ref, watch, nextTick, toRefs, getCurrentInstance } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
explain: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
blockSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '30px'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType, type, blockSize, explain } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
const secretKey = ref('') //后端返回的ase加密秘钥
|
||||
const passFlag = ref('') //是否通过的标识
|
||||
const backImgBase = ref('') //验证码背景图片
|
||||
const blockBackImgBase = ref('') //验证滑块的背景图片
|
||||
const backToken = ref('') //后端返回的唯一token值
|
||||
const startMoveTime = ref('') //移动开始的时间
|
||||
const endMovetime = ref('') //移动结束的时间
|
||||
const tipWords = ref('')
|
||||
const text = ref('')
|
||||
const finishText = ref('')
|
||||
const setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
})
|
||||
const moveBlockLeft = ref(undefined)
|
||||
const leftBarWidth = ref(undefined)
|
||||
// 移动中样式
|
||||
const moveBlockBackgroundColor = ref(undefined)
|
||||
const leftBarBorderColor = ref('#ddd')
|
||||
const iconColor = ref(undefined)
|
||||
const iconClass = ref('icon-right')
|
||||
const status = ref(false) //鼠标状态
|
||||
const isEnd = ref(false) //是够验证完成
|
||||
const showRefresh = ref(true)
|
||||
const transitionLeft = ref('')
|
||||
const transitionWidth = ref('')
|
||||
const startLeft = ref(0)
|
||||
|
||||
const barArea = computed(() => {
|
||||
return proxy.$el.querySelector('.verify-bar-area')
|
||||
})
|
||||
const init = () => {
|
||||
if (explain.value === '') {
|
||||
text.value = t('component.captcha.slide')
|
||||
} else {
|
||||
text.value = explain.value
|
||||
}
|
||||
getPictrue()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
|
||||
window.removeEventListener('touchmove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
window.removeEventListener('mousemove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
|
||||
//鼠标松开
|
||||
window.removeEventListener('touchend', function () {
|
||||
end()
|
||||
})
|
||||
window.removeEventListener('mouseup', function () {
|
||||
end()
|
||||
})
|
||||
|
||||
window.addEventListener('touchmove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
|
||||
//鼠标松开
|
||||
window.addEventListener('touchend', function () {
|
||||
end()
|
||||
})
|
||||
window.addEventListener('mouseup', function () {
|
||||
end()
|
||||
})
|
||||
}
|
||||
watch(type, () => {
|
||||
init()
|
||||
})
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
//鼠标按下
|
||||
const start = (e) => {
|
||||
e = e || window.event
|
||||
if (!e.touches) {
|
||||
//兼容PC端
|
||||
var x = e.clientX
|
||||
} else {
|
||||
//兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)
|
||||
startMoveTime.value = +new Date() //开始滑动的时间
|
||||
if (isEnd.value == false) {
|
||||
text.value = ''
|
||||
moveBlockBackgroundColor.value = '#337ab7'
|
||||
leftBarBorderColor.value = '#337AB7'
|
||||
iconColor.value = '#fff'
|
||||
e.stopPropagation()
|
||||
status.value = true
|
||||
}
|
||||
}
|
||||
//鼠标移动
|
||||
const move = (e) => {
|
||||
e = e || window.event
|
||||
if (status.value && isEnd.value == false) {
|
||||
if (!e.touches) {
|
||||
//兼容PC端
|
||||
var x = e.clientX
|
||||
} else {
|
||||
//兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
var bar_area_left = barArea.value.getBoundingClientRect().left
|
||||
var move_block_left = x - bar_area_left //小方块相对于父元素的left值
|
||||
if (move_block_left >= barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2) {
|
||||
move_block_left = barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
|
||||
}
|
||||
if (move_block_left <= 0) {
|
||||
move_block_left = parseInt(parseInt(blockSize.value.width) / 2)
|
||||
}
|
||||
//拖动后小方块的left值
|
||||
moveBlockLeft.value = move_block_left - startLeft.value + 'px'
|
||||
leftBarWidth.value = move_block_left - startLeft.value + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
//鼠标松开
|
||||
const end = () => {
|
||||
endMovetime.value = +new Date()
|
||||
//判断是否重合
|
||||
if (status.value && isEnd.value == false) {
|
||||
var moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', ''))
|
||||
moveLeftDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth)
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value
|
||||
? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
|
||||
: JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
token: backToken.value
|
||||
}
|
||||
checkCaptcha(data).then((response) => {
|
||||
const res = response.data
|
||||
if (res.repCode == '0000') {
|
||||
moveBlockBackgroundColor.value = '#5cb85c'
|
||||
leftBarBorderColor.value = '#5cb85c'
|
||||
iconColor.value = '#fff'
|
||||
iconClass.value = 'icon-check'
|
||||
showRefresh.value = false
|
||||
isEnd.value = true
|
||||
if (mode.value == 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
passFlag.value = true
|
||||
tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s
|
||||
${t('component.captcha.success')}`
|
||||
const captchaVerification = secretKey.value
|
||||
? aesEncrypt(backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
|
||||
: backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
proxy.$parent.closeBox()
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
}, 1000)
|
||||
} else {
|
||||
moveBlockBackgroundColor.value = '#d9534f'
|
||||
leftBarBorderColor.value = '#d9534f'
|
||||
iconColor.value = '#fff'
|
||||
iconClass.value = 'icon-close'
|
||||
passFlag.value = false
|
||||
setTimeout(function () {
|
||||
refresh()
|
||||
}, 1000)
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
tipWords.value = t('component.captcha.fail')
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
status.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
showRefresh.value = true
|
||||
finishText.value = ''
|
||||
|
||||
transitionLeft.value = 'left .3s'
|
||||
moveBlockLeft.value = 0
|
||||
|
||||
leftBarWidth.value = undefined
|
||||
transitionWidth.value = 'width .3s'
|
||||
|
||||
leftBarBorderColor.value = '#ddd'
|
||||
moveBlockBackgroundColor.value = '#fff'
|
||||
iconColor.value = '#000'
|
||||
iconClass.value = 'icon-right'
|
||||
isEnd.value = false
|
||||
|
||||
await getPictrue()
|
||||
setTimeout(() => {
|
||||
transitionWidth.value = ''
|
||||
transitionLeft.value = ''
|
||||
text.value = explain.value
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
const getPictrue = async () => {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
const res = await getCaptcha(data)
|
||||
if (res.data.repCode == '0000') {
|
||||
backImgBase.value = res.data.repData.originalImageBase64
|
||||
blockBackImgBase.value = 'data:image/png;base64,' + res.data.repData.jigsawImageBase64
|
||||
backToken.value = res.data.repData.token
|
||||
secretKey.value = res.data.repData.secretKey
|
||||
} else {
|
||||
tipWords.value = res.data.repMsg
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,4 @@
|
|||
import VerifySlide from './VerifySlide.vue'
|
||||
import VerifyPoints from './VerifyPoints.vue'
|
||||
|
||||
export { VerifySlide, VerifyPoints }
|
|
@ -0,0 +1,14 @@
|
|||
import CryptoJS from 'crypto-js'
|
||||
/**
|
||||
* @word 要加密的内容
|
||||
* @keyWord String 服务器随机返回的关键字
|
||||
* */
|
||||
export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord)
|
||||
const srcs = CryptoJS.enc.Utf8.parse(word)
|
||||
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
})
|
||||
return encrypted.toString()
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
export function resetSize(vm) {
|
||||
let img_width, img_height, bar_width, bar_height //图片的宽度、高度,移动条的宽度、高度
|
||||
const EmployeeWindow = window as any
|
||||
const parentWidth = vm.$el.parentNode.offsetWidth || EmployeeWindow.offsetWidth
|
||||
const parentHeight = vm.$el.parentNode.offsetHeight || EmployeeWindow.offsetHeight
|
||||
if (vm.imgSize.width.indexOf('%') != -1) {
|
||||
img_width = (parseInt(vm.imgSize.width) / 100) * parentWidth + 'px'
|
||||
} else {
|
||||
img_width = vm.imgSize.width
|
||||
}
|
||||
|
||||
if (vm.imgSize.height.indexOf('%') != -1) {
|
||||
img_height = (parseInt(vm.imgSize.height) / 100) * parentHeight + 'px'
|
||||
} else {
|
||||
img_height = vm.imgSize.height
|
||||
}
|
||||
|
||||
if (vm.barSize.width.indexOf('%') != -1) {
|
||||
bar_width = (parseInt(vm.barSize.width) / 100) * parentWidth + 'px'
|
||||
} else {
|
||||
bar_width = vm.barSize.width
|
||||
}
|
||||
|
||||
if (vm.barSize.height.indexOf('%') != -1) {
|
||||
bar_height = (parseInt(vm.barSize.height) / 100) * parentHeight + 'px'
|
||||
} else {
|
||||
bar_height = vm.barSize.height
|
||||
}
|
||||
|
||||
return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }
|
||||
}
|
||||
|
||||
export const _code_chars = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z'
|
||||
]
|
||||
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
|
||||
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']
|
|
@ -1,7 +0,0 @@
|
|||
import { withInstall } from '@/utils'
|
||||
import basicDragVerify from './src/DragVerify.vue'
|
||||
import rotateDragVerify from './src/ImgRotate.vue'
|
||||
|
||||
export const BasicDragVerify = withInstall(basicDragVerify)
|
||||
export const RotateDragVerify = withInstall(rotateDragVerify)
|
||||
export * from './src/typing'
|
|
@ -1,365 +0,0 @@
|
|||
<script lang="tsx">
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, ref, computed, unref, reactive, watch, watchEffect } from 'vue'
|
||||
import { useTimeoutFn } from '@/hooks/core/useTimeout'
|
||||
import { useEventListener } from '@/hooks/event/useEventListener'
|
||||
import { basicProps } from './props'
|
||||
import { getSlot } from '@/utils/helper/tsxHelper'
|
||||
import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseDargVerify',
|
||||
props: basicProps,
|
||||
emits: ['success', 'update:value', 'change', 'start', 'move', 'end'],
|
||||
setup(props, { emit, slots, expose }) {
|
||||
const state = reactive({
|
||||
isMoving: false,
|
||||
isPassing: false,
|
||||
moveDistance: 0,
|
||||
toLeft: false,
|
||||
startTime: 0,
|
||||
endTime: 0
|
||||
})
|
||||
|
||||
const wrapElRef = ref<HTMLDivElement | null>(null)
|
||||
const barElRef = ref<HTMLDivElement | null>(null)
|
||||
const contentElRef = ref<HTMLDivElement | null>(null)
|
||||
const actionElRef = ref(null) as Ref<HTMLDivElement | null>
|
||||
|
||||
useEventListener({
|
||||
el: document,
|
||||
name: 'mouseup',
|
||||
listener: () => {
|
||||
if (state.isMoving) {
|
||||
resume()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getActionStyleRef = computed(() => {
|
||||
const { height, actionStyle } = props
|
||||
const h = `${parseInt(height as string)}px`
|
||||
return {
|
||||
left: 0,
|
||||
width: h,
|
||||
height: h,
|
||||
...actionStyle
|
||||
}
|
||||
})
|
||||
|
||||
const getWrapStyleRef = computed(() => {
|
||||
const { height, width, circle, wrapStyle } = props
|
||||
const h = parseInt(height as string)
|
||||
const w = `${parseInt(width as string)}px`
|
||||
return {
|
||||
width: w,
|
||||
height: `${h}px`,
|
||||
lineHeight: `${h}px`,
|
||||
borderRadius: circle ? h / 2 + 'px' : 0,
|
||||
...wrapStyle
|
||||
}
|
||||
})
|
||||
|
||||
const getBarStyleRef = computed(() => {
|
||||
const { height, circle, barStyle } = props
|
||||
const h = parseInt(height as string)
|
||||
return {
|
||||
height: `${h}px`,
|
||||
borderRadius: circle ? h / 2 + 'px 0 0 ' + h / 2 + 'px' : 0,
|
||||
...barStyle
|
||||
}
|
||||
})
|
||||
|
||||
const getContentStyleRef = computed(() => {
|
||||
const { height, width, contentStyle } = props
|
||||
const h = `${parseInt(height as string)}px`
|
||||
const w = `${parseInt(width as string)}px`
|
||||
|
||||
return {
|
||||
height: h,
|
||||
width: w,
|
||||
...contentStyle
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => state.isPassing,
|
||||
(isPassing) => {
|
||||
if (isPassing) {
|
||||
const { startTime, endTime } = state
|
||||
const time = (endTime - startTime) / 1000
|
||||
emit('success', { isPassing, time: time.toFixed(1) })
|
||||
emit('update:value', isPassing)
|
||||
emit('change', isPassing)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watchEffect(() => {
|
||||
state.isPassing = !!props.value
|
||||
})
|
||||
|
||||
function getEventPageX(e: MouseEvent | TouchEvent) {
|
||||
return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX
|
||||
}
|
||||
|
||||
function handleDragStart(e: MouseEvent | TouchEvent) {
|
||||
if (state.isPassing) {
|
||||
return
|
||||
}
|
||||
const actionEl = unref(actionElRef)
|
||||
if (!actionEl) return
|
||||
emit('start', e)
|
||||
state.moveDistance = getEventPageX(e) - parseInt(actionEl.style.left.replace('px', ''), 10)
|
||||
state.startTime = new Date().getTime()
|
||||
state.isMoving = true
|
||||
}
|
||||
|
||||
function getOffset(el: HTMLDivElement) {
|
||||
const actionWidth = parseInt(el.style.width)
|
||||
const { width } = props
|
||||
const widthNum = parseInt(width as string)
|
||||
const offset = widthNum - actionWidth - 6
|
||||
return { offset, widthNum, actionWidth }
|
||||
}
|
||||
|
||||
function handleDragMoving(e: MouseEvent | TouchEvent) {
|
||||
const { isMoving, moveDistance } = state
|
||||
if (isMoving) {
|
||||
const actionEl = unref(actionElRef)
|
||||
const barEl = unref(barElRef)
|
||||
if (!actionEl || !barEl) return
|
||||
const { offset, widthNum, actionWidth } = getOffset(actionEl)
|
||||
const moveX = getEventPageX(e) - moveDistance
|
||||
|
||||
emit('move', {
|
||||
event: e,
|
||||
moveDistance,
|
||||
moveX
|
||||
})
|
||||
if (moveX > 0 && moveX <= offset) {
|
||||
actionEl.style.left = `${moveX}px`
|
||||
barEl.style.width = `${moveX + actionWidth / 2}px`
|
||||
} else if (moveX > offset) {
|
||||
actionEl.style.left = `${widthNum - actionWidth}px`
|
||||
barEl.style.width = `${widthNum - actionWidth / 2}px`
|
||||
if (!props.isSlot) {
|
||||
checkPass()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragOver(e: MouseEvent | TouchEvent) {
|
||||
const { isMoving, isPassing, moveDistance } = state
|
||||
if (isMoving && !isPassing) {
|
||||
emit('end', e)
|
||||
const actionEl = unref(actionElRef)
|
||||
const barEl = unref(barElRef)
|
||||
if (!actionEl || !barEl) return
|
||||
const moveX = getEventPageX(e) - moveDistance
|
||||
const { offset, widthNum, actionWidth } = getOffset(actionEl)
|
||||
if (moveX < offset) {
|
||||
if (!props.isSlot) {
|
||||
resume()
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (!props.value) {
|
||||
resume()
|
||||
} else {
|
||||
const contentEl = unref(contentElRef)
|
||||
if (contentEl) {
|
||||
contentEl.style.width = `${parseInt(barEl.style.width)}px`
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
} else {
|
||||
actionEl.style.left = `${widthNum - actionWidth}px`
|
||||
barEl.style.width = `${widthNum - actionWidth / 2}px`
|
||||
checkPass()
|
||||
}
|
||||
state.isMoving = false
|
||||
}
|
||||
}
|
||||
|
||||
function checkPass() {
|
||||
if (props.isSlot) {
|
||||
resume()
|
||||
return
|
||||
}
|
||||
state.endTime = new Date().getTime()
|
||||
state.isPassing = true
|
||||
state.isMoving = false
|
||||
}
|
||||
|
||||
function resume() {
|
||||
state.isMoving = false
|
||||
state.isPassing = false
|
||||
state.moveDistance = 0
|
||||
state.toLeft = false
|
||||
state.startTime = 0
|
||||
state.endTime = 0
|
||||
const actionEl = unref(actionElRef)
|
||||
const barEl = unref(barElRef)
|
||||
const contentEl = unref(contentElRef)
|
||||
if (!actionEl || !barEl || !contentEl) return
|
||||
state.toLeft = true
|
||||
useTimeoutFn(() => {
|
||||
state.toLeft = false
|
||||
actionEl.style.left = '0'
|
||||
barEl.style.width = '0'
|
||||
// The time is consistent with the animation time
|
||||
}, 300)
|
||||
contentEl.style.width = unref(getContentStyleRef).width
|
||||
}
|
||||
|
||||
expose({
|
||||
resume
|
||||
})
|
||||
|
||||
return () => {
|
||||
const renderBar = () => {
|
||||
const cls = [`darg-verify-bar`]
|
||||
if (state.toLeft) {
|
||||
cls.push('to-left')
|
||||
}
|
||||
return <div class={cls} ref={barElRef} style={unref(getBarStyleRef)} />
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
const cls = [`darg-verify-content`]
|
||||
const { isPassing } = state
|
||||
const { text, successText } = props
|
||||
|
||||
isPassing && cls.push('success')
|
||||
|
||||
return (
|
||||
<div class={cls} ref={contentElRef} style={unref(getContentStyleRef)}>
|
||||
{getSlot(slots, 'text', isPassing) || (isPassing ? successText : text)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderAction = () => {
|
||||
const cls = [`darg-verify-action`]
|
||||
const { toLeft, isPassing } = state
|
||||
if (toLeft) {
|
||||
cls.push('to-left')
|
||||
}
|
||||
return (
|
||||
<div class={cls} onMousedown={handleDragStart} onTouchstart={handleDragStart} style={unref(getActionStyleRef)} ref={actionElRef}>
|
||||
{getSlot(slots, 'actionIcon', isPassing) ||
|
||||
(isPassing ? (
|
||||
<CheckOutlined class={`darg-verify-action__icon`} />
|
||||
) : (
|
||||
<DoubleRightOutlined class={`darg-verify-action__icon`} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class="darg-verify"
|
||||
ref={wrapElRef}
|
||||
style={unref(getWrapStyleRef)}
|
||||
onMousemove={handleDragMoving}
|
||||
onTouchmove={handleDragMoving}
|
||||
onMouseleave={handleDragOver}
|
||||
onMouseup={handleDragOver}
|
||||
onTouchend={handleDragOver}
|
||||
>
|
||||
{renderBar()}
|
||||
{renderContent()}
|
||||
{renderAction()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@radius: 4px;
|
||||
|
||||
.darg-verify {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-color: rgb(238 238 238);
|
||||
border: 1px solid #ddd;
|
||||
border-radius: @radius;
|
||||
|
||||
&-bar {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 36px;
|
||||
background-color: @success-color;
|
||||
border-radius: @radius;
|
||||
|
||||
&.to-left {
|
||||
width: 0 !important;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
font-size: 12px;
|
||||
text-size-adjust: none;
|
||||
background-color: -webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
right top,
|
||||
color-stop(0, #333),
|
||||
color-stop(0.4, #333),
|
||||
color-stop(0.5, #fff),
|
||||
color-stop(0.6, #333),
|
||||
color-stop(1, #333)
|
||||
);
|
||||
animation: slidetounlock 3s infinite;
|
||||
background-clip: text;
|
||||
user-select: none;
|
||||
|
||||
&.success {
|
||||
-webkit-text-fill-color: @white;
|
||||
}
|
||||
|
||||
& > * {
|
||||
-webkit-text-fill-color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
cursor: move;
|
||||
background-color: @white;
|
||||
border-radius: @radius;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__icon {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
&.to-left {
|
||||
left: 0 !important;
|
||||
transition: left 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slidetounlock {
|
||||
0% {
|
||||
background-position: -120px 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 120px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,214 +0,0 @@
|
|||
<script lang="tsx">
|
||||
import type { MoveData, DragVerifyActionType } from './typing'
|
||||
import { defineComponent, computed, unref, reactive, watch, ref } from 'vue'
|
||||
import { useTimeoutFn } from '@/hooks/core/useTimeout'
|
||||
import BasicDragVerify from './DragVerify.vue'
|
||||
import { hackCss } from '@/utils/domUtils'
|
||||
import { rotateProps } from './props'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ImgRotateDragVerify',
|
||||
inheritAttrs: false,
|
||||
props: rotateProps,
|
||||
emits: ['success', 'change', 'update:value'],
|
||||
setup(props, { emit, attrs, expose }) {
|
||||
const basicRef = ref<Nullable<DragVerifyActionType>>(null)
|
||||
const state = reactive({
|
||||
showTip: false,
|
||||
isPassing: false,
|
||||
imgStyle: {},
|
||||
randomRotate: 0,
|
||||
currentRotate: 0,
|
||||
toOrigin: false,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
draged: false
|
||||
})
|
||||
const { t } = useI18n()
|
||||
|
||||
watch(
|
||||
() => state.isPassing,
|
||||
(isPassing) => {
|
||||
if (isPassing) {
|
||||
const { startTime, endTime } = state
|
||||
const time = (endTime - startTime) / 1000
|
||||
emit('success', { isPassing, time: time.toFixed(1) })
|
||||
emit('change', isPassing)
|
||||
emit('update:value', isPassing)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const getImgWrapStyleRef = computed(() => {
|
||||
const { imgWrapStyle, imgWidth } = props
|
||||
return {
|
||||
width: `${imgWidth}px`,
|
||||
height: `${imgWidth}px`,
|
||||
...imgWrapStyle
|
||||
}
|
||||
})
|
||||
|
||||
const getFactorRef = computed(() => {
|
||||
const { minDegree, maxDegree } = props
|
||||
if (minDegree === maxDegree) {
|
||||
return Math.floor(1 + Math.random() * 1) / 10 + 1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
function handleStart() {
|
||||
state.startTime = new Date().getTime()
|
||||
}
|
||||
|
||||
function handleDragBarMove(data: MoveData) {
|
||||
state.draged = true
|
||||
const { imgWidth, height, maxDegree } = props
|
||||
const { moveX } = data
|
||||
const currentRotate = Math.ceil((moveX / (imgWidth! - parseInt(height as string))) * maxDegree! * unref(getFactorRef))
|
||||
state.currentRotate = currentRotate
|
||||
state.imgStyle = hackCss('transform', `rotateZ(${state.randomRotate - currentRotate}deg)`)
|
||||
}
|
||||
|
||||
function handleImgOnLoad() {
|
||||
const { minDegree, maxDegree } = props
|
||||
const ranRotate = Math.floor(minDegree! + Math.random() * (maxDegree! - minDegree!)) // 生成随机角度
|
||||
state.randomRotate = ranRotate
|
||||
state.imgStyle = hackCss('transform', `rotateZ(${ranRotate}deg)`)
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
const { randomRotate, currentRotate } = state
|
||||
const { diffDegree } = props
|
||||
|
||||
if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) {
|
||||
state.imgStyle = hackCss('transform', `rotateZ(${randomRotate}deg)`)
|
||||
state.toOrigin = true
|
||||
useTimeoutFn(() => {
|
||||
state.toOrigin = false
|
||||
state.showTip = true
|
||||
// 时间与动画时间保持一致
|
||||
}, 300)
|
||||
} else {
|
||||
checkPass()
|
||||
}
|
||||
state.showTip = true
|
||||
}
|
||||
function checkPass() {
|
||||
state.isPassing = true
|
||||
state.endTime = new Date().getTime()
|
||||
}
|
||||
|
||||
function resume() {
|
||||
state.showTip = false
|
||||
const basicEl = unref(basicRef)
|
||||
if (!basicEl) {
|
||||
return
|
||||
}
|
||||
state.isPassing = false
|
||||
|
||||
basicEl.resume()
|
||||
handleImgOnLoad()
|
||||
}
|
||||
|
||||
expose({ resume })
|
||||
|
||||
// handleImgOnLoad();
|
||||
return () => {
|
||||
const { src } = props
|
||||
const { toOrigin, isPassing, startTime, endTime } = state
|
||||
const imgCls: string[] = []
|
||||
if (toOrigin) {
|
||||
imgCls.push('to-origin')
|
||||
}
|
||||
const time = (endTime - startTime) / 1000
|
||||
|
||||
return (
|
||||
<div class="ir-dv">
|
||||
<div class={`ir-dv-img__wrap`} style={unref(getImgWrapStyleRef)}>
|
||||
<img
|
||||
src={src}
|
||||
onLoad={handleImgOnLoad}
|
||||
width={parseInt(props.width as string)}
|
||||
class={imgCls}
|
||||
style={state.imgStyle}
|
||||
onClick={() => {
|
||||
resume()
|
||||
}}
|
||||
alt="verify"
|
||||
/>
|
||||
{state.showTip && (
|
||||
<span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}>
|
||||
{state.isPassing ? t('component.verify.time', { time: time.toFixed(1) }) : t('component.verify.error')}
|
||||
</span>
|
||||
)}
|
||||
{!state.showTip && !state.draged && <span class={[`ir-dv-img__tip`, 'normal']}>{t('component.verify.redoTip')}</span>}
|
||||
</div>
|
||||
<BasicDragVerify
|
||||
class={`ir-dv-drag__bar`}
|
||||
onMove={handleDragBarMove}
|
||||
onEnd={handleDragEnd}
|
||||
onStart={handleStart}
|
||||
ref={basicRef}
|
||||
{...{ ...attrs, ...props }}
|
||||
value={isPassing}
|
||||
isSlot={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
.ir-dv {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&-img__wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
&.to-origin {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-img__tip {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
color: @white;
|
||||
text-align: center;
|
||||
|
||||
&.success {
|
||||
background-color: fade(@success-color, 60%);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: fade(@error-color, 60%);
|
||||
}
|
||||
|
||||
&.normal {
|
||||
background-color: rgb(0 0 0 / 30%);
|
||||
}
|
||||
}
|
||||
|
||||
&-drag__bar {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,86 +0,0 @@
|
|||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
export const basicProps = {
|
||||
value: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
|
||||
isSlot: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
|
||||
text: {
|
||||
type: [String] as PropType<string>,
|
||||
default: t('component.verify.dragText')
|
||||
},
|
||||
successText: {
|
||||
type: [String] as PropType<string>,
|
||||
default: t('component.verify.successText')
|
||||
},
|
||||
height: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: 40
|
||||
},
|
||||
|
||||
width: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: 220
|
||||
},
|
||||
|
||||
circle: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
|
||||
wrapStyle: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
contentStyle: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
barStyle: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
actionStyle: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
}
|
||||
}
|
||||
|
||||
export const rotateProps = {
|
||||
...basicProps,
|
||||
src: {
|
||||
type: String as PropType<string>
|
||||
},
|
||||
|
||||
imgWidth: {
|
||||
type: Number as PropType<number>,
|
||||
default: 260
|
||||
},
|
||||
|
||||
imgWrapStyle: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
|
||||
minDegree: {
|
||||
type: Number as PropType<number>,
|
||||
default: 90
|
||||
},
|
||||
|
||||
maxDegree: {
|
||||
type: Number as PropType<number>,
|
||||
default: 270
|
||||
},
|
||||
|
||||
diffDegree: {
|
||||
type: Number as PropType<number>,
|
||||
default: 20
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
export interface DragVerifyActionType {
|
||||
resume: () => void
|
||||
}
|
||||
|
||||
export interface PassingData {
|
||||
isPassing: boolean
|
||||
time: number
|
||||
}
|
||||
|
||||
export interface MoveData {
|
||||
event: MouseEvent | TouchEvent
|
||||
moveDistance: number
|
||||
moveX: number
|
||||
}
|
|
@ -35,11 +35,11 @@ export interface RetryRequest {
|
|||
count: number
|
||||
waitTime: number
|
||||
}
|
||||
|
||||
export interface Result<T = any> {
|
||||
code: number
|
||||
type: 'success' | 'error' | 'warning'
|
||||
message: string
|
||||
result: T
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
|
||||
// multipart/form-data: upload file
|
||||
|
|
|
@ -159,6 +159,10 @@ export interface GlobConfig {
|
|||
urlPrefix?: string
|
||||
// Project abbreviation
|
||||
shortName: string
|
||||
// 租户开关
|
||||
tenantEnable: string
|
||||
// 验证码开关
|
||||
captchaEnable: string
|
||||
}
|
||||
export interface GlobEnvConfig {
|
||||
// Site title
|
||||
|
@ -171,4 +175,8 @@ export interface GlobEnvConfig {
|
|||
VITE_GLOB_APP_SHORT_NAME: string
|
||||
// Upload url
|
||||
VITE_GLOB_UPLOAD_URL?: string
|
||||
// 租户开关
|
||||
VITE_GLOB_APP_TENANT_ENABLE: string
|
||||
// 验证码开关
|
||||
VITE_GLOB_APP_CAPTCHA_ENABLE: string
|
||||
}
|
||||
|
|
|
@ -8,6 +8,16 @@ declare interface PromiseFn<T = any, R = T> {
|
|||
|
||||
declare type RefType<T> = T | null
|
||||
|
||||
declare interface PageParam {
|
||||
pageSize?: number
|
||||
pageNo?: number
|
||||
}
|
||||
|
||||
declare interface PageResult<T = any> {
|
||||
list: T[]
|
||||
total: number
|
||||
}
|
||||
|
||||
declare type LabelValueOptions = {
|
||||
label: string
|
||||
value: any
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ErrorTypeEnum } from '@/enums/exceptionEnum'
|
||||
import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum'
|
||||
import { RoleInfo } from '@/api/sys/model/userModel'
|
||||
|
||||
// Lock screen information
|
||||
export interface LockInfo {
|
||||
|
@ -37,7 +36,7 @@ export interface UserInfo {
|
|||
avatar: string
|
||||
desc?: string
|
||||
homePath?: string
|
||||
roles: RoleInfo[]
|
||||
roles: string[]
|
||||
}
|
||||
|
||||
export interface BeforeMiniState {
|
||||
|
@ -46,3 +45,8 @@ export interface BeforeMiniState {
|
|||
menuMode?: MenuModeEnum
|
||||
menuType?: MenuTypeEnum
|
||||
}
|
||||
|
||||
export interface DictState {
|
||||
dictMap: Map<string, any>
|
||||
isSetDict: boolean
|
||||
}
|
||||
|
|
|
@ -5,43 +5,47 @@ export {}
|
|||
declare module 'vue-router' {
|
||||
interface RouteMeta extends Record<string | number | symbol, unknown> {
|
||||
orderNo?: number
|
||||
// title
|
||||
// 路由title 一般必填
|
||||
title: string
|
||||
// dynamic router level.
|
||||
// 动态路由可打开Tab页数
|
||||
dynamicLevel?: number
|
||||
// dynamic router real route path (For performance).
|
||||
// 动态路由的实际Path, 即去除路由的动态部分;
|
||||
realPath?: string
|
||||
// Whether to ignore permissions
|
||||
// 是否忽略权限,只在权限模式为Role的时候有效
|
||||
ignoreAuth?: boolean
|
||||
// role info
|
||||
// 可以访问的角色,只在权限模式为Role的时候有效
|
||||
roles?: RoleEnum[]
|
||||
// Whether not to cache
|
||||
// 是否忽略KeepAlive缓存
|
||||
ignoreKeepAlive?: boolean
|
||||
// Is it fixed on tab
|
||||
// 是否固定标签
|
||||
affix?: boolean
|
||||
// icon on tab
|
||||
// 图标,也是菜单图标
|
||||
icon?: string
|
||||
// 内嵌iframe的地址
|
||||
frameSrc?: string
|
||||
// current page transition
|
||||
// 指定该路由切换的动画名
|
||||
transitionName?: string
|
||||
// Whether the route has been dynamically added
|
||||
// 隐藏该路由在面包屑上面的显示
|
||||
hideBreadcrumb?: boolean
|
||||
// Hide submenu
|
||||
// 如果该路由会携带参数,且需要在tab页上面显示。则需要设置为true
|
||||
carryParam?: boolean
|
||||
// 隐藏所有子菜单
|
||||
hideChildrenInMenu?: boolean
|
||||
// Carrying parameters
|
||||
carryParam?: boolean
|
||||
// Used internally to mark single-level menus
|
||||
single?: boolean
|
||||
// Currently active menu
|
||||
// 当前激活的菜单。用于配置详情页时左侧激活的菜单路径
|
||||
currentActiveMenu?: string
|
||||
// Never show in tab
|
||||
// 当前路由不再标签页显示
|
||||
hideTab?: boolean
|
||||
// Never show in menu
|
||||
// 当前路由不再菜单显示
|
||||
hideMenu?: boolean
|
||||
isLink?: boolean
|
||||
// only build for Menu
|
||||
// 菜单排序,只对第一级有效
|
||||
orderNo?: number
|
||||
// 忽略路由。用于在ROUTE_MAPPING以及BACK权限模式下,生成对应的菜单而忽略路由。2.5.3以上版本有效
|
||||
ignoreRoute?: boolean
|
||||
// Hide path for children
|
||||
// 是否在子级菜单的完整path中忽略本级path。2.5.3以上版本有效
|
||||
hidePathForChildren?: boolean
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,19 @@ import { isFunction } from '@/utils/is'
|
|||
import { cloneDeep } from 'lodash-es'
|
||||
import { ContentTypeEnum } from '@/enums/httpEnum'
|
||||
import { RequestEnum } from '@/enums/httpEnum'
|
||||
import { downloadByData } from '@/utils/file/download'
|
||||
import { useGlobSetting } from '@/hooks/setting'
|
||||
import { setAccessToken, getRefreshToken, getTenantId, getAccessToken } from '@/utils/auth'
|
||||
|
||||
export * from './axiosTransform'
|
||||
const globSetting = useGlobSetting()
|
||||
// 请求队列
|
||||
let requestList: any[] = []
|
||||
// 是否正在刷新中
|
||||
let isRefreshToken = false
|
||||
|
||||
/**
|
||||
* @description: axios module
|
||||
* @description: axios 模块
|
||||
*/
|
||||
export class VAxios {
|
||||
private axiosInstance: AxiosInstance
|
||||
|
@ -25,7 +33,7 @@ export class VAxios {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description: Create axios instance
|
||||
* @description: 创建 axios 实例
|
||||
*/
|
||||
private createAxios(config: CreateAxiosOptions): void {
|
||||
this.axiosInstance = axios.create(config)
|
||||
|
@ -39,9 +47,13 @@ export class VAxios {
|
|||
getAxios(): AxiosInstance {
|
||||
return this.axiosInstance
|
||||
}
|
||||
refreshToken() {
|
||||
axios.defaults.headers.common['tenant-id'] = getTenantId() as number
|
||||
return axios.post(globSetting.apiUrl + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Reconfigure axios
|
||||
* @description: 重新配置 axios
|
||||
*/
|
||||
configAxios(config: CreateAxiosOptions) {
|
||||
if (!this.axiosInstance) {
|
||||
|
@ -51,7 +63,7 @@ export class VAxios {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description: Set general header
|
||||
* @description: 设置通用标题
|
||||
*/
|
||||
setHeader(headers: any): void {
|
||||
if (!this.axiosInstance) {
|
||||
|
@ -86,13 +98,54 @@ export class VAxios {
|
|||
return config
|
||||
}, undefined)
|
||||
|
||||
// Request interceptor error capture
|
||||
// 请求拦截器错误捕获
|
||||
requestInterceptorsCatch &&
|
||||
isFunction(requestInterceptorsCatch) &&
|
||||
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch)
|
||||
|
||||
// Response result interceptor processing
|
||||
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
|
||||
// 响应结果拦截器处理
|
||||
this.axiosInstance.interceptors.response.use(async (res: AxiosResponse<any>) => {
|
||||
const config = res.config
|
||||
if (res.data.code === 401) {
|
||||
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
|
||||
if (!isRefreshToken) {
|
||||
isRefreshToken = true
|
||||
// 1. 获取到刷新token
|
||||
if (getRefreshToken()) {
|
||||
// 2. 进行刷新访问令牌
|
||||
console.info('进行刷新访问令牌')
|
||||
try {
|
||||
const refreshTokenRes = await this.refreshToken()
|
||||
// 2.1 刷新成功,则回放队列的请求 + 当前请求
|
||||
setAccessToken(refreshTokenRes.data.data)
|
||||
;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken()
|
||||
requestList.forEach((cb: any) => {
|
||||
cb()
|
||||
})
|
||||
requestList = []
|
||||
// TODO
|
||||
// res = await Promise.all([this.axiosInstance(config)])[0]
|
||||
console.info('刷新令牌end', res)
|
||||
} catch (e) {
|
||||
console.info(e)
|
||||
requestList.forEach((cb: any) => {
|
||||
cb()
|
||||
})
|
||||
} finally {
|
||||
requestList = []
|
||||
isRefreshToken = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 添加到队列,等待刷新获取到新的令牌
|
||||
return new Promise((resolve) => {
|
||||
requestList.push(() => {
|
||||
;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
resolve(this.axiosInstance(config))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
res && axiosCanceler.removePending(res.config)
|
||||
if (responseInterceptors && isFunction(responseInterceptors)) {
|
||||
res = responseInterceptors(res)
|
||||
|
@ -100,7 +153,7 @@ export class VAxios {
|
|||
return res
|
||||
}, undefined)
|
||||
|
||||
// Response result interceptor error capture
|
||||
// 响应结果拦截器错误捕获
|
||||
responseInterceptorsCatch &&
|
||||
isFunction(responseInterceptorsCatch) &&
|
||||
this.axiosInstance.interceptors.response.use(undefined, (error) => {
|
||||
|
@ -110,7 +163,7 @@ export class VAxios {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description: File Upload
|
||||
* @description: 文件上传
|
||||
*/
|
||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
|
||||
const formData = new window.FormData()
|
||||
|
@ -142,12 +195,13 @@ export class VAxios {
|
|||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
// @ts-ignore
|
||||
ignoreCancelToken: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// support form-data
|
||||
// 支持表单数据
|
||||
supportFormData(config: AxiosRequestConfig) {
|
||||
const headers = config.headers || this.options.headers
|
||||
const contentType = headers?.['Content-Type'] || headers?.['content-type']
|
||||
|
@ -182,6 +236,94 @@ export class VAxios {
|
|||
return this.request({ ...config, method: 'DELETE' }, options)
|
||||
}
|
||||
|
||||
download<T = any>(config: AxiosRequestConfig, title: string, options?: RequestOptions): Promise<T> {
|
||||
let conf: CreateAxiosOptions = cloneDeep({
|
||||
...config,
|
||||
method: 'GET',
|
||||
responseType: 'blob'
|
||||
})
|
||||
const transform = this.getTransform()
|
||||
|
||||
const { requestOptions } = this.options
|
||||
|
||||
const opt: RequestOptions = Object.assign({}, requestOptions, options)
|
||||
|
||||
const { beforeRequestHook, requestCatchHook } = transform || {}
|
||||
|
||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||
conf = beforeRequestHook(conf, opt)
|
||||
}
|
||||
conf.requestOptions = opt
|
||||
|
||||
conf = this.supportFormData(conf)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axiosInstance
|
||||
.request<any, AxiosResponse<Result>>(conf)
|
||||
.then((res: AxiosResponse<Result>) => {
|
||||
resolve(res as unknown as Promise<T>)
|
||||
// download file
|
||||
if (typeof res != undefined) {
|
||||
downloadByData(res?.data as unknown as BlobPart, title)
|
||||
}
|
||||
})
|
||||
.catch((e: Error | AxiosError) => {
|
||||
if (requestCatchHook && isFunction(requestCatchHook)) {
|
||||
reject(requestCatchHook(e, opt))
|
||||
return
|
||||
}
|
||||
if (axios.isAxiosError(e)) {
|
||||
// rewrite error message from axios in here
|
||||
}
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export<T = any>(config: AxiosRequestConfig, title: string, options?: RequestOptions): Promise<T> {
|
||||
let conf: CreateAxiosOptions = cloneDeep({
|
||||
...config,
|
||||
method: 'POST',
|
||||
responseType: 'blob'
|
||||
})
|
||||
const transform = this.getTransform()
|
||||
|
||||
const { requestOptions } = this.options
|
||||
|
||||
const opt: RequestOptions = Object.assign({}, requestOptions, options)
|
||||
|
||||
const { beforeRequestHook, requestCatchHook } = transform || {}
|
||||
|
||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||
conf = beforeRequestHook(conf, opt)
|
||||
}
|
||||
conf.requestOptions = opt
|
||||
|
||||
conf = this.supportFormData(conf)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axiosInstance
|
||||
.request<any, AxiosResponse<Result>>(conf)
|
||||
.then((res: AxiosResponse<Result>) => {
|
||||
resolve(res as unknown as Promise<T>)
|
||||
// download file
|
||||
if (typeof res != undefined) {
|
||||
downloadByData(res?.data as unknown as BlobPart, title)
|
||||
}
|
||||
})
|
||||
.catch((e: Error | AxiosError) => {
|
||||
if (requestCatchHook && isFunction(requestCatchHook)) {
|
||||
reject(requestCatchHook(e, opt))
|
||||
return
|
||||
}
|
||||
if (axios.isAxiosError(e)) {
|
||||
// rewrite error message from axios in here
|
||||
}
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
let conf: CreateAxiosOptions = cloneDeep(config)
|
||||
// cancelToken 如果被深拷贝,会导致最外层无法使用cancel方法来取消请求
|
||||
|
@ -223,7 +365,7 @@ export class VAxios {
|
|||
return
|
||||
}
|
||||
if (axios.isAxiosError(e)) {
|
||||
// rewrite error message from axios in here
|
||||
// 在此处重写来自 axios 的错误消息
|
||||
}
|
||||
reject(e)
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ export const getPendingUrl = (config: InternalAxiosRequestConfig) => [config.met
|
|||
|
||||
export class AxiosCanceler {
|
||||
/**
|
||||
* Add request
|
||||
* 添加请求
|
||||
* @param {Object} config
|
||||
*/
|
||||
addPending(config: InternalAxiosRequestConfig) {
|
||||
|
@ -19,14 +19,14 @@ export class AxiosCanceler {
|
|||
config.cancelToken ||
|
||||
new axios.CancelToken((cancel) => {
|
||||
if (!pendingMap.has(url)) {
|
||||
// If there is no current request in pending, add it
|
||||
// 如果当前没有待处理的请求,添加它
|
||||
pendingMap.set(url, cancel)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Clear all pending
|
||||
* @description: 清除所有待处理
|
||||
*/
|
||||
removeAllPending() {
|
||||
pendingMap.forEach((cancel) => {
|
||||
|
@ -36,15 +36,14 @@ export class AxiosCanceler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removal request
|
||||
* 删除请求
|
||||
* @param {Object} config
|
||||
*/
|
||||
removePending(config: InternalAxiosRequestConfig) {
|
||||
const url = getPendingUrl(config)
|
||||
|
||||
if (pendingMap.has(url)) {
|
||||
// If there is a current request identifier in pending,
|
||||
// the current request needs to be cancelled and removed
|
||||
// 如果有当前请求标识符处于pending状态,则需要取消当前请求并移除
|
||||
const cancel = pendingMap.get(url)
|
||||
cancel && cancel(url)
|
||||
pendingMap.delete(url)
|
||||
|
|
|
@ -12,8 +12,7 @@ export interface CreateAxiosOptions extends AxiosRequestConfig {
|
|||
|
||||
export abstract class AxiosTransform {
|
||||
/**
|
||||
* @description: Process configuration before request
|
||||
* @description: Process configuration before request
|
||||
* @description: 请求前的流程配置
|
||||
*/
|
||||
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export function checkStatus(status: number, msg: string, errorMessageMode: Error
|
|||
// Jump to the login page if not logged in, and carry the path of the current page
|
||||
// Return to the current page after successful login. This step needs to be operated on the login page.
|
||||
case 401:
|
||||
userStore.setToken(undefined)
|
||||
userStore.setAccessToken(undefined)
|
||||
errMessage = msg || t('sys.api.errMsg401')
|
||||
if (stp === SessionTimeoutProcessingEnum.PAGE_COVERAGE) {
|
||||
userStore.setSessionTimeout(true)
|
||||
|
|
|
@ -16,7 +16,7 @@ export function joinTimestamp(join: boolean, restful = false): string | object {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description: Format request parameter time
|
||||
* @description: 请求参数时间格式
|
||||
*/
|
||||
export function formatRequestDate(params: Recordable) {
|
||||
if (Object.prototype.toString.call(params) !== '[object Object]') {
|
||||
|
|
|
@ -10,8 +10,8 @@ import { checkStatus } from './checkStatus'
|
|||
import { useGlobSetting } from '@/hooks/setting'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum'
|
||||
import { isString, isUnDef, isNull, isEmpty } from '@/utils/is'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { isEmpty, isNull, isString, isUnDef } from '@/utils/is'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import { setObjToUrlParams, deepMerge } from '@/utils'
|
||||
import { useErrorLogStoreWithOut } from '@/store/modules/errorLog'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
@ -22,8 +22,12 @@ import axios from 'axios'
|
|||
|
||||
const globSetting = useGlobSetting()
|
||||
const urlPrefix = globSetting.urlPrefix
|
||||
const tenantEnable = globSetting.tenantEnable
|
||||
const { createMessage, createErrorModal, createSuccessModal } = useMessage()
|
||||
|
||||
// 请求白名单,无须token的接口
|
||||
const whiteList: string[] = ['/login', '/refresh-token']
|
||||
|
||||
/**
|
||||
* @description: 数据处理,方便区分多种处理方式
|
||||
*/
|
||||
|
@ -34,6 +38,10 @@ const transform: AxiosTransform = {
|
|||
transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
const { t } = useI18n()
|
||||
const { isTransformResponse, isReturnNativeResponse } = options
|
||||
// 二进制数据则直接返回
|
||||
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
|
||||
return res.data
|
||||
}
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return res
|
||||
|
@ -50,18 +58,20 @@ const transform: AxiosTransform = {
|
|||
// return '[HTTP] Request has no return value';
|
||||
throw new Error(t('sys.api.apiRequestFailed'))
|
||||
}
|
||||
console.info(data)
|
||||
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
|
||||
const { code, result, message } = data
|
||||
|
||||
const { code, data: result, msg } = data
|
||||
console.info(result)
|
||||
// 这里逻辑可以根据项目进行修改
|
||||
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS
|
||||
if (hasSuccess) {
|
||||
let successMsg = message
|
||||
|
||||
let successMsg = msg
|
||||
if (successMsg === null || successMsg === undefined || successMsg === '') {
|
||||
successMsg = t('sys.api.operationSuccess')
|
||||
}
|
||||
if (isNull(successMsg) || isUnDef(successMsg) || isEmpty(successMsg)) {
|
||||
successMsg = t(`sys.api.operationSuccess`)
|
||||
}
|
||||
|
||||
if (options.successMessageMode === 'modal') {
|
||||
createSuccessModal({ title: t('sys.api.successTip'), content: successMsg })
|
||||
} else if (options.successMessageMode === 'message') {
|
||||
|
@ -74,19 +84,19 @@ const transform: AxiosTransform = {
|
|||
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
|
||||
let timeoutMsg = ''
|
||||
switch (code) {
|
||||
case ResultEnum.TIMEOUT:
|
||||
case ResultEnum.UNAUTHORIZED:
|
||||
timeoutMsg = t('sys.api.timeoutMessage')
|
||||
const userStore = useUserStoreWithOut()
|
||||
userStore.setToken(undefined)
|
||||
userStore.setAccessToken(undefined)
|
||||
userStore.logout(true)
|
||||
break
|
||||
default:
|
||||
if (message) {
|
||||
timeoutMsg = message
|
||||
if (msg) {
|
||||
timeoutMsg = msg
|
||||
}
|
||||
}
|
||||
|
||||
// errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||
// errorMessageMode='modal' 的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
|
||||
if (options.errorMessageMode === 'modal') {
|
||||
createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg })
|
||||
|
@ -147,12 +157,25 @@ const transform: AxiosTransform = {
|
|||
* @description: 请求拦截器处理
|
||||
*/
|
||||
requestInterceptors: (config, options) => {
|
||||
// 是否需要设置 token
|
||||
let isToken = (config as Recordable)?.requestOptions?.withToken == false
|
||||
whiteList.some((v) => {
|
||||
if (config.url) {
|
||||
config.url.indexOf(v) > -1
|
||||
return (isToken = false)
|
||||
}
|
||||
})
|
||||
// 请求之前处理config
|
||||
const token = getToken()
|
||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||
const token = getAccessToken()
|
||||
if (token && !isToken) {
|
||||
// jwt token
|
||||
;(config as Recordable).headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : token
|
||||
}
|
||||
// 设置租户
|
||||
if (tenantEnable && tenantEnable === 'true') {
|
||||
const tenantId = getTenantId()
|
||||
if (tenantId) (config as Recordable).headers['tenant-id'] = tenantId
|
||||
}
|
||||
return config
|
||||
},
|
||||
|
||||
|
@ -221,7 +244,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
|||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
|
||||
// authentication schemes,e.g: Bearer
|
||||
// authenticationScheme: 'Bearer',
|
||||
authenticationScheme: '',
|
||||
authenticationScheme: 'Bearer',
|
||||
timeout: 10 * 1000,
|
||||
// 基础接口地址
|
||||
// baseURL: globSetting.apiUrl,
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="hidden min-h-full pl-4 mr-4 xl:flex xl:flex-col xl:w-6/12">
|
||||
<AppLogo class="-enter-x" />
|
||||
<div class="my-auto">
|
||||
<img :alt="title" src="../../../assets/svg/login-box-bg.svg" class="w-1/2 -mt-16 -enter-x" />
|
||||
<img :alt="title" src="@/assets/svg/login-box-bg.svg" class="w-1/2 -mt-16 -enter-x" />
|
||||
<div class="mt-10 font-medium text-white -enter-x">
|
||||
<span class="inline-block mt-4 text-3xl"> {{ t('sys.login.signInTitle') }}</span>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
<template>
|
||||
<LoginFormTitle v-show="getShow" class="enter-x" />
|
||||
<Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef" v-show="getShow" @keypress.enter="handleLogin">
|
||||
<FormItem name="account" class="enter-x">
|
||||
<Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" class="fix-auto-fill" />
|
||||
<FormItem name="tenantName" class="enter-x">
|
||||
<Input
|
||||
v-if="tenantEnable === 'true'"
|
||||
size="large"
|
||||
v-model:value="formData.tenantName"
|
||||
:placeholder="t('sys.login.tenantName')"
|
||||
class="fix-auto-fill"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="username" class="enter-x">
|
||||
<Input size="large" v-model:value="formData.username" :placeholder="t('sys.login.userName')" class="fix-auto-fill" />
|
||||
</FormItem>
|
||||
<FormItem name="password" class="enter-x">
|
||||
<InputPassword size="large" visibilityToggle v-model:value="formData.password" :placeholder="t('sys.login.password')" />
|
||||
|
@ -28,7 +37,7 @@
|
|||
</ARow>
|
||||
|
||||
<FormItem class="enter-x">
|
||||
<Button type="primary" size="large" block @click="handleLogin" :loading="loading">
|
||||
<Button type="primary" size="large" block @click="getCode" :loading="loading">
|
||||
{{ t('sys.login.loginButton') }}
|
||||
</Button>
|
||||
<!-- <Button size="large" class="mt-4 enter-x" block @click="handleRegister">
|
||||
|
@ -47,9 +56,9 @@
|
|||
</Button>
|
||||
</ACol>
|
||||
<ACol :md="6" :xs="24">
|
||||
<Button block @click="setLoginState(LoginStateEnum.REGISTER)">
|
||||
<a-button block @click="setLoginState(LoginStateEnum.REGISTER)">
|
||||
{{ t('sys.login.registerButton') }}
|
||||
</Button>
|
||||
</a-button>
|
||||
</ACol>
|
||||
</ARow>
|
||||
|
||||
|
@ -63,6 +72,7 @@
|
|||
<TwitterCircleFilled />
|
||||
</div>
|
||||
</Form>
|
||||
<Verify ref="verify" mode="pop" :captchaType="captchaType" :imgSize="{ width: '400px', height: '200px' }" @success="handleLogin" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, unref, computed } from 'vue'
|
||||
|
@ -75,18 +85,29 @@ import { useI18n } from '@/hooks/web/useI18n'
|
|||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
|
||||
import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin'
|
||||
import { useGlobSetting } from '@/hooks/setting'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
//import { onKeyStroke } from '@vueuse/core';
|
||||
|
||||
import * as authUtil from '@/utils/auth'
|
||||
|
||||
import { Verify } from '@/components/Verifition'
|
||||
import { getTenantIdByName } from '@/api/base/login'
|
||||
|
||||
const ACol = Col
|
||||
const ARow = Row
|
||||
const FormItem = Form.Item
|
||||
const InputPassword = Input.Password
|
||||
|
||||
const { t } = useI18n()
|
||||
const { notification, createErrorModal } = useMessage()
|
||||
const { prefixCls } = useDesign('login')
|
||||
const userStore = useUserStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { tenantEnable, captchaEnable } = useGlobSetting()
|
||||
|
||||
const { setLoginState, getLoginState } = useLoginState()
|
||||
const { getFormRules } = useFormRules()
|
||||
|
@ -95,9 +116,14 @@ const formRef = ref()
|
|||
const loading = ref(false)
|
||||
const rememberMe = ref(false)
|
||||
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
|
||||
const formData = reactive({
|
||||
account: 'vben',
|
||||
password: '123456'
|
||||
tenantName: '芋道源码',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: ''
|
||||
})
|
||||
|
||||
const { validForm } = useFormValid(formRef)
|
||||
|
@ -106,20 +132,43 @@ const { validForm } = useFormValid(formRef)
|
|||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
async function handleLogin() {
|
||||
// 获取验证码
|
||||
async function getCode() {
|
||||
// 情况一,未开启:则直接登录
|
||||
if (captchaEnable === 'false') {
|
||||
await handleLogin({})
|
||||
} else {
|
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
||||
// 弹出验证码
|
||||
verify.value.show()
|
||||
}
|
||||
}
|
||||
|
||||
//获取租户ID
|
||||
async function getTenantId() {
|
||||
if (tenantEnable === 'true') {
|
||||
const res = await getTenantIdByName(formData.tenantName)
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin(params) {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
try {
|
||||
loading.value = true
|
||||
const userInfo = await userStore.login({
|
||||
password: data.password,
|
||||
username: data.account,
|
||||
username: data.username,
|
||||
captchaVerification: params.captchaVerification,
|
||||
mode: 'none' //不要默认的错误提示
|
||||
})
|
||||
if (userInfo) {
|
||||
await permissionStore.changePermissionCode(userInfo.permissions)
|
||||
notification.success({
|
||||
message: t('sys.login.loginSuccessTitle'),
|
||||
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
|
||||
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.nickname}`,
|
||||
duration: 3
|
||||
})
|
||||
}
|
||||
|
|
|
@ -48,6 +48,5 @@ const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)
|
|||
async function handleLogin() {
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
console.log(data)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -18,7 +18,7 @@ import { QrCode } from '@/components/Qrcode'
|
|||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useLoginState, LoginStateEnum } from './useLogin'
|
||||
|
||||
const qrCodeUrl = 'https://vben.vvbin.cn/login'
|
||||
const qrCodeUrl = 'https://vben.xingyuv.com/login'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
|
|
|
@ -73,6 +73,5 @@ const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
|||
async function handleRegister() {
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
console.log(data)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -26,12 +26,11 @@ const isBackMode = () => {
|
|||
|
||||
onMounted(() => {
|
||||
// 记录当前的UserId
|
||||
userId.value = userStore.getUserInfo?.userId
|
||||
console.log('Mounted', userStore.getUserInfo)
|
||||
userId.value = userStore.getUserInfo?.user.id
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (userId.value && userId.value !== userStore.getUserInfo.userId) {
|
||||
if (userId.value && userId.value !== userStore.getUserInfo.user.id) {
|
||||
// 登录的不是同一个用户,刷新整个页面以便丢弃之前用户的页面状态
|
||||
document.location.reload()
|
||||
} else if (isBackMode() && permissionStore.getLastBuildMenuTime === 0) {
|
||||
|
|
Loading…
Reference in New Issue