Compare commits

..

15 Commits

Author SHA1 Message Date
xiaoxian521
cdaa4cdba8 release: update 3.5.0 2022-09-10 21:53:46 +08:00
xiaoxian521
496947b524 chore: update 2022-09-09 00:04:29 +08:00
xiaoxian521
d48058c28f feat: 添加cssnano,打包时压缩css体积 2022-09-08 17:15:22 +08:00
xiaoxian521
820f724d5b chore: use pnpm shell-emulator instead of cross-env 2022-09-07 21:53:26 +08:00
xiaoxian521
63e2ddc171 chore: 简化登录页 2022-09-07 16:47:57 +08:00
xiaoxian521
60cdaf4697 refactor: use tailwindcss replace unocss 2022-09-07 16:04:26 +08:00
xiaoxian521
d3cabb0f33 fix: token过期,刷新死循环 2022-09-07 15:23:06 +08:00
xiaoxian521
97193a71a6 chore: update 2022-09-05 22:22:36 +08:00
xiaoxian521
f8694e953c chore: update 2022-09-03 14:06:56 +08:00
xiaoxian521
55f97fce96 chore: update 2022-08-31 11:33:44 +08:00
xiaoxian521
29144aba44 perf: 重置路由时,清空缓存页面 2022-08-24 16:11:41 +08:00
xiaoxian521
9a448143ae chore: window.process定义在index.html中,防止低版本浏览器报错 2022-08-24 09:48:16 +08:00
xiaoxian521
a85a2f9022 chore: 开启vscode括号对指南 2022-08-23 20:49:25 +08:00
xiaoxian521
20fee1418c style: update 2022-08-23 18:02:00 +08:00
xiaoxian521
2888f8c4f1 release: update 3.4.6 2022-08-23 10:43:33 +08:00
58 changed files with 1051 additions and 2594 deletions

View File

@@ -2,3 +2,9 @@ public
dist dist
*.d.ts *.d.ts
package.json package.json
.eslintrc.js
.prettierrc.js
commitlint.config.js
postcss.config.js
tailwind.config.js
stylelint.config.js

View File

@@ -1,6 +1,6 @@
module.exports = { module.exports = {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"], "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"{!(package)*.json,.!(browserslist)*rc}": ["prettier --write--parser json"], "{!(package)*.json}": ["prettier --write--parser json"],
"package.json": ["prettier --write"], "package.json": ["prettier --write"],
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"], "*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
"*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"], "*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"],

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
shamefully-hoist=true
strict-peer-dependencies=false
shell-emulator=true

View File

@@ -3,13 +3,13 @@
"vscode-icons-team.vscode-icons", "vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint", "davidanson.vscode-markdownlint",
"stylelint.vscode-stylelint", "stylelint.vscode-stylelint",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"mikestead.dotenv", "mikestead.dotenv",
"eamodio.gitlens", "eamodio.gitlens",
"antfu.iconify", "antfu.iconify",
"antfu.unocss",
"Vue.volar" "Vue.volar"
] ]
} }

View File

@@ -6,6 +6,7 @@
}, },
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.formatOnPaste": true, "editor.formatOnPaste": true,
"editor.guides.bracketPairs": "active",
"files.autoSave": "afterDelay", "files.autoSave": "afterDelay",
"git.confirmSync": false, "git.confirmSync": false,
"workbench.startupEditor": "newUntitledFile", "workbench.startupEditor": "newUntitledFile",

View File

@@ -6,7 +6,7 @@
## introduce ## introduce
The lite version is based on the shelf extracted from [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin), which contains the main functions and is more suitable for actual project development, the packaged size is only `3MB` The lite version is based on the shelf extracted from [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin), which contains the main functions and is more suitable for actual project development, the packaged size is only `3MB`, and will permanently sync the full version of the code
## Supporting Video ## Supporting Video
@@ -15,7 +15,9 @@ The lite version is based on the shelf extracted from [vue-pure-admin](https://g
## Docs ## Docs
- [Click Watch Docs](https://pure-admin-doc.vercel.app) - [Click me to view the domestic documentation site](http://yiming_chang.gitee.io/pure-admin-doc)
- [Click me to view foreign document site 1](https://xiaoxian521.github.io/pure-admin-doc)
- [Click me to view foreign document site 2](https://pure-admin-doc.vercel.app)
## Usage ## Usage

View File

@@ -6,7 +6,7 @@
## 介绍 ## 介绍
精简版是基于[vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin)提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小仅 `3MB` 精简版是基于 [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin) 提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小仅 `3MB`,并且会永久同步完整版的代码
## 配套视频 ## 配套视频
@@ -15,7 +15,9 @@
## 配套文档 ## 配套文档
- [点我查看文档](https://pure-admin-doc.vercel.app) - [点我查看国内文档](http://yiming_chang.gitee.io/pure-admin-doc)
- [点我查看国外文档站 1](https://xiaoxian521.github.io/pure-admin-doc)
- [点我查看国外文档站 2](https://pure-admin-doc.vercel.app)
## 维护者 ## 维护者
@@ -29,9 +31,9 @@
## QQ 交流群 ## QQ 交流群
一群已满,下面是二群,群里严禁`黄``赌``毒``vpn`等违法行为! 一群已满,下面是二群,群里严禁 `黄``赌``毒``vpn` 等违法行为!
<img src="https://pure-admin-doc.vercel.app/img/support/qq.png" width="150px" height="225px" /> <img src="http://yiming_chang.gitee.io/pure-admin-doc/img/support/qq.png" width="150px" height="225px" />
## 用法 ## 用法
@@ -47,12 +49,12 @@ pnpm add 包名
pnpm remove 包名 pnpm remove 包名
我认为你应该先 fork 项目去开发,以便我更新时您可以同步拉取更新!!! 我认为你应该先 `fork` 项目去开发,以便我更新时您可以同步拉取更新!!!
## ⚠️ 注意 ## ⚠️ 注意
- 精简版不接受任何 issues 和 pr,如果有问题请到完整版 https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose 去提,谢谢!!! - 精简版不接受任何 `issues``pr`,如果有问题请到完整版 [issues](https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose) 去提,谢谢!!!
- 不要使用`delete-i18n`分支代码,这个分支只是给你们完全删除国际化的参考,不会同步代码的!!![完全删除国际化教程](https://www.bilibili.com/video/BV1Ru411B7k3/),请务必使用`main`分支的代码!!! - 不要使用 `delete-i18n` 分支代码,这个分支只是给你们完全删除国际化的参考,不会同步代码的!!! [完全删除国际化教程](https://www.bilibili.com/video/BV1Ru411B7k3/) ,请务必使用 `main` 分支的代码!!!
## 许可证 ## 许可证

View File

@@ -1,5 +1,4 @@
import { resolve } from "path"; import { resolve } from "path";
import Unocss from "unocss/vite";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { viteBuildInfo } from "./info"; import { viteBuildInfo } from "./info";
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
@@ -27,7 +26,6 @@ export function getPluginsList(command, VITE_LEGACY) {
}), }),
// jsx、tsx语法支持 // jsx、tsx语法支持
vueJsx(), vueJsx(),
Unocss(),
DefineOptions(), DefineOptions(),
// 线上环境删除console // 线上环境删除console
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }), removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),

View File

@@ -33,40 +33,7 @@ status:
login: login:
username: Username username: Username
password: Password password: Password
verifyCode: VerifyCode
remember: Remember Password
sure: Sure Password
forget: Forget Password?
login: Login login: Login
thirdLogin: Third Login
phoneLogin: Phone Login
qRCodeLogin: QRCode Login
register: Register
weChatLogin: WeChat Login
alipayLogin: Alipay Login
qqLogin: QQ Login
weiboLogin: Weibo Login
phone: Phone
smsVerifyCode: SMS VerifyCode
back: Back
test: Mock Test
tip: After scanning the code, click "Confirm" to complete the login
definite: Definite
loginSuccess: Login Success
registerSuccess: Regist Success
tickPrivacy: Please tick Privacy Policy
readAccept: I have read it carefully and accept
privacyPolicy: Privacy Policy
getVerifyCode: Get VerifyCode
info: Seconds
usernameReg: Please enter username usernameReg: Please enter username
passwordReg: Please enter password passwordReg: Please enter password
verifyCodeReg: Please enter verify code
verifyCodeCorrectReg: Please enter correct verify code
verifyCodeSixReg: Please enter a 6-digit verify code
phoneReg: Please enter the phone
phoneCorrectReg: Please enter the correct phone number format
passwordRuleReg: The password format should be any combination of 8-18 digits passwordRuleReg: The password format should be any combination of 8-18 digits
passwordSureReg: Please enter confirm password
passwordDifferentReg: The two passwords do not match!
passwordUpdateReg: Password has been updated

View File

@@ -33,40 +33,7 @@ status:
login: login:
username: 账号 username: 账号
password: 密码 password: 密码
verifyCode: 验证码
remember: 记住密码
sure: 确认密码
forget: 忘记密码?
login: 登录 login: 登录
thirdLogin: 第三方登录
phoneLogin: 手机登录
qRCodeLogin: 二维码登录
register: 注册
weChatLogin: 微信登录
alipayLogin: 支付宝登录
qqLogin: QQ登录
weiboLogin: 微博登录
phone: 手机号码
smsVerifyCode: 短信验证码
back: 返回
test: 模拟测试
tip: 扫码后点击"确认",即可完成登录
definite: 确定
loginSuccess: 登录成功
registerSuccess: 注册成功
tickPrivacy: 请勾选隐私政策
readAccept: 我已仔细阅读并接受
privacyPolicy: 《隐私政策》
getVerifyCode: 获取验证码
info: 秒后重新获取
usernameReg: 请输入账号 usernameReg: 请输入账号
passwordReg: 请输入密码 passwordReg: 请输入密码
verifyCodeReg: 请输入验证码
verifyCodeCorrectReg: 请输入正确的验证码
verifyCodeSixReg: 请输入6位数字验证码
phoneReg: 请输入手机号码
phoneCorrectReg: 请输入正确的手机号码格式
passwordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合 passwordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合
passwordSureReg: 请输入确认密码
passwordDifferentReg: 两次密码不一致!
passwordUpdateReg: 修改密码成功

View File

@@ -1,20 +1,20 @@
{ {
"name": "pure-admin-thin", "name": "pure-admin-thin",
"version": "3.4.5", "version": "3.5.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "cross-env --max_old_space_size=4096 vite", "dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
"serve": "pnpm dev", "serve": "pnpm dev",
"build": "rimraf dist && cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build", "build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 vite build",
"build:staging": "rimraf dist && cross-env vite build --mode staging", "build:staging": "rimraf dist && vite build --mode staging",
"report": "rimraf dist && cross-env vite build", "report": "rimraf dist && vite build",
"preview": "vite preview", "preview": "vite preview",
"preview:build": "pnpm build && vite preview", "preview:build": "pnpm build && vite preview",
"typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck", "typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck",
"cloc": "cross-env --max_old_space_size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML", "cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install", "clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", "lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged", "lint:pretty": "pretty-quick --staged",
@@ -33,15 +33,14 @@
"@pureadmin/descriptions": "^1.1.0", "@pureadmin/descriptions": "^1.1.0",
"@pureadmin/table": "^1.2.0", "@pureadmin/table": "^1.2.0",
"@pureadmin/utils": "^0.1.1", "@pureadmin/utils": "^0.1.1",
"@vueuse/core": "^9.1.0", "@vueuse/core": "^9.1.1",
"@vueuse/motion": "^2.0.0-beta.12", "@vueuse/motion": "^2.0.0-beta.12",
"@vueuse/shared": "^9.1.0", "@vueuse/shared": "^9.1.1",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.27.2", "axios": "^0.27.2",
"css-color-function": "^1.3.3",
"dayjs": "^1.11.4", "dayjs": "^1.11.4",
"echarts": "^5.3.3", "echarts": "^5.3.3",
"element-plus": "^2.2.14", "element-plus": "^2.2.16",
"element-resize-detector": "^1.2.3", "element-resize-detector": "^1.2.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -51,17 +50,15 @@
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.0.19", "pinia": "^2.0.21",
"qrcode": "^1.5.1",
"qs": "^6.11.0", "qs": "^6.11.0",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^2.1.0", "responsive-storage": "^2.1.0",
"rgb-hex": "^4.0.0", "vue": "^3.2.39",
"vue": "^3.2.37",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-router": "^4.1.3", "vue-router": "^4.1.5",
"vue-types": "^4.2.1", "vue-types": "^4.2.1",
"vxe-table": "^4.3.0-beta.5", "vxe-table": "^4.3.2",
"xe-utils": "^3.5.6" "xe-utils": "^3.5.6"
}, },
"devDependencies": { "devDependencies": {
@@ -83,15 +80,15 @@
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2", "@typescript-eslint/parser": "^5.10.2",
"@vitejs/plugin-legacy": "^2.0.0", "@vitejs/plugin-legacy": "^2.1.0",
"@vitejs/plugin-vue": "^3.0.1", "@vitejs/plugin-vue": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^2.0.0", "@vitejs/plugin-vue-jsx": "^2.0.1",
"@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"@vue/runtime-core": "^3.2.37", "@vue/runtime-core": "^3.2.39",
"autoprefixer": "^10.4.8", "autoprefixer": "^10.4.8",
"cloc": "^2.10.0", "cloc": "^2.10.0",
"cross-env": "7.0.3", "cssnano": "^5.1.13",
"eslint": "^8.8.0", "eslint": "^8.8.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.4.1", "eslint-plugin-vue": "^8.4.1",
@@ -101,12 +98,12 @@
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"postcss": "^8.4.16", "postcss": "^8.4.16",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-import": "^14.1.0", "postcss-import": "^15.0.0",
"postcss-scss": "^4.0.4", "postcss-scss": "^4.0.4",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"pretty-quick": "3.1.1", "pretty-quick": "3.1.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"rollup-plugin-visualizer": "^5.7.1", "rollup-plugin-visualizer": "^5.8.1",
"sass": "^1.53.0", "sass": "^1.53.0",
"sass-loader": "^13.0.2", "sass-loader": "^13.0.2",
"stylelint": "^14.3.0", "stylelint": "^14.3.0",
@@ -115,21 +112,21 @@
"stylelint-config-recommended": "^6.0.0", "stylelint-config-recommended": "^6.0.0",
"stylelint-config-standard": "^24.0.0", "stylelint-config-standard": "^24.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"tailwindcss": "^3.1.8",
"terser": "^5.15.0",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"unocss": "^0.45.9", "unplugin-vue-define-options": "0.7.3",
"unplugin-vue-define-options": "^0.7.3", "vite": "^3.1.0",
"vite": "^3.0.9",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-remove-console": "^1.1.0", "vite-plugin-remove-console": "^1.1.0",
"vite-svg-loader": "^3.4.0", "vite-svg-loader": "^3.4.0",
"vue-eslint-parser": "^8.2.0", "vue-eslint-parser": "^8.2.0",
"vue-tsc": "^0.40.1" "vue-tsc": "^0.40.7"
}, },
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {
"ignoreMissing": [ "ignoreMissing": [
"rollup", "rollup",
"terser",
"webpack" "webpack"
] ]
} }

2041
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,8 @@
module.exports = { module.exports = {
plugins: [require("autoprefixer"), require("postcss-import")] plugins: {
"postcss-import": {},
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
}
}; };

View File

@@ -1,5 +1,5 @@
{ {
"Version": "3.4.5", "Version": "3.5.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,

View File

@@ -1,7 +0,0 @@
import reImageVerify from "./src/index.vue";
import { withInstall } from "@pureadmin/utils";
/** 图形验证码组件 */
export const ReImageVerify = withInstall(reImageVerify);
export default ReImageVerify;

View File

@@ -1,85 +0,0 @@
import { ref, onMounted } from "vue";
/**
* 绘制图形验证码
* @param width - 图形宽度
* @param height - 图形高度
*/
export const useImageVerify = (width = 120, height = 40) => {
const domRef = ref<HTMLCanvasElement>();
const imgCode = ref("");
function setImgCode(code: string) {
imgCode.value = code;
}
function getImgCode() {
if (!domRef.value) return;
imgCode.value = draw(domRef.value, width, height);
}
onMounted(() => {
getImgCode();
});
return {
domRef,
imgCode,
setImgCode,
getImgCode
};
};
function randomNum(min: number, max: number) {
const num = Math.floor(Math.random() * (max - min) + min);
return num;
}
function randomColor(min: number, max: number) {
const r = randomNum(min, max);
const g = randomNum(min, max);
const b = randomNum(min, max);
return `rgb(${r},${g},${b})`;
}
function draw(dom: HTMLCanvasElement, width: number, height: number) {
let imgCode = "";
const NUMBER_STRING = "0123456789";
const ctx = dom.getContext("2d");
if (!ctx) return imgCode;
ctx.fillStyle = randomColor(180, 230);
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < 4; i += 1) {
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)];
imgCode += text;
const fontSize = randomNum(18, 41);
const deg = randomNum(-30, 30);
ctx.font = `${fontSize}px Simhei`;
ctx.textBaseline = "top";
ctx.fillStyle = randomColor(80, 150);
ctx.save();
ctx.translate(30 * i + 15, 15);
ctx.rotate((deg * Math.PI) / 180);
ctx.fillText(text, -15 + 5, -15);
ctx.restore();
}
for (let i = 0; i < 5; i += 1) {
ctx.beginPath();
ctx.moveTo(randomNum(0, width), randomNum(0, height));
ctx.lineTo(randomNum(0, width), randomNum(0, height));
ctx.strokeStyle = randomColor(180, 230);
ctx.closePath();
ctx.stroke();
}
for (let i = 0; i < 41; i += 1) {
ctx.beginPath();
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = randomColor(150, 200);
ctx.fill();
}
return imgCode;
}

View File

@@ -1,46 +0,0 @@
<script setup lang="ts">
import { watch } from "vue";
import { useImageVerify } from "./hooks";
defineOptions({
name: "ReImageVerify"
});
interface Props {
code?: string;
}
interface Emits {
(e: "update:code", code: string): void;
}
const props = withDefaults(defineProps<Props>(), {
code: ""
});
const emit = defineEmits<Emits>();
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify();
watch(
() => props.code,
newValue => {
setImgCode(newValue);
}
);
watch(imgCode, newValue => {
emit("update:code", newValue);
});
defineExpose({ getImgCode });
</script>
<template>
<canvas
ref="domRef"
width="120"
height="40"
class="cursor-pointer"
@click="getImgCode"
/>
</template>

View File

@@ -1,7 +0,0 @@
import reQrcode from "./src/index";
import { withInstall } from "@pureadmin/utils";
/** 二维码组件 */
export const ReQrcode = withInstall(reQrcode);
export default ReQrcode;

View File

@@ -1,8 +0,0 @@
.qrcode {
&--disabled {
background: rgba(255, 255, 255, 0.95);
& > div {
transform: translate(-50%, -50%);
}
}
}

View File

@@ -1,262 +0,0 @@
import {
ref,
unref,
watch,
nextTick,
computed,
PropType,
defineComponent
} from "vue";
import "./index.scss";
import { cloneDeep } from "lodash-unified";
import { isString } from "@pureadmin/utils";
import { propTypes } from "/@/utils/propTypes";
import { IconifyIconOffline } from "../../ReIcon";
import QRCode, { QRCodeRenderersOptions } from "qrcode";
interface QrcodeLogo {
src?: string;
logoSize?: number;
bgColor?: string;
borderSize?: number;
crossOrigin?: string;
borderRadius?: number;
logoRadius?: number;
}
const props = {
// img 或者 canvas,img不支持logo嵌套
tag: propTypes.string
.validate((v: string) => ["canvas", "img"].includes(v))
.def("canvas"),
// 二维码内容
text: {
type: [String, Array] as PropType<string | Recordable[]>,
default: null
},
// qrcode.js配置项
options: {
type: Object as PropType<QRCodeRenderersOptions>,
default: (): QRCodeRenderersOptions => ({})
},
// 宽度
width: propTypes.number.def(200),
// logo
logo: {
type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
default: (): QrcodeLogo | string => ""
},
// 是否过期
disabled: propTypes.bool.def(false),
// 过期提示内容
disabledText: propTypes.string.def("")
};
export default defineComponent({
name: "ReQrcode",
props,
emits: ["done", "click", "disabled-click"],
setup(props, { emit }) {
const { toCanvas, toDataURL } = QRCode;
const loading = ref(true);
const wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null);
const renderText = computed(() => String(props.text));
const wrapStyle = computed(() => {
return {
width: props.width + "px",
height: props.width + "px"
};
});
const initQrcode = async () => {
await nextTick();
const options = cloneDeep(props.options || {});
if (props.tag === "canvas") {
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel =
options.errorCorrectionLevel ||
getErrorCorrectionLevel(unref(renderText));
const _width: number = await getOriginWidth(unref(renderText), options);
options.scale =
props.width === 0 ? undefined : (props.width / _width) * 4;
const canvasRef: any = await toCanvas(
unref(wrapRef) as HTMLCanvasElement,
unref(renderText),
options
);
if (props.logo) {
const url = await createLogoCode(canvasRef);
emit("done", url);
loading.value = false;
} else {
emit("done", canvasRef.toDataURL());
loading.value = false;
}
} else {
const url = await toDataURL(renderText.value, {
errorCorrectionLevel: "H",
width: props.width,
...options
});
(unref(wrapRef) as HTMLImageElement).src = url;
emit("done", url);
loading.value = false;
}
};
watch(
() => renderText.value,
val => {
if (!val) return;
initQrcode();
},
{
deep: true,
immediate: true
}
);
const createLogoCode = (canvasRef: HTMLCanvasElement) => {
const canvasWidth = canvasRef.width;
const logoOptions: QrcodeLogo = Object.assign(
{
logoSize: 0.15,
bgColor: "#ffffff",
borderSize: 0.05,
crossOrigin: "anonymous",
borderRadius: 8,
logoRadius: 0
},
isString(props.logo) ? {} : props.logo
);
const {
logoSize = 0.15,
bgColor = "#ffffff",
borderSize = 0.05,
crossOrigin = "anonymous",
borderRadius = 8,
logoRadius = 0
} = logoOptions;
const logoSrc = isString(props.logo) ? props.logo : props.logo.src;
const logoWidth = canvasWidth * logoSize;
const logoXY = (canvasWidth * (1 - logoSize)) / 2;
const logoBgWidth = canvasWidth * (logoSize + borderSize);
const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2;
const ctx = canvasRef.getContext("2d");
if (!ctx) return;
// logo 底色
canvasRoundRect(ctx)(
logoBgXY,
logoBgXY,
logoBgWidth,
logoBgWidth,
borderRadius
);
ctx.fillStyle = bgColor;
ctx.fill();
// logo
const image = new Image();
if (crossOrigin || logoRadius) {
image.setAttribute("crossOrigin", crossOrigin);
}
(image as any).src = logoSrc;
// 使用image绘制可以避免某些跨域情况
const drawLogoWithImage = (image: HTMLImageElement) => {
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
};
// 使用canvas绘制以获得更多的功能
const drawLogoWithCanvas = (image: HTMLImageElement) => {
const canvasImage = document.createElement("canvas");
canvasImage.width = logoXY + logoWidth;
canvasImage.height = logoXY + logoWidth;
const imageCanvas = canvasImage.getContext("2d");
if (!imageCanvas || !ctx) return;
imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius);
if (!ctx) return;
const fillStyle = ctx.createPattern(canvasImage, "no-repeat");
if (fillStyle) {
ctx.fillStyle = fillStyle;
ctx.fill();
}
};
// 将 logo绘制到 canvas上
return new Promise((resolve: any) => {
image.onload = () => {
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image);
resolve(canvasRef.toDataURL());
};
});
};
// 得到原QrCode的大小以便缩放得到正确的QrCode大小
const getOriginWidth = async (
content: string,
options: QRCodeRenderersOptions
) => {
const _canvas = document.createElement("canvas");
await toCanvas(_canvas, content, options);
return _canvas.width;
};
// 对于内容少的QrCode增大容错率
const getErrorCorrectionLevel = (content: string) => {
if (content.length > 36) {
return "M";
} else if (content.length > 16) {
return "Q";
} else {
return "H";
}
};
// 用于绘制圆角
const canvasRoundRect = (ctx: CanvasRenderingContext2D) => {
return (x: number, y: number, w: number, h: number, r: number) => {
const minSize = Math.min(w, h);
if (r > minSize / 2) {
r = minSize / 2;
}
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
return ctx;
};
};
const clickCode = () => {
emit("click");
};
const disabledClick = () => {
emit("disabled-click");
};
return () => (
<>
<div
v-loading={unref(loading)}
class="qrcode relative inline-block"
style={unref(wrapStyle)}
>
{props.tag === "canvas" ? (
<canvas ref={wrapRef} onClick={clickCode}></canvas>
) : (
<img ref={wrapRef} onClick={clickCode}></img>
)}
{props.disabled && (
<div
class="qrcode--disabled absolute top-0 left-0 flex w-full h-full items-center justify-center"
onClick={disabledClick}
>
<div class="absolute top-[50%] left-[50%] font-bold">
<IconifyIconOffline
class="cursor-pointer"
icon="refresh-right"
width="30"
color="var(--el-color-primary)"
/>
<div>{props.disabledText}</div>
</div>
</div>
)}
</div>
</>
);
}
});

View File

@@ -55,13 +55,13 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click"> <el-dropdown id="header-translation" trigger="click">
<globalization <globalization
class="navbar-bg-hover w-40px h-48px p-11px cursor-pointer outline-none" class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
/> />
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="translation"> <el-dropdown-menu class="translation">
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')" :style="getDropdownItemStyle(locale, 'zh')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'zh')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh" @click="translationCh"
> >
<IconifyIconOffline <IconifyIconOffline
@@ -73,7 +73,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')" :style="getDropdownItemStyle(locale, 'en')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span class="check-en" v-show="locale === 'en'">
@@ -88,7 +88,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover"> <span class="el-dropdown-link navbar-bg-hover">
<img v-if="avatars" :src="avatars" :style="avatarsStyle" /> <img v-if="avatars" :src="avatars" :style="avatarsStyle" />
<p v-if="username" class="dark:color-white">{{ username }}</p> <p v-if="username" class="dark:text-white">{{ username }}</p>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="logout"> <el-dropdown-menu class="logout">

View File

@@ -45,7 +45,7 @@ function hoverDescription(event, description) {
<template> <template>
<div <div
class="notice-container border-b-1 border-[#f0f0f0] dark:border-[#303030]" class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"
> >
<el-avatar <el-avatar
v-if="props.noticeItem.avatar" v-if="props.noticeItem.avatar"
@@ -54,7 +54,7 @@ function hoverDescription(event, description) {
class="notice-container-avatar" class="notice-container-avatar"
/> />
<div class="notice-container-text"> <div class="notice-container-text">
<div class="notice-text-title color-[#000000d9] dark:color-white"> <div class="notice-text-title text-[#000000d9] dark:text-white">
<el-tooltip <el-tooltip
popper-class="notice-title-popper" popper-class="notice-title-popper"
:disabled="!titleTooltip" :disabled="!titleTooltip"
@@ -93,7 +93,7 @@ function hoverDescription(event, description) {
{{ props.noticeItem.description }} {{ props.noticeItem.description }}
</div> </div>
</el-tooltip> </el-tooltip>
<div class="notice-text-datetime color-[#00000073] dark:color-white"> <div class="notice-text-datetime text-[#00000073] dark:text-white">
{{ props.noticeItem.datetime }} {{ props.noticeItem.datetime }}
</div> </div>
</div> </div>

View File

@@ -21,12 +21,14 @@ emitter.on("openPanel", () => {
<div ref="target" class="right-panel bg-white dark:bg-dark"> <div ref="target" class="right-panel bg-white dark:bg-dark">
<div class="right-panel-items"> <div class="right-panel-items">
<div class="project-configuration"> <div class="project-configuration">
<h3>项目配置</h3> <h3 class="dark:text-white">项目配置</h3>
<el-icon title="关闭配置" class="el-icon-close" @click="show = !show"> <el-icon title="关闭配置" class="el-icon-close" @click="show = !show">
<IconifyIconOffline icon="close" /> <IconifyIconOffline icon="close" />
</el-icon> </el-icon>
</div> </div>
<div class="border-b-1 border-[#dcdfe6] dark:border-[#303030]" /> <div
class="border-b-[1px] border-solid border-[#dcdfe6] dark:border-[#303030]"
/>
<slot /> <slot />
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@ const { isFullscreen, toggle } = useFullscreen();
<template> <template>
<div <div
class="screen-full w-36px h-48px flex-ac cursor-pointer navbar-bg-hover" class="screen-full w-[36px] h-[48px] flex-ac cursor-pointer navbar-bg-hover"
@click="toggle" @click="toggle"
> >
<FontIcon <FontIcon

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="search-footer color-[#333] dark:color-white"> <div class="search-footer text-[#333] dark:text-white">
<span class="search-footer-item"> <span class="search-footer-item">
<enterOutlined class="icon" /> <enterOutlined class="icon" />
确认 确认

View File

@@ -9,7 +9,7 @@ function handleSearch() {
<template> <template>
<div <div
class="search-container w-40px h-48px flex-c cursor-pointer navbar-bg-hover" class="search-container w-[40px] h-[48px] flex-c cursor-pointer navbar-bg-hover"
@click="handleSearch" @click="handleSearch"
> >
<IconifyIconOffline icon="search" /> <IconifyIconOffline icon="search" />

View File

@@ -309,7 +309,7 @@ nextTick(() => {
<el-divider>界面显示</el-divider> <el-divider>界面显示</el-divider>
<ul class="setting"> <ul class="setting">
<li> <li>
<span>灰色模式</span> <span class="dark:text-white">灰色模式</span>
<el-switch <el-switch
v-model="settings.greyVal" v-model="settings.greyVal"
inline-prompt inline-prompt
@@ -320,7 +320,7 @@ nextTick(() => {
/> />
</li> </li>
<li> <li>
<span>色弱模式</span> <span class="dark:text-white">色弱模式</span>
<el-switch <el-switch
v-model="settings.weakVal" v-model="settings.weakVal"
inline-prompt inline-prompt
@@ -331,7 +331,7 @@ nextTick(() => {
/> />
</li> </li>
<li> <li>
<span>隐藏标签页</span> <span class="dark:text-white">隐藏标签页</span>
<el-switch <el-switch
v-model="settings.tabsVal" v-model="settings.tabsVal"
inline-prompt inline-prompt
@@ -342,7 +342,7 @@ nextTick(() => {
/> />
</li> </li>
<li> <li>
<span>侧边栏Logo</span> <span class="dark:text-white">侧边栏Logo</span>
<el-switch <el-switch
v-model="logoVal" v-model="logoVal"
inline-prompt inline-prompt
@@ -355,7 +355,7 @@ nextTick(() => {
/> />
</li> </li>
<li> <li>
<span>标签页持久化</span> <span class="dark:text-white">标签页持久化</span>
<el-switch <el-switch
v-model="settings.multiTagsCache" v-model="settings.multiTagsCache"
inline-prompt inline-prompt
@@ -367,7 +367,7 @@ nextTick(() => {
</li> </li>
<li> <li>
<span>标签风格</span> <span class="dark:text-white">标签风格</span>
<el-radio-group v-model="markValue" size="small" @change="onChange"> <el-radio-group v-model="markValue" size="small" @change="onChange">
<el-radio label="card">卡片</el-radio> <el-radio label="card">卡片</el-radio>
<el-radio label="smart">灵动</el-radio> <el-radio label="smart">灵动</el-radio>

View File

@@ -67,13 +67,13 @@ watch(
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click"> <el-dropdown id="header-translation" trigger="click">
<globalization <globalization
class="navbar-bg-hover w-40px h-48px p-11px cursor-pointer outline-none" class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
/> />
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="translation"> <el-dropdown-menu class="translation">
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')" :style="getDropdownItemStyle(locale, 'zh')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'zh')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh" @click="translationCh"
> >
<span class="check-zh" v-show="locale === 'zh'"> <span class="check-zh" v-show="locale === 'zh'">
@@ -83,7 +83,7 @@ watch(
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')" :style="getDropdownItemStyle(locale, 'en')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span class="check-en" v-show="locale === 'en'">
@@ -98,7 +98,7 @@ watch(
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover"> <span class="el-dropdown-link navbar-bg-hover">
<img v-if="avatars" :src="avatars" :style="avatarsStyle" /> <img v-if="avatars" :src="avatars" :style="avatarsStyle" />
<p v-if="username" class="dark:color-white">{{ username }}</p> <p v-if="username" class="dark:text-white">{{ username }}</p>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="logout"> <el-dropdown-menu class="logout">

View File

@@ -28,7 +28,7 @@ const toggleClick = () => {
> >
<IconifyIconOffline <IconifyIconOffline
:icon="props.isActive ? 'menu-fold' : 'menu-unfold'" :icon="props.isActive ? 'menu-fold' : 'menu-unfold'"
class="cursor-pointer inline-block align-middle color-primary hover:color-primary !dark:hover:color-white w-16px h-16px ml-4 mb-1" class="cursor-pointer inline-block align-middle text-primary hover:text-primary dark:hover:!text-white w-[16px] h-[16px] ml-4 mb-1"
@click="toggleClick" @click="toggleClick"
/> />
</el-tooltip> </el-tooltip>

View File

@@ -99,13 +99,13 @@ watch(
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click"> <el-dropdown id="header-translation" trigger="click">
<globalization <globalization
class="navbar-bg-hover w-40px h-48px p-11px cursor-pointer outline-none" class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
/> />
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="translation"> <el-dropdown-menu class="translation">
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')" :style="getDropdownItemStyle(locale, 'zh')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'zh')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh" @click="translationCh"
> >
<span class="check-zh" v-show="locale === 'zh'"> <span class="check-zh" v-show="locale === 'zh'">
@@ -115,7 +115,7 @@ watch(
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')" :style="getDropdownItemStyle(locale, 'en')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span class="check-en" v-show="locale === 'en'">
@@ -130,7 +130,7 @@ watch(
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover"> <span class="el-dropdown-link navbar-bg-hover">
<img v-if="avatars" :src="avatars" :style="avatarsStyle" /> <img v-if="avatars" :src="avatars" :style="avatarsStyle" />
<p v-if="username" class="dark:color-white">{{ username }}</p> <p v-if="username" class="dark:text-white">{{ username }}</p>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="logout"> <el-dropdown-menu class="logout">

View File

@@ -24,7 +24,7 @@ const toggleClick = () => {
> >
<IconifyIconOffline <IconifyIconOffline
:icon="props.isActive ? 'menu-fold' : 'menu-unfold'" :icon="props.isActive ? 'menu-fold' : 'menu-unfold'"
class="inline-block align-middle hover:color-primary !dark:hover:color-white" class="inline-block align-middle hover:text-primary dark:hover:!text-white"
/> />
</div> </div>
</template> </template>

View File

@@ -9,7 +9,6 @@ import { useResizeObserver, useDebounceFn } from "@vueuse/core";
import { useSettingStoreHook } from "/@/store/modules/settings"; import { useSettingStoreHook } from "/@/store/modules/settings";
import { handleAliveRoute, delAliveRoutes } from "/@/router/utils"; import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue"; import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
const { const {
@@ -276,7 +275,6 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
startIndex: 1, startIndex: 1,
length: multiTags.value.length length: multiTags.value.length
}); });
usePermissionStoreHook().clearAllCachePage();
router.push("/welcome"); router.push("/welcome");
break; break;
} }
@@ -492,7 +490,7 @@ onMounted(() => {
> >
<router-link <router-link
:to="item.path" :to="item.path"
class="!dark:color-text_color_primary !dark:hover:color-primary" class="dark:!text-text_color_primary dark:hover:!text-primary"
> >
{{ transformI18n(item.meta.title) }} {{ transformI18n(item.meta.title) }}
</router-link> </router-link>
@@ -557,7 +555,7 @@ onMounted(() => {
placement="bottom-end" placement="bottom-end"
@command="handleCommand" @command="handleCommand"
> >
<IconifyIconOffline icon="arrow-down" class="dark:color-white" /> <IconifyIconOffline icon="arrow-down" class="dark:text-white" />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item

View File

@@ -34,7 +34,7 @@ export function useNav() {
const getDropdownItemClass = computed(() => { const getDropdownItemClass = computed(() => {
return (locale, t) => { return (locale, t) => {
return locale === t ? "" : "!dark:hover:color-primary"; return locale === t ? "" : "dark:hover:!text-primary";
}; };
}); });

View File

@@ -144,8 +144,8 @@ const layoutHeader = defineComponent({
{ {
default: () => [ default: () => [
!pureSetting.hiddenSideBar !pureSetting.hiddenSideBar
? h(fullScreen, { class: "dark:color-white" }) ? h(fullScreen, { class: "dark:text-white" })
: h(exitScreen, { class: "dark:color-white" }) : h(exitScreen, { class: "dark:text-white" })
] ]
} }
) )

View File

@@ -13,7 +13,6 @@ import { injectResponsiveStorage } from "/@/utils/responsive";
// import Table from "@pureadmin/table"; // import Table from "@pureadmin/table";
// import PureDescriptions from "@pureadmin/descriptions"; // import PureDescriptions from "@pureadmin/descriptions";
import "uno.css";
import "animate.css"; import "animate.css";
// 引入重置样式 // 引入重置样式
import "./style/reset.scss"; import "./style/reset.scss";

View File

@@ -81,6 +81,7 @@ export function resetRouter() {
); );
} }
}); });
usePermissionStoreHook().clearAllCachePage();
} }
// 路由白名单 // 路由白名单

View File

@@ -230,16 +230,21 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
// 将backstage属性加入meta标识此路由为后端返回路由 // 将backstage属性加入meta标识此路由为后端返回路由
v.meta.backstage = true; v.meta.backstage = true;
// 父级的redirect属性取值如果子级存在且父级的redirect属性不存在默认取第一个子级的path如果子级存在且父级的redirect属性存在取存在的redirect属性会覆盖默认值 // 父级的redirect属性取值如果子级存在且父级的redirect属性不存在默认取第一个子级的path如果子级存在且父级的redirect属性存在取存在的redirect属性会覆盖默认值
if (v?.children && !v.redirect) v.redirect = v.children[0].path; if (v?.children && v.children.length && !v.redirect)
v.redirect = v.children[0].path;
// 父级的name属性取值如果子级存在且父级的name属性不存在默认取第一个子级的name如果子级存在且父级的name属性存在取存在的name属性会覆盖默认值 // 父级的name属性取值如果子级存在且父级的name属性不存在默认取第一个子级的name如果子级存在且父级的name属性存在取存在的name属性会覆盖默认值
if (v?.children && !v.name) v.name = v.children[0].name; if (v?.children && v.children.length && !v.name)
if (v.meta?.frameSrc) v.component = IFrame; v.name = v.children[0].name;
// 对后端传component组件路径和不传做兼容如果后端传component组件路径那么path可以随便写如果不传component组件路径会跟path保持一致 if (v.meta?.frameSrc) {
const index = v?.component v.component = IFrame;
? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any)) } else {
: modulesRoutesKeys.findIndex(ev => ev.includes(v.path)); // 对后端传component组件路径和不传做兼容如果后端传component组件路径那么path可以随便写如果不传component组件路径会跟path保持一致
v.component = modulesRoutes[modulesRoutesKeys[index]]; const index = v?.component
if (v.children) { ? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any))
: modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
v.component = modulesRoutes[modulesRoutesKeys[index]];
}
if (v?.children && v.children.length) {
addAsyncRoutes(v.children); addAsyncRoutes(v.children);
} }
}); });

View File

@@ -63,6 +63,9 @@ export const usePermissionStore = defineStore({
}, },
// 清空缓存页面 // 清空缓存页面
clearAllCachePage() { clearAllCachePage() {
this.wholeMenus = [];
this.menusTree = [];
this.buttonAuth = [];
this.cachePageList = []; this.cachePageList = [];
} }
} }

View File

@@ -23,11 +23,7 @@ export const useUserStore = defineStore({
id: "pure-user", id: "pure-user",
state: (): userType => ({ state: (): userType => ({
token, token,
name, name
// 前端生成的验证码(按实际需求替换)
verifyCode: "",
// 登录显示组件判断 0登录 1手机登录 2二维码登录 3注册 4忘记密码默认0登录
currentPage: 0
}), }),
actions: { actions: {
SET_TOKEN(token) { SET_TOKEN(token) {
@@ -36,12 +32,6 @@ export const useUserStore = defineStore({
SET_NAME(name) { SET_NAME(name) {
this.name = name; this.name = name;
}, },
SET_VERIFYCODE(verifyCode) {
this.verifyCode = verifyCode;
},
SET_CURRENTPAGE(value) {
this.currentPage = value;
},
// 登入 // 登入
async loginByUsername(data) { async loginByUsername(data) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
@@ -68,6 +58,7 @@ export const useUserStore = defineStore({
}, },
// 刷新token // 刷新token
async refreshToken(data) { async refreshToken(data) {
removeToken();
return refreshToken(data).then(data => { return refreshToken(data).then(data => {
if (data) { if (data) {
setToken(data); setToken(data);

View File

@@ -127,7 +127,8 @@ html.dark {
} }
.vxe-modal--title, .vxe-modal--title,
.vxe-button--content { .vxe-button--content,
.vxe-modal--header-title {
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
@@ -135,6 +136,10 @@ html.dark {
background: var(--el-color-primary) !important; background: var(--el-color-primary) !important;
} }
.vxe-button {
background-color: transparent;
}
/* 项目配置面板 */ /* 项目配置面板 */
.right-panel-items { .right-panel-items {
.el-divider__text { .el-divider__text {

View File

@@ -3,6 +3,7 @@
@import "./element-plus.scss"; @import "./element-plus.scss";
@import "./sidebar.scss"; @import "./sidebar.scss";
@import "./dark.scss"; @import "./dark.scss";
@import "./tailwind.css";
:root { :root {
--pure-transition-duration: 0.016s; --pure-transition-duration: 0.016s;

View File

@@ -182,7 +182,6 @@ button,
[type="reset"], [type="reset"],
[type="submit"] { [type="submit"] {
-webkit-appearance: button; /* 1 */ -webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */ background-image: none; /* 2 */
} }

View File

@@ -229,7 +229,7 @@
.screen-full, .screen-full,
/* 国际化 */ /* 国际化 */
.globalization, .globalization,
/* */ /* */
.el-dropdown-link, .el-dropdown-link,
/* 设置 */ /* 设置 */
.el-icon-setting { .el-icon-setting {
@@ -592,7 +592,7 @@ body[layout="vertical"] {
.screen-full, .screen-full,
/* 国际化 */ /* 国际化 */
.globalization, .globalization,
/* */ /* */
.el-dropdown-link, .el-dropdown-link,
/* 设置 */ /* 设置 */
.el-icon-setting { .el-icon-setting {

29
src/style/tailwind.css Normal file
View File

@@ -0,0 +1,29 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.bg-dark {
@apply bg-bg_color;
}
.wh-full {
@apply w-full h-full;
}
.flex-c {
@apply flex justify-center items-center;
}
.flex-ac {
@apply flex justify-around items-center;
}
.flex-bc {
@apply flex justify-between items-center;
}
.navbar-bg-hover {
@apply dark:text-white dark:hover:!bg-[#242424];
}
}

View File

@@ -7,7 +7,7 @@ defineOptions({
</script> </script>
<template> <template>
<div class="flex justify-center items-center h-screen-sm"> <div class="flex justify-center items-center h-[640px]">
<noAccess /> <noAccess />
<div class="ml-12"> <div class="ml-12">
<p <p

View File

@@ -7,7 +7,7 @@ defineOptions({
</script> </script>
<template> <template>
<div class="flex justify-center items-center h-screen-sm"> <div class="flex justify-center items-center h-[640px]">
<noExist /> <noExist />
<div class="ml-12"> <div class="ml-12">
<p <p

View File

@@ -7,7 +7,7 @@ defineOptions({
</script> </script>
<template> <template>
<div class="flex justify-center items-center h-screen-sm"> <div class="flex justify-center items-center h-[640px]">
<noServer /> <noServer />
<div class="ml-12"> <div class="ml-12">
<p <p

View File

@@ -1,106 +0,0 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { ref, reactive } from "vue";
import Motion from "../utils/motion";
import { phoneRules } from "../utils/rule";
import { message } from "@pureadmin/components";
import type { FormInstance } from "element-plus";
import { $t, transformI18n } from "/@/plugins/i18n";
import { useVerifyCode } from "../utils/verifyCode";
import { useUserStoreHook } from "/@/store/modules/user";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const { t } = useI18n();
const loading = ref(false);
const ruleForm = reactive({
phone: "",
verifyCode: ""
});
const ruleFormRef = ref<FormInstance>();
const { isDisabled, text } = useVerifyCode();
const onLogin = async (formEl: FormInstance | undefined) => {
loading.value = true;
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
// 模拟登录请求,需根据实际开发进行修改
setTimeout(() => {
message.success(transformI18n($t("login.loginSuccess")));
loading.value = false;
}, 2000);
} else {
loading.value = false;
return fields;
}
});
};
function onBack() {
useVerifyCode().end();
useUserStoreHook().SET_CURRENTPAGE(0);
}
</script>
<template>
<el-form ref="ruleFormRef" :model="ruleForm" :rules="phoneRules" size="large">
<Motion>
<el-form-item prop="phone">
<el-input
clearable
v-model="ruleForm.phone"
:placeholder="t('login.phone')"
:prefix-icon="useRenderIcon('iphone')"
/>
</el-form-item>
</Motion>
<Motion :delay="100">
<el-form-item prop="verifyCode">
<div class="w-full flex justify-between">
<el-input
clearable
v-model="ruleForm.verifyCode"
:placeholder="t('login.smsVerifyCode')"
:prefix-icon="
useRenderIcon('ri:shield-keyhole-line', { online: true })
"
/>
<el-button
:disabled="isDisabled"
class="ml-2"
@click="useVerifyCode().start(ruleFormRef, 'phone')"
>
{{
text.length > 0
? text + t("login.info")
: t("login.getVerifyCode")
}}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item>
<el-button
class="w-full"
size="default"
type="primary"
:loading="loading"
@click="onLogin(ruleFormRef)"
>
{{ t("login.login") }}
</el-button>
</el-form-item>
</Motion>
<Motion :delay="200">
<el-form-item>
<el-button class="w-full" size="default" @click="onBack">
{{ t("login.back") }}
</el-button>
</el-form-item>
</Motion>
</el-form>
</template>

View File

@@ -1,25 +0,0 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import Motion from "../utils/motion";
import ReQrcode from "/@/components/ReQrcode";
import { useUserStoreHook } from "/@/store/modules/user";
const { t } = useI18n();
</script>
<template>
<Motion class="-mt-2 -mb-2"> <ReQrcode :text="t('login.test')" /> </Motion>
<Motion :delay="100">
<el-divider>
<p class="text-gray-500 text-xs">{{ t("login.tip") }}</p>
</el-divider>
</Motion>
<Motion :delay="150">
<el-button
class="w-full mt-4"
@click="useUserStoreHook().SET_CURRENTPAGE(0)"
>
{{ t("login.back") }}
</el-button>
</Motion>
</template>

View File

@@ -1,189 +0,0 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { ref, reactive } from "vue";
import Motion from "../utils/motion";
import { updateRules } from "../utils/rule";
import { message } from "@pureadmin/components";
import type { FormInstance } from "element-plus";
import { useVerifyCode } from "../utils/verifyCode";
import { $t, transformI18n } from "/@/plugins/i18n";
import { useUserStoreHook } from "/@/store/modules/user";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const { t } = useI18n();
const checked = ref(false);
const loading = ref(false);
const ruleForm = reactive({
username: "",
phone: "",
verifyCode: "",
password: "",
repeatPassword: ""
});
const ruleFormRef = ref<FormInstance>();
const { isDisabled, text } = useVerifyCode();
const repeatPasswordRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.passwordSureReg"))));
} else if (ruleForm.password !== value) {
callback(new Error(transformI18n($t("login.passwordDifferentReg"))));
} else {
callback();
}
},
trigger: "blur"
}
];
const onUpdate = async (formEl: FormInstance | undefined) => {
loading.value = true;
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
if (checked.value) {
// 模拟请求,需根据实际开发进行修改
setTimeout(() => {
message.success(transformI18n($t("login.registerSuccess")));
loading.value = false;
}, 2000);
} else {
loading.value = false;
message.warning(transformI18n($t("login.tickPrivacy")));
}
} else {
loading.value = false;
return fields;
}
});
};
function onBack() {
useVerifyCode().end();
useUserStoreHook().SET_CURRENTPAGE(0);
}
</script>
<template>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="updateRules"
size="large"
>
<Motion>
<el-form-item
:rules="[
{
required: true,
message: transformI18n($t('login.usernameReg')),
trigger: 'blur'
}
]"
prop="username"
>
<el-input
clearable
v-model="ruleForm.username"
:placeholder="t('login.username')"
:prefix-icon="useRenderIcon('user')"
/>
</el-form-item>
</Motion>
<Motion :delay="100">
<el-form-item prop="phone">
<el-input
clearable
v-model="ruleForm.phone"
:placeholder="t('login.phone')"
:prefix-icon="useRenderIcon('iphone')"
/>
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="verifyCode">
<div class="w-full flex justify-between">
<el-input
clearable
v-model="ruleForm.verifyCode"
:placeholder="t('login.smsVerifyCode')"
:prefix-icon="
useRenderIcon('ri:shield-keyhole-line', { online: true })
"
/>
<el-button
:disabled="isDisabled"
class="ml-2"
@click="useVerifyCode().start(ruleFormRef, 'phone')"
>
{{
text.length > 0
? text + t("login.info")
: t("login.getVerifyCode")
}}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion :delay="200">
<el-form-item prop="password">
<el-input
clearable
show-password
v-model="ruleForm.password"
:placeholder="t('login.password')"
:prefix-icon="useRenderIcon('lock')"
/>
</el-form-item>
</Motion>
<Motion :delay="250">
<el-form-item :rules="repeatPasswordRule" prop="repeatPassword">
<el-input
clearable
show-password
v-model="ruleForm.repeatPassword"
:placeholder="t('login.sure')"
:prefix-icon="useRenderIcon('lock')"
/>
</el-form-item>
</Motion>
<Motion :delay="300">
<el-form-item>
<el-checkbox v-model="checked">
{{ t("login.readAccept") }}
</el-checkbox>
<el-button link type="primary">
{{ t("login.privacyPolicy") }}
</el-button>
</el-form-item>
</Motion>
<Motion :delay="350">
<el-form-item>
<el-button
class="w-full"
size="default"
type="primary"
:loading="loading"
@click="onUpdate(ruleFormRef)"
>
{{ t("login.definite") }}
</el-button>
</el-form-item>
</Motion>
<Motion :delay="400">
<el-form-item>
<el-button class="w-full" size="default" @click="onBack">
{{ t("login.back") }}
</el-button>
</el-form-item>
</Motion>
</el-form>
</template>

View File

@@ -1,151 +0,0 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { ref, reactive } from "vue";
import Motion from "../utils/motion";
import { updateRules } from "../utils/rule";
import { message } from "@pureadmin/components";
import type { FormInstance } from "element-plus";
import { useVerifyCode } from "../utils/verifyCode";
import { $t, transformI18n } from "/@/plugins/i18n";
import { useUserStoreHook } from "/@/store/modules/user";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const { t } = useI18n();
const loading = ref(false);
const ruleForm = reactive({
phone: "",
verifyCode: "",
password: "",
repeatPassword: ""
});
const ruleFormRef = ref<FormInstance>();
const { isDisabled, text } = useVerifyCode();
const repeatPasswordRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.passwordSureReg"))));
} else if (ruleForm.password !== value) {
callback(new Error(transformI18n($t("login.passwordDifferentReg"))));
} else {
callback();
}
},
trigger: "blur"
}
];
const onUpdate = async (formEl: FormInstance | undefined) => {
loading.value = true;
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
// 模拟请求,需根据实际开发进行修改
setTimeout(() => {
message.success(transformI18n($t("login.passwordUpdateReg")));
loading.value = false;
}, 2000);
} else {
loading.value = false;
return fields;
}
});
};
function onBack() {
useVerifyCode().end();
useUserStoreHook().SET_CURRENTPAGE(0);
}
</script>
<template>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="updateRules"
size="large"
>
<Motion>
<el-form-item prop="phone">
<el-input
clearable
v-model="ruleForm.phone"
:placeholder="t('login.phone')"
:prefix-icon="useRenderIcon('iphone')"
/>
</el-form-item>
</Motion>
<Motion :delay="100">
<el-form-item prop="verifyCode">
<div class="w-full flex justify-between">
<el-input
clearable
v-model="ruleForm.verifyCode"
:placeholder="t('login.smsVerifyCode')"
:prefix-icon="
useRenderIcon('ri:shield-keyhole-line', { online: true })
"
/>
<el-button
:disabled="isDisabled"
class="ml-2"
@click="useVerifyCode().start(ruleFormRef, 'phone')"
>
{{
text.length > 0
? text + t("login.info")
: t("login.getVerifyCode")
}}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="password">
<el-input
clearable
show-password
v-model="ruleForm.password"
:placeholder="t('login.password')"
:prefix-icon="useRenderIcon('lock')"
/>
</el-form-item>
</Motion>
<Motion :delay="200">
<el-form-item :rules="repeatPasswordRule" prop="repeatPassword">
<el-input
clearable
show-password
v-model="ruleForm.repeatPassword"
:placeholder="t('login.sure')"
:prefix-icon="useRenderIcon('lock')"
/>
</el-form-item>
</Motion>
<Motion :delay="250">
<el-form-item>
<el-button
class="w-full"
size="default"
type="primary"
:loading="loading"
@click="onUpdate(ruleFormRef)"
>
{{ t("login.definite") }}
</el-button>
</el-form-item>
</Motion>
<Motion :delay="300">
<el-form-item>
<el-button class="w-full" size="default" @click="onBack">
{{ t("login.back") }}
</el-button>
</el-form-item>
</Motion>
</el-form>
</template>

View File

@@ -3,22 +3,15 @@ import { useI18n } from "vue-i18n";
import Motion from "./utils/motion"; import Motion from "./utils/motion";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { loginRules } from "./utils/rule"; import { loginRules } from "./utils/rule";
import phone from "./components/phone.vue"; import { ref, reactive, toRaw } from "vue";
import qrCode from "./components/qrCode.vue";
import regist from "./components/regist.vue";
import update from "./components/update.vue";
import { initRouter } from "/@/router/utils"; import { initRouter } from "/@/router/utils";
import { useNav } from "/@/layout/hooks/useNav"; import { useNav } from "/@/layout/hooks/useNav";
import { message } from "@pureadmin/components"; import { message } from "@pureadmin/components";
import type { FormInstance } from "element-plus"; import type { FormInstance } from "element-plus";
import { storageSession } from "@pureadmin/utils"; import { storageSession } from "@pureadmin/utils";
import { $t, transformI18n } from "/@/plugins/i18n"; import { $t, transformI18n } from "/@/plugins/i18n";
import { operates, thirdParty } from "./utils/enums";
import { useLayout } from "/@/layout/hooks/useLayout"; import { useLayout } from "/@/layout/hooks/useLayout";
import { useUserStoreHook } from "/@/store/modules/user";
import { bg, avatar, illustration } from "./utils/static"; import { bg, avatar, illustration } from "./utils/static";
import { ref, reactive, watch, computed, toRaw } from "vue";
import { ReImageVerify } from "/@/components/ReImageVerify";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks"; import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
import { useTranslationLang } from "/@/layout/hooks/useTranslationLang"; import { useTranslationLang } from "/@/layout/hooks/useTranslationLang";
import { useDataThemeChange } from "/@/layout/hooks/useDataThemeChange"; import { useDataThemeChange } from "/@/layout/hooks/useDataThemeChange";
@@ -30,14 +23,9 @@ import globalization from "/@/assets/svg/globalization.svg?component";
defineOptions({ defineOptions({
name: "Login" name: "Login"
}); });
const imgCode = ref("");
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const checked = ref(false);
const ruleFormRef = ref<FormInstance>(); const ruleFormRef = ref<FormInstance>();
const currentPage = computed(() => {
return useUserStoreHook().currentPage;
});
const { initStorage } = useLayout(); const { initStorage } = useLayout();
initStorage(); initStorage();
@@ -49,8 +37,7 @@ const { locale, translationCh, translationEn } = useTranslationLang();
const ruleForm = reactive({ const ruleForm = reactive({
username: "admin", username: "admin",
password: "admin123", password: "admin123"
verifyCode: ""
}); });
const onLogin = async (formEl: FormInstance | undefined) => { const onLogin = async (formEl: FormInstance | undefined) => {
@@ -76,14 +63,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
}); });
}; };
function onHandle(value) {
useUserStoreHook().SET_CURRENTPAGE(value);
}
watch(imgCode, value => {
useUserStoreHook().SET_VERIFYCODE(value);
});
dataThemeChange(); dataThemeChange();
</script> </script>
@@ -102,13 +81,13 @@ dataThemeChange();
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<globalization <globalization
class="hover:color-primary !hover:bg-transparent w-20px h-20px ml-1.5 cursor-pointer outline-none duration-300" class="hover:text-primary hover:!bg-[transparent] w-[20px] h-[20px] ml-1.5 cursor-pointer outline-none duration-300"
/> />
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="translation"> <el-dropdown-menu class="translation">
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')" :style="getDropdownItemStyle(locale, 'zh')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'zh')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh" @click="translationCh"
> >
<IconifyIconOffline <IconifyIconOffline
@@ -120,7 +99,7 @@ dataThemeChange();
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')" :style="getDropdownItemStyle(locale, 'en')"
:class="['!dark:color-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span class="check-en" v-show="locale === 'en'">
@@ -144,7 +123,6 @@ dataThemeChange();
</Motion> </Motion>
<el-form <el-form
v-if="currentPage === 0"
ref="ruleFormRef" ref="ruleFormRef"
:model="ruleForm" :model="ruleForm"
:rules="loginRules" :rules="loginRules"
@@ -183,94 +161,18 @@ dataThemeChange();
</el-form-item> </el-form-item>
</Motion> </Motion>
<Motion :delay="200">
<el-form-item prop="verifyCode">
<el-input
clearable
v-model="ruleForm.verifyCode"
:placeholder="t('login.verifyCode')"
:prefix-icon="
useRenderIcon('ri:shield-keyhole-line', { online: true })
"
>
<template v-slot:append>
<ReImageVerify v-model:code="imgCode" />
</template>
</el-input>
</el-form-item>
</Motion>
<Motion :delay="250"> <Motion :delay="250">
<el-form-item> <el-button
<div class="w-full h-20px flex justify-between items-center"> class="w-full mt-4"
<el-checkbox v-model="checked"> size="default"
{{ t("login.remember") }} type="primary"
</el-checkbox> :loading="loading"
<el-button @click="onLogin(ruleFormRef)"
link >
type="primary" {{ t("login.login") }}
@click="useUserStoreHook().SET_CURRENTPAGE(4)" </el-button>
>
{{ t("login.forget") }}
</el-button>
</div>
<el-button
class="w-full mt-4"
size="default"
type="primary"
:loading="loading"
@click="onLogin(ruleFormRef)"
>
{{ t("login.login") }}
</el-button>
</el-form-item>
</Motion>
<Motion :delay="300">
<el-form-item>
<div class="w-full h-20px flex justify-between items-center">
<el-button
v-for="(item, index) in operates"
:key="index"
class="w-full mt-4"
size="default"
@click="onHandle(index + 1)"
>
{{ t(item.title) }}
</el-button>
</div>
</el-form-item>
</Motion> </Motion>
</el-form> </el-form>
<Motion v-if="currentPage === 0" :delay="350">
<el-form-item>
<el-divider>
<p class="text-gray-500 text-xs">{{ t("login.thirdLogin") }}</p>
</el-divider>
<div class="w-full flex justify-evenly">
<span
v-for="(item, index) in thirdParty"
:key="index"
:title="t(item.title)"
>
<IconifyIconOnline
:icon="`ri:${item.icon}-fill`"
width="20"
class="cursor-pointer text-gray-500 hover:text-blue-400"
/>
</span>
</div>
</el-form-item>
</Motion>
<!-- 手机号登录 -->
<phone v-if="currentPage === 1" />
<!-- 二维码登录 -->
<qrCode v-if="currentPage === 2" />
<!-- 注册 -->
<regist v-if="currentPage === 3" />
<!-- 忘记密码 -->
<update v-if="currentPage === 4" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,34 +0,0 @@
import { $t } from "/@/plugins/i18n";
const operates = [
{
title: $t("login.phoneLogin")
},
{
title: $t("login.qRCodeLogin")
},
{
title: $t("login.register")
}
];
const thirdParty = [
{
title: $t("login.weChatLogin"),
icon: "wechat"
},
{
title: $t("login.alipayLogin"),
icon: "alipay"
},
{
title: $t("login.qqLogin"),
icon: "qq"
},
{
title: $t("login.weiboLogin"),
icon: "weibo"
}
];
export { operates, thirdParty };

View File

@@ -1,11 +1,6 @@
import { reactive } from "vue"; import { reactive } from "vue";
import { isPhone } from "@pureadmin/utils";
import type { FormRules } from "element-plus"; import type { FormRules } from "element-plus";
import { $t, transformI18n } from "/@/plugins/i18n"; import { $t, transformI18n } from "/@/plugins/i18n";
import { useUserStoreHook } from "/@/store/modules/user";
/** 6位数字验证码正则 */
export const REGEXP_SIX = /^\d{6}$/;
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */ /** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
export const REGEXP_PWD = export const REGEXP_PWD =
@@ -26,99 +21,7 @@ const loginRules = reactive(<FormRules>{
}, },
trigger: "blur" trigger: "blur"
} }
],
verifyCode: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.verifyCodeReg"))));
} else if (useUserStoreHook().verifyCode !== value) {
callback(new Error(transformI18n($t("login.verifyCodeCorrectReg"))));
} else {
callback();
}
},
trigger: "blur"
}
] ]
}); });
/** 手机登录校验 */ export { loginRules };
const phoneRules = reactive(<FormRules>{
phone: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.phoneReg"))));
} else if (!isPhone(value)) {
callback(new Error(transformI18n($t("login.phoneCorrectReg"))));
} else {
callback();
}
},
trigger: "blur"
}
],
verifyCode: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.verifyCodeReg"))));
} else if (!REGEXP_SIX.test(value)) {
callback(new Error(transformI18n($t("login.verifyCodeSixReg"))));
} else {
callback();
}
},
trigger: "blur"
}
]
});
/** 忘记密码校验 */
const updateRules = reactive(<FormRules>{
phone: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.phoneReg"))));
} else if (!isPhone(value)) {
callback(new Error(transformI18n($t("login.phoneCorrectReg"))));
} else {
callback();
}
},
trigger: "blur"
}
],
verifyCode: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.verifyCodeReg"))));
} else if (!REGEXP_SIX.test(value)) {
callback(new Error(transformI18n($t("login.verifyCodeSixReg"))));
} else {
callback();
}
},
trigger: "blur"
}
],
password: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.passwordReg"))));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error(transformI18n($t("login.passwordRuleReg"))));
} else {
callback();
}
},
trigger: "blur"
}
]
});
export { loginRules, phoneRules, updateRules };

View File

@@ -1,49 +0,0 @@
import type { FormInstance, FormItemProp } from "element-plus";
import { cloneDeep } from "lodash-unified";
import { ref } from "vue";
const isDisabled = ref(false);
const timer = ref(null);
const text = ref("");
export const useVerifyCode = () => {
const start = async (
formEl: FormInstance | undefined,
props: FormItemProp,
time = 60
) => {
if (!formEl) return;
const initTime = cloneDeep(time);
await formEl.validateField(props, isValid => {
if (isValid) {
clearInterval(timer.value);
timer.value = setInterval(() => {
if (time > 0) {
text.value = `${time}`;
isDisabled.value = true;
time -= 1;
} else {
text.value = "";
isDisabled.value = false;
clearInterval(timer.value);
time = initTime;
}
}, 1000);
}
});
};
const end = () => {
text.value = "";
isDisabled.value = false;
clearInterval(timer.value);
};
return {
isDisabled,
timer,
text,
start,
end
};
};

20
tailwind.config.js Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
corePlugins: {
preflight: false
},
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
bg_color: "var(--el-bg-color)",
primary: "var(--el-color-primary)",
primary_light_9: "var(--el-color-primary-light-9)",
text_color_primary: "var(--el-text-color-primary)",
text_color_regular: "var(--el-text-color-regular)",
text_color_disabled: "var(--el-text-color-disabled)"
}
}
}
};

View File

@@ -105,7 +105,7 @@ export interface RouteChildrenConfigsTable {
/** /**
* @description 完整路由配置表 * @description 完整路由配置表
* @see {@link https://pure-admin-doc.vercel.app/pages/782b6e/#%E4%B8%80-%E9%85%8D%E7%BD%AE%E9%A1%B9} * @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/782b6e/#%E4%B8%80-%E9%85%8D%E7%BD%AE%E9%A1%B9}
*/ */
export interface RouteConfigsTable { export interface RouteConfigsTable {
/** 路由地址 `必填` */ /** 路由地址 `必填` */

View File

@@ -1,57 +0,0 @@
import {
transformerVariantGroup,
transformerDirectives,
presetAttributify,
defineConfig,
presetMini,
presetUno
} from "unocss";
// https://github.com/unocss/unocss#readme
export default defineConfig({
presets: [presetMini({ dark: "class" }), presetAttributify(), presetUno()],
transformers: [transformerDirectives(), transformerVariantGroup()],
// unocss默认不扫描 node_modules、dist、css以及其扩展 具体实现https://github.com/unocss/unocss/blob/03c8abe8f5020b3baaed3dfbfe8e2258dd041a7e/packages/vite/src/utils.ts#L3
exclude: [
"node_modules",
"dist",
".git",
".github",
".husky",
".vscode",
"build",
"locales",
"mock",
"public",
"types",
".eslintrc.js",
".prettierrc.js",
"postcss.config.js",
"stylelint.config.js",
"commitlint.config.js",
"README.md",
"CHANGELOG.md",
"README.en-US.md",
"CHANGELOG.en_US.md",
"CHANGELOG.zh_CN.md"
],
shortcuts: {
"bg-dark": "bg-bg_color",
"wh-full": "w-full h-full",
"flex-wrap": "flex flex-wrap",
"flex-c": "flex justify-center items-center",
"flex-ac": "flex justify-around items-center",
"flex-bc": "flex justify-between items-center",
"navbar-bg-hover": "dark:color-white !dark:hover:bg-[#242424]"
},
theme: {
colors: {
bg_color: "var(--el-bg-color)",
primary: "var(--el-color-primary)",
primary_light_9: "var(--el-color-primary-light-9)",
text_color_primary: "var(--el-text-color-primary)",
text_color_regular: "var(--el-text-color-regular)",
text_color_disabled: "var(--el-text-color-disabled)"
}
}
});