refactor: 更换 ESLint 配置为 @antfu/eslint-config

This commit is contained in:
2024-05-10 22:29:45 +08:00
parent 5101dd12d9
commit bfc8e42bad
148 changed files with 7314 additions and 5046 deletions

View File

@@ -1,2 +0,0 @@
dist
node_modules

View File

@@ -1,68 +0,0 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
// @see https://eslint.bootcss.com/docs/rules/
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
jest: true
},
/* 指定如何解析语法 */
parser: 'vue-eslint-parser',
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
/* 继承已有的规则(全部规则默认是关闭的, 这个配置项开启推荐规则, 推荐规则参照文档) */
extends: [
'eslint:recommended', // 比如: 函数不能重名、对象不能出现重复key
'plugin:vue/vue3-essential', // vue3语法规则
'plugin:vue/vue3-recommended',
'@vue/eslint-config-typescript/recommended', // ts语法规则
'@vue/eslint-config-prettier'
],
plugins: ['vue', '@typescript-eslint'],
/*
* "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// eslinthttps://eslint.bootcss.com/docs/rules/
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的转义字符
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间
'@typescript-eslint/semi': 'off',
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
'vue/no-mutating-props': 'off', // 不允许组件 prop 的改变
'vue/no-reserved-component-names': 'off', // 不允许在组件定义中使用保留名称
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
'vue/padding-line-between-blocks': [2, 'always'], // 模板 template script style 之间空一行
'vue/component-name-in-template-casing': [2, 'PascalCase'], // 模板中的自定义组件名采用PascalCase方式
'vue/custom-event-name-casing': [2, 'kebab-case'], // emit事件名采用kebab-case方式
'vue/v-on-event-hyphenation': [1, 'always', { autofix: true }] // 组件事件名采用@kebab-case方式
}
}

View File

@@ -1,7 +0,0 @@
/dist/*
.local
/node_modules/**
**/*.sh
/public/*

View File

@@ -1,14 +0,0 @@
module.exports = {
printWidth: 120, // 单行字符数, 默认80
tabWidth: 2, // 缩进字节数, 默认2
semi: false, // 句尾使用分号, 默认true
singleQuote: true, // 使用单引号代替双引号, 默认false
trailingComma: 'none', // 多行时尽可能打印尾随逗号, 默认'none', 可选: ['none', 'es5', 'all']
endOfLine: 'auto' // 结束行形式, 默认'auto', 结尾是 \n \r \n\r auto
// arrowParens: 'avoid', // 在单个箭头函数参数周围加上括号, 默认'avoid', 可选: ['avoid', 'always'] avoid: 尽可能 always: 始终
// bracketSpacing: true, // 在对象文字中打印括号之间的空格, 默认true, 示例: true -- { name: 'abc' } false -- {name:'abc'}
// useTabs: false, // 使用制表符 tab 缩进行而不是空格, 默认false
// bracketSpacing: true, //在对象前后添加空格-obj: { foo: bar }
// jsxSingleQuote: false, // 在JSX中使用单引号代替双引号, 默认false
}

52
.vscode/settings.json vendored
View File

@@ -1,13 +1,47 @@
{ {
// 配置该项, 新建文件时默认就是space: 2 // Enable the ESlint flat config support
"editor.tabSize": 2, // (remove this if your ESLint extension above v3.0.5)
// 保存的时候自动格式化 "eslint.experimental.useFlatConfig": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": true, "editor.formatOnSave": true,
// 默认格式化工具选择prettier
"editor.defaultFormatter": "esbenp.prettier-vscode", // Auto fix
// 开启自动修复
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": "never", "source.fixAll.eslint": "explicit",
"source.fixAll.eslint": "explicit" "source.organizeImports": "never"
} },
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off" },
{ "rule": "format/*", "severity": "off" },
{ "rule": "*-indent", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-order", "severity": "off" },
{ "rule": "*-dangle", "severity": "off" },
{ "rule": "*-newline", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "*semi", "severity": "off" }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"gql",
"graphql",
"astro"
]
} }

View File

@@ -224,9 +224,9 @@ pnpm dev
## 项目结构 ## 项目结构
``` ```
continew-admin-ui # 前端项目 continew-admin-ui
├─ config # Vite 插件配置 ├─ config # Vite 插件配置
├─ public # 公共静态资源favicon.ico、logo.svg ├─ public # 公共静态资源favicon.ico、logo.svg
├─ src ├─ src
│ ├─ apis # 请求接口 │ ├─ apis # 请求接口
│ │ ├─ auth # 认证模块 │ │ ├─ auth # 认证模块
@@ -275,13 +275,10 @@ continew-admin-ui # 前端项目
│ │ └─ user # 用户管理 │ │ └─ user # 用户管理
│ ├─ App.vue │ ├─ App.vue
│ └─ main.ts │ └─ main.ts
├─ .env.development ├─ .env.development # 开发环境配置
├─ .env.production ├─ .env.production # 生产环境配置
├─ .env.test ├─ .env.test # 测试环境配置
├─ .eslintignore ├─ eslint.config.js # ESLint 配置
├─ .eslintrc.cjs
├─ .prettierignore
├─ .prettierrc.js
├─ index.html ├─ index.html
├─ package.json ├─ package.json
├─ package-lock.json ├─ package-lock.json

View File

@@ -6,7 +6,7 @@ export default function createAutoImport() {
imports: [ imports: [
'vue', 'vue',
'vue-router', 'vue-router',
'pinia', 'pinia'
], ],
dts: './src/types/auto-imports.d.ts' dts: './src/types/auto-imports.d.ts'
}) })

View File

@@ -6,6 +6,6 @@ export default function createComponents() {
dirs: ['src/components'], dirs: ['src/components'],
extensions: ['vue', 'tsx'], extensions: ['vue', 'tsx'],
// 配置文件生成位置 // 配置文件生成位置
dts: './src/types/components.d.ts', dts: './src/types/components.d.ts'
}) })
} }

View File

@@ -8,6 +8,6 @@ export default function createSvgIcon(isBuild) {
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定 symbolId 格式 // 指定 symbolId 格式
symbolId: 'icon-[dir]-[name]', symbolId: 'icon-[dir]-[name]',
svgoOptions: isBuild, svgoOptions: isBuild
}) })
} }

55
eslint.config.js Normal file
View File

@@ -0,0 +1,55 @@
import antfu from '@antfu/eslint-config'
// https://github.com/antfu/eslint-config
export default antfu(
{
vue: true,
typescript: true,
ignores: [
'README.md',
'src/types/shims-vue.d.ts'
]
},
{
// Remember to specify the file glob here, otherwise it might cause the vue plugin to handle non-vue files
files: ['**/*.vue'],
rules: {
'vue/block-order': [2, {
order: [['script', 'template'], 'style']
}], // 强制组件顶级元素的顺序
'vue/html-self-closing': [0, {
html: {
void: 'never',
normal: 'always',
component: 'never'
}
}], // 强制自结束样式
'vue/custom-event-name-casing': [2, 'kebab-case'], // 对自定义事件名称强制使用特定大小写
'vue/singleline-html-element-content-newline': 0, // 要求在单行元素的内容前后换行
'vue/first-attribute-linebreak': 0, // 强制第一个属性的位置
'vue/define-macros-order': [2, {
order: ['defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots'],
defineExposeLast: false
}], // 强制执行定义限制和定义弹出编译器宏的顺序
'vue/html-indent': 0, // 在《模板》中强制一致的缩进
'vue/html-closing-bracket-newline': 0 // 要求或不允许在标记的右括号前换行
}
},
{
// Without `files`, they are general rules for all files
rules: {
'curly': [0, 'all'], // 对所有控制语句强制使用一致的大括号样式
'dot-notation': 0, // 尽可能强制使用点表示法。 在 JavaScript 中,可以使用点表示法 (foo.bar) 或方括号表示法 (foo["bar"]) 访问属性
'no-new': 0, // 不允许在赋值或比较之外使用 new 运算符
// 'no-console': 2, // 禁止使用 console
'no-process-env': 0,
'style/arrow-parens': [2, 'always'], // 箭头函数参数需要括号
'style/brace-style': [2, '1tbs', { allowSingleLine: true }], // 对块执行一致的大括号样式
'style/comma-dangle': [2, 'never'], // 要求或不允许尾随逗号
'ts/consistent-type-definitions': 0,
'node/prefer-global/process': 0,
'antfu/top-level-function': 0,
'antfu/if-newline': 0
}
}
)

View File

@@ -1,5 +1,6 @@
{ {
"name": "continew-admin-ui", "name": "continew-admin-ui",
"type": "module",
"version": "3.1.0-SNAPSHOT", "version": "3.1.0-SNAPSHOT",
"private": "true", "private": "true",
"scripts": { "scripts": {
@@ -9,7 +10,7 @@
"preview": "vite preview --port 5050", "preview": "vite preview --port 5050",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc --noEmit",
"lint": "eslint src", "lint": "eslint src",
"fix": "eslint src --fix" "lint:fix": "eslint src --fix"
}, },
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
@@ -54,24 +55,21 @@
"xgplayer": "^2.31.6" "xgplayer": "^2.31.6"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.16.3",
"@arco-design/web-vue": "^2.55.0", "@arco-design/web-vue": "^2.55.0",
"@rushstack/eslint-patch": "^1.3.1",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/lodash": "^4.14.195",
"@types/node": "^20.2.5", "@types/node": "^20.2.5",
"@types/query-string": "^6.3.0", "@types/query-string": "^6.3.0",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"eslint": "^8.41.0", "eslint": "^9.0.0",
"eslint-plugin-vue": "^9.13.0",
"less": "^4.1.3", "less": "^4.1.3",
"less-loader": "^11.0.0", "less-loader": "^11.0.0",
"prettier": "^2.8.8", "lint-staged": "^15.2.2",
"sass": "^1.62.1", "sass": "^1.62.1",
"sass-loader": "^13.2.2", "sass-loader": "^13.2.2",
"simple-git-hooks": "^2.11.1",
"typescript": "~5.0.4", "typescript": "~5.0.4",
"unplugin-auto-import": "^0.16.4", "unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.1", "unplugin-vue-components": "^0.25.1",
@@ -80,5 +78,11 @@
"vite-plugin-style-import": "^2.0.0", "vite-plugin-style-import": "^2.0.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^2.0.6" "vue-tsc": "^2.0.6"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
} }
} }

10361
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import http from '@/utils/http'
import type * as Area from './type' import type * as Area from './type'
import http from '@/utils/http'
/** @desc 获取地区列表 */ /** @desc 获取地区列表 */
export const getAreaList = (params: { type: 'province' | 'city' | 'area'; code?: string }) => { export const getAreaList = (params: { type: 'province' | 'city' | 'area', code?: string }) => {
return http.get<Area.AreaItem>('/area/list', params) return http.get<Area.AreaItem>('/area/list', params)
} }

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Auth from './type' import type * as Auth from './type'
import http from '@/utils/http'
const BASE_URL = '/auth' const BASE_URL = '/auth'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Common from './type' import type * as Common from './type'
import http from '@/utils/http'
const BASE_URL = '/captcha' const BASE_URL = '/captcha'

View File

@@ -1,6 +1,6 @@
import type { TreeNodeData } from '@arco-design/web-vue'
import http from '@/utils/http' import http from '@/utils/http'
import type { LabelValueState } from '@/types/global' import type { LabelValueState } from '@/types/global'
import type { TreeNodeData } from '@arco-design/web-vue'
import type { OptionQuery } from '@/apis' import type { OptionQuery } from '@/apis'
const BASE_URL = '/common' const BASE_URL = '/common'
@@ -16,7 +16,7 @@ export function listMenuTree(query: { description: string }) {
} }
/** @desc 查询角色列表 */ /** @desc 查询角色列表 */
export function listRoleDict(query?: { name: string; status: number }) { export function listRoleDict(query?: { name: string, status: number }) {
return http.get<LabelValueState[]>(`${BASE_URL}/dict/role`, query) return http.get<LabelValueState[]>(`${BASE_URL}/dict/role`, query)
} }

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Common from './type' import type * as Common from './type'
import http from '@/utils/http'
const BASE_URL = '/dashboard' const BASE_URL = '/dashboard'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Monitor from './type' import type * as Monitor from './type'
import http from '@/utils/http'
const BASE_URL = '/system/log' const BASE_URL = '/system/log'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Monitor from './type' import type * as Monitor from './type'
import http from '@/utils/http'
const BASE_URL = '/monitor/online' const BASE_URL = '/monitor/online'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/dept' const BASE_URL = '/system/dept'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/dict' const BASE_URL = '/system/dict'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/file' const BASE_URL = '/system/file'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/menu' const BASE_URL = '/system/menu'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/notice' const BASE_URL = '/system/notice'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/option' const BASE_URL = '/system/option'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/role' const BASE_URL = '/system/role'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/storage' const BASE_URL = '/system/storage'

View File

@@ -261,7 +261,7 @@ export interface SecurityConfigResp {
password_update_interval: OptionResp password_update_interval: OptionResp
} }
/** 绑定三方账号信息*/ /** 绑定三方账号信息 */
export interface BindSocialAccountRes { export interface BindSocialAccountRes {
source: string source: string
description: string description: string

View File

@@ -9,22 +9,22 @@ export function uploadAvatar(data: FormData) {
} }
/** @desc 修改用户基本信息 */ /** @desc 修改用户基本信息 */
export function updateUserBaseInfo(data: { nickname: string; gender: number }) { export function updateUserBaseInfo(data: { nickname: string, gender: number }) {
return http.patch(`${BASE_URL}/basic/info`, data) return http.patch(`${BASE_URL}/basic/info`, data)
} }
/** @desc 修改密码 */ /** @desc 修改密码 */
export function updateUserPassword(data: { oldPassword: string; newPassword: string }) { export function updateUserPassword(data: { oldPassword: string, newPassword: string }) {
return http.patch(`${BASE_URL}/password`, data) return http.patch(`${BASE_URL}/password`, data)
} }
/** @desc 修改手机号 */ /** @desc 修改手机号 */
export function updateUserPhone(data: { phone: string; captcha: string; oldPassword: string }) { export function updateUserPhone(data: { phone: string, captcha: string, oldPassword: string }) {
return http.patch(`${BASE_URL}/phone`, data) return http.patch(`${BASE_URL}/phone`, data)
} }
/** @desc 修改邮箱 */ /** @desc 修改邮箱 */
export function updateUserEmail(data: { email: string; captcha: string; oldPassword: string }) { export function updateUserEmail(data: { email: string, captcha: string, oldPassword: string }) {
return http.patch(`${BASE_URL}/email`, data) return http.patch(`${BASE_URL}/email`, data)
} }

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type' import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/user' const BASE_URL = '/system/user'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Tool from './type' import type * as Tool from './type'
import http from '@/utils/http'
const BASE_URL = '/generator' const BASE_URL = '/generator'

View File

@@ -13,8 +13,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { RouteLocationMatched } from 'vue-router' import type { RouteLocationMatched } from 'vue-router'
import { useRouteStore } from '@/stores'
import { findTree } from 'xe-utils' import { findTree } from 'xe-utils'
import { useRouteStore } from '@/stores'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()

View File

@@ -11,12 +11,6 @@
<script lang="ts" setup> <script lang="ts" setup>
defineOptions({ name: 'GiCellAvatar' }) defineOptions({ name: 'GiCellAvatar' })
interface Props {
avatar: string
name: string
isLink?: boolean
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
avatar: '', avatar: '',
name: '', name: '',
@@ -26,6 +20,12 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'click'): void (e: 'click'): void
}>() }>()
interface Props {
avatar: string
name: string
isLink?: boolean
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -15,13 +15,13 @@
<script lang="ts" setup> <script lang="ts" setup>
defineOptions({ name: 'GiCellGender' }) defineOptions({ name: 'GiCellGender' })
interface Props {
gender: 1 | 2 | 0
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
gender: 1 gender: 1
}) })
interface Props {
gender: 1 | 2 | 0
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -12,13 +12,13 @@
<script lang="ts" setup> <script lang="ts" setup>
defineOptions({ name: 'GiCellStatus' }) defineOptions({ name: 'GiCellStatus' })
interface Props {
status: 0 | 1
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
status: 1 status: 1
}) })
interface Props {
status: 0 | 1
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -21,13 +21,13 @@
<script lang="ts" setup> <script lang="ts" setup>
defineOptions({ name: 'GiCellTags' }) defineOptions({ name: 'GiCellTags' })
interface Props {
data: string[]
}
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
data: () => [] data: () => []
}) })
interface Props {
data: string[]
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -17,6 +17,10 @@ import { githubLight } from '@ddietr/codemirror-themes/github-light'
import { oneDark } from '@codemirror/theme-one-dark' import { oneDark } from '@codemirror/theme-one-dark'
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
const props = withDefaults(defineProps<Props>(), {
type: 'javascript',
codeJson: ''
})
const appStore = useAppStore() const appStore = useAppStore()
const isDark = computed(() => appStore.theme === 'dark') const isDark = computed(() => appStore.theme === 'dark')
@@ -24,11 +28,6 @@ interface Props {
type?: 'javascript' | 'vue' type?: 'javascript' | 'vue'
codeJson: string codeJson: string
} }
const props = withDefaults(defineProps<Props>(), {
type: 'javascript',
codeJson: ''
})
const defaultConfig = { const defaultConfig = {
tabSize: 2, tabSize: 2,
basic: true, basic: true,

View File

@@ -1,4 +1,4 @@
import { defineComponent, type PropType } from 'vue' import { type PropType, defineComponent } from 'vue'
import './dot.scss' import './dot.scss'
type TPropsType = 'primary' | 'success' | 'warning' | 'danger' | 'info' type TPropsType = 'primary' | 'success' | 'warning' | 'danger' | 'info'

View File

@@ -9,22 +9,22 @@ import type { CSSProperties } from 'vue'
defineOptions({ name: 'GiFlexibleBox' }) defineOptions({ name: 'GiFlexibleBox' })
interface Props {
modelValue: boolean
direction: 'left' | 'right'
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: false, modelValue: false,
direction: 'right' direction: 'right'
}) })
interface Props {
modelValue: boolean
direction: 'left' | 'right'
}
const BoxRef = ref<HTMLElement | null>() const BoxRef = ref<HTMLElement | null>()
const style = computed(() => { const style = computed(() => {
const obj: CSSProperties = {} const obj: CSSProperties = {}
obj[`margin-${props.direction}`] = obj[`margin-${props.direction}`]
!props.modelValue && BoxRef.value && BoxRef.value.clientWidth ? `-${BoxRef.value.clientWidth}px` : 0 = !props.modelValue && BoxRef.value && BoxRef.value.clientWidth ? `-${BoxRef.value.clientWidth}px` : 0
return obj return obj
}) })
</script> </script>

View File

@@ -5,9 +5,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
const appStore = useAppStore()
defineOptions({ name: 'GiFooter' }) defineOptions({ name: 'GiFooter' })
const appStore = useAppStore()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -104,7 +104,7 @@
<template v-if="item.type === 'date-picker'"> <template v-if="item.type === 'date-picker'">
<a-date-picker <a-date-picker
:placeholder="`请选择日期`" placeholder="请选择日期"
v-bind="(item.props as A.DatePickerInstance['$props'])" v-bind="(item.props as A.DatePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]" :model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)" @update:model-value="valueChange($event, item.field)"
@@ -113,7 +113,7 @@
<template v-if="item.type === 'time-picker'"> <template v-if="item.type === 'time-picker'">
<a-time-picker <a-time-picker
:placeholder="`请选择时间`" placeholder="请选择时间"
v-bind="(item.props as A.TimePickerInstance['$props'])" v-bind="(item.props as A.TimePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]" :model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)" @update:model-value="valueChange($event, item.field)"
@@ -171,9 +171,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { Options, Columns, ColumnsItemHide, ColumnsItemDisabled, ColumnsItem } from './type'
import type * as A from '@arco-design/web-vue' import type * as A from '@arco-design/web-vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import type { Columns, ColumnsItem, ColumnsItemDisabled, ColumnsItemHide, Options } from './type'
interface Props { interface Props {
modelValue: any modelValue: any

View File

@@ -1,7 +1,7 @@
import { reactive } from 'vue' import { reactive } from 'vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import type { Columns, ColumnsItem, ColumnsItemPropsKey } from './type'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import type { Columns, ColumnsItem, ColumnsItemPropsKey } from './type'
export function useGiForm(initValue: Columns) { export function useGiForm(initValue: Columns) {
const getInitValue = () => cloneDeep(initValue) const getInitValue = () => cloneDeep(initValue)

View File

@@ -36,11 +36,11 @@ export type ColumnsItemRequest<F = any> = (form: F) => Promise<any>
export type ColumnsItemFormat<T = any> = ( export type ColumnsItemFormat<T = any> = (
res: T res: T
) => ) =>
| A.SelectInstance['$props']['options'] | A.SelectInstance['$props']['options']
| A.RadioGroupInstance['$props']['options'] | A.RadioGroupInstance['$props']['options']
| A.CheckboxGroupInstance['$props']['options'] | A.CheckboxGroupInstance['$props']['options']
| A.CascaderInstance['$props']['options'] | A.CascaderInstance['$props']['options']
| A.TreeSelectInstance['$props']['data'] | A.TreeSelectInstance['$props']['data']
export type ColumnsItemOptionsOrData = export type ColumnsItemOptionsOrData =
| A.SelectInstance['$props']['options'] | A.SelectInstance['$props']['options']
@@ -91,8 +91,8 @@ export interface Options {
form: Omit<A.FormInstance['$props'], 'model'> form: Omit<A.FormInstance['$props'], 'model'>
row?: Partial<typeof import('@arco-design/web-vue')['Row']['__defaults']> row?: Partial<typeof import('@arco-design/web-vue')['Row']['__defaults']>
col?: A.ColProps col?: A.ColProps
btns?: { hide?: boolean; span?: number; col?: A.ColProps; searchBtnText?: string } btns?: { hide?: boolean, span?: number, col?: A.ColProps, searchBtnText?: string }
fold?: { enable?: boolean; index?: number; defaultCollapsed?: boolean } fold?: { enable?: boolean, index?: number, defaultCollapsed?: boolean }
} }
export type Columns<F = any> = ColumnsItem<F>[] export type Columns<F = any> = ColumnsItem<F>[]

View File

@@ -55,7 +55,7 @@
<a-row justify="center" align="center"> <a-row justify="center" align="center">
<a-pagination <a-pagination
size="mini" size="mini"
:pageSize="pageSize" :page-size="pageSize"
:total="total" :total="total"
:show-size-changer="false" :show-size-changer="false"
@change="onPageChange" @change="onPageChange"
@@ -69,22 +69,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
// 自定义图标模块
const SvgIconModules = import.meta.glob('@/assets/icons/*.svg')
defineOptions({ name: 'GiIconSelector' }) defineOptions({ name: 'GiIconSelector' })
const emit = defineEmits(['select', 'update:modelValue'])
interface Props {
modelValue?: string
enableCopy?: boolean
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: '', modelValue: '',
enableCopy: false enableCopy: false
}) })
const emit = defineEmits(['select', 'update:modelValue'])
// 自定义图标模块
const SvgIconModules = import.meta.glob('@/assets/icons/*.svg')
interface Props {
modelValue?: string
enableCopy?: boolean
}
const searchValue = ref('') // 搜索词 const searchValue = ref('') // 搜索词
// 图标列表 // 图标列表

View File

@@ -21,13 +21,6 @@
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: 'GiOptionItem' }) defineOptions({ name: 'GiOptionItem' })
interface Props {
icon?: string
label?: string
more?: boolean
active?: boolean
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
icon: '', icon: '',
label: '', label: '',
@@ -39,6 +32,13 @@ const emit = defineEmits<{
(e: 'click'): void (e: 'click'): void
}>() }>()
interface Props {
icon?: string
label?: string
more?: boolean
active?: boolean
}
const handleClick = () => { const handleClick = () => {
emit('click') emit('click')
} }

View File

@@ -21,12 +21,12 @@
<script lang="ts" setup> <script lang="ts" setup>
defineOptions({ name: 'GiOverFlowTags' }) defineOptions({ name: 'GiOverFlowTags' })
interface Props {
data: string[]
}
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
data: () => [] data: () => []
}) })
interface Props {
data: string[]
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -3,7 +3,7 @@
aria-hidden="true" aria-hidden="true"
:class="svgClass" :class="svgClass"
v-bind="$attrs" v-bind="$attrs"
:style="{ color: color, fill: color, width: iconSize, height: iconSize }" :style="{ color, fill: color, width: iconSize, height: iconSize }"
> >
<use :xlink:href="iconName"></use> <use :xlink:href="iconName"></use>
</svg> </svg>
@@ -12,21 +12,21 @@
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: 'GiSvgIcon' }) defineOptions({ name: 'GiSvgIcon' })
interface Props {
name: string
color?: string
size?: string | number
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
name: '', name: '',
color: '', color: '',
size: 20 size: 20
}) })
interface Props {
name: string
color?: string
size?: string | number
}
// 判断传入的值是否带有单位如果没有就默认用px单位 // 判断传入的值是否带有单位如果没有就默认用px单位
const getUnitValue = (value: string | number): string | number => { const getUnitValue = (value: string | number): string | number => {
return /(px|em|rem|%)$/.test(value.toString()) ? value : value + 'px' return /(px|em|rem|%)$/.test(value.toString()) ? value : `${value}px`
} }
const iconSize = computed<string | number>(() => { const iconSize = computed<string | number>(() => {

View File

@@ -1,5 +1,5 @@
<template> <template>
<div ref="giTableRef" class="gi-table" :class="{ 'gi-table--fullscreen': isFullscreen }"> <div class="gi-table" :class="{ 'gi-table--fullscreen': isFullscreen }">
<a-row justify="space-between" align="center" class="gi-table__toolbar"> <a-row justify="space-between" align="center" class="gi-table__toolbar">
<a-space wrap class="gi-table__toolbar-left" :size="[8, 8]"> <a-space wrap class="gi-table__toolbar-left" :size="[8, 8]">
<slot name="custom-left"></slot> <slot name="custom-left"></slot>
@@ -18,9 +18,11 @@
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<template #content> <template #content>
<a-doption v-for="item in sizeList" :key="item.value" :value="item.value" :active="item.value === size">{{ <a-doption v-for="item in sizeList" :key="item.value" :value="item.value" :active="item.value === size">
{{
item.label item.label
}}</a-doption> }}
</a-doption>
</template> </template>
</a-dropdown> </a-dropdown>
<a-popover <a-popover
@@ -38,7 +40,7 @@
</a-tooltip> </a-tooltip>
<template #content> <template #content>
<div class="gi-table__draggable"> <div class="gi-table__draggable">
<VueDraggable ref="el" v-model="settingColumnList"> <VueDraggable v-model="settingColumnList">
<div v-for="item in settingColumnList" :key="item.title" class="drag-item"> <div v-for="item in settingColumnList" :key="item.title" class="drag-item">
<div class="drag-item__move"><icon-drag-dot-vertical /></div> <div class="drag-item__move"><icon-drag-dot-vertical /></div>
<a-checkbox v-model:model-value="item.show" :disabled="item.disabled">{{ item.title }}</a-checkbox> <a-checkbox v-model:model-value="item.show" :disabled="item.disabled">{{ item.title }}</a-checkbox>
@@ -73,17 +75,23 @@
v-bind="{ ...attrs, columns: _columns }" v-bind="{ ...attrs, columns: _columns }"
> >
<template v-for="key in Object.keys(slots)" :key="key" #[key]="scoped"> <template v-for="key in Object.keys(slots)" :key="key" #[key]="scoped">
<slot :key="key" :name="key" v-bind="scoped"></slot> </template <slot :key="key" :name="key" v-bind="scoped"></slot>
></a-table> </template>
</a-table>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { TableInstance, TableColumnData, DropdownInstance } from '@arco-design/web-vue' import type { DropdownInstance, TableColumnData, TableInstance } from '@arco-design/web-vue'
import { VueDraggable } from 'vue-draggable-plus' import { VueDraggable } from 'vue-draggable-plus'
defineOptions({ name: 'GiTable', inheritAttrs: false }) defineOptions({ name: 'GiTable', inheritAttrs: false })
const props = withDefaults(defineProps<Props>(), {
disabledTools: () => [], // 禁止显示的工具
disabledColumnKeys: () => [] // 禁止控制显示隐藏的列
})
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'refresh'): void (e: 'refresh'): void
}>() }>()
@@ -96,18 +104,13 @@ interface Props {
disabledColumnKeys?: string[] disabledColumnKeys?: string[]
} }
const props = withDefaults(defineProps<Props>(), {
disabledTools: () => [], // 禁止显示的工具
disabledColumnKeys: () => [] // 禁止控制显示隐藏的列
})
const tableRef = ref<TableInstance | null>(null) const tableRef = ref<TableInstance | null>(null)
const stripe = ref(false) const stripe = ref(false)
const size = ref<TableInstance['size']>('medium') const size = ref<TableInstance['size']>('medium')
const isBordered = ref(false) const isBordered = ref(false)
const isFullscreen = ref(false) const isFullscreen = ref(false)
type SizeItem = { label: string; value: TableInstance['size'] } type SizeItem = { label: string, value: TableInstance['size'] }
const sizeList: SizeItem[] = [ const sizeList: SizeItem[] = [
{ label: '紧凑', value: 'small' }, { label: '紧凑', value: 'small' },
{ label: '默认', value: 'medium' } { label: '默认', value: 'medium' }
@@ -127,7 +130,7 @@ const showFullscreenBtn = computed(() => !props.disabledTools.includes('fullscre
const showSettingColumnBtn = computed( const showSettingColumnBtn = computed(
() => !props.disabledTools.includes('setting') && attrs?.columns && (attrs?.columns as TableColumnData[])?.length () => !props.disabledTools.includes('setting') && attrs?.columns && (attrs?.columns as TableColumnData[])?.length
) )
type SettingColumnItem = { title: string; key: string; show: boolean; disabled: boolean } type SettingColumnItem = { title: string, key: string, show: boolean, disabled: boolean }
const settingColumnList = ref<SettingColumnItem[]>([]) const settingColumnList = ref<SettingColumnItem[]>([])
// 重置配置列 // 重置配置列

View File

@@ -1,4 +1,4 @@
import { defineComponent, computed, type PropType } from 'vue' import { type PropType, computed, defineComponent } from 'vue'
import './tag.scss' import './tag.scss'
type TPropsType = 'dark' | 'light' | 'outline' | 'light-outline' type TPropsType = 'dark' | 'light' | 'outline' | 'light-outline'

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="json_pretty_container"> <div class="json_pretty_container">
<VueJsonPretty :path="'res'" :data="JSONObject" :show-length="true" /> <VueJsonPretty path="res" :data="JSONObject" :show-length="true" />
<icon-copy class="copy_icon" @click="onCopy(JSONObject)" /> <icon-copy class="copy_icon" @click="onCopy(JSONObject)" />
</div> </div>
</template> </template>

View File

@@ -6,6 +6,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
interface Props { interface Props {
value: any value: any
} }

View File

@@ -1,4 +1,4 @@
type LabelValueItem = { label: string; value: number; color: string } type LabelValueItem = { label: string, value: number, color: string }
export const DisEnableStatusList: LabelValueItem[] = [ export const DisEnableStatusList: LabelValueItem[] = [
{ label: '启用', value: 1, color: 'green' }, { label: '启用', value: 1, color: 'green' },
{ label: '禁用', value: 2, color: 'red' } { label: '禁用', value: 2, color: 'red' }

View File

@@ -1,4 +1,4 @@
import type { DirectiveBinding, Directive } from 'vue' import type { Directive, DirectiveBinding } from 'vue'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
/** /**

View File

@@ -1,4 +1,4 @@
import type { DirectiveBinding, Directive } from 'vue' import type { Directive, DirectiveBinding } from 'vue'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
/** /**

View File

@@ -1,6 +1,6 @@
import { ref } from 'vue' import { ref } from 'vue'
import { listDeptTree } from '@/apis'
import type { TreeNodeData } from '@arco-design/web-vue' import type { TreeNodeData } from '@arco-design/web-vue'
import { listDeptTree } from '@/apis'
/** 部门模块 */ /** 部门模块 */
export function useDept(options?: { onSuccess?: () => void }) { export function useDept(options?: { onSuccess?: () => void }) {

View File

@@ -1,6 +1,6 @@
import { reactive, computed, ref, type Ref } from 'vue' import { type Ref, computed, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { Modal, Message, type FormInstance } from '@arco-design/web-vue' import { type FormInstance, Message, Modal } from '@arco-design/web-vue'
import { isEqual } from 'lodash-es' import { isEqual } from 'lodash-es'
type Option<T> = { type Option<T> = {

View File

@@ -1,6 +1,6 @@
import { ref } from 'vue' import { ref } from 'vue'
import { listMenuTree } from '@/apis'
import type { TreeNodeData } from '@arco-design/web-vue' import type { TreeNodeData } from '@arco-design/web-vue'
import { listMenuTree } from '@/apis'
/** 菜单模块 */ /** 菜单模块 */
export function useMenu(options?: { onSuccess?: () => void }) { export function useMenu(options?: { onSuccess?: () => void }) {

View File

@@ -1,4 +1,4 @@
import { computed, type ComputedRef } from 'vue' import { type ComputedRef, computed } from 'vue'
import { useBreakpoints } from '@vueuse/core' import { useBreakpoints } from '@vueuse/core'
import type { ColProps } from '@arco-design/web-vue' import type { ColProps } from '@arco-design/web-vue'

View File

@@ -16,7 +16,7 @@ export function useBreakpointIndex(callback: (v: number) => void, breakpointObj?
() => breakpoint.value, () => breakpoint.value,
(v) => { (v) => {
const def = { xs: 0, sm: 0, md: 0, lg: 1, xl: 1, xxl: 2 } const def = { xs: 0, sm: 0, md: 0, lg: 1, xl: 1, xxl: 2 }
const obj = breakpointObj ? breakpointObj : def const obj = breakpointObj || def
callback(obj[v as keyof typeof obj]) callback(obj[v as keyof typeof obj])
}, },
{ immediate: true } { immediate: true }

View File

@@ -2,13 +2,13 @@ import { Message, Notification } from '@arco-design/web-vue'
/** /**
* @description 接收数据流生成 blob创建链接下载文件 * @description 接收数据流生成 blob创建链接下载文件
* @param {Function} api 导出表格的api方法 (必传) * @param {Function} api 导出表格的api方法 (必传)
* @param {String} tempName 导出的文件名 (必传) * @param {string} tempName 导出的文件名 (必传)
* @param {Object} params 导出的参数 (默认{}) * @param {object} params 导出的参数 (默认{})
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true) * @param {boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fileType 导出的文件格式 (默认为.xlsx) * @param {string} fileType 导出的文件格式 (默认为.xlsx)
* */ */
interface NavigatorWithMsSaveOrOpenBlob extends Navigator { interface NavigatorWithMsSaveOrOpenBlob extends Navigator {
msSaveOrOpenBlob(blob: Blob, fileName: string): void msSaveOrOpenBlob: (blob: Blob, fileName: string) => void
} }
export const useDownload = async (api: () => Promise<any>, isNotify = true, tempName = '', fileType = '.xlsx') => { export const useDownload = async (api: () => Promise<any>, isNotify = true, tempName = '', fileType = '.xlsx') => {
try { try {
@@ -16,7 +16,7 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
if (res.headers['content-disposition']) { if (res.headers['content-disposition']) {
tempName = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1]) tempName = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1])
} else { } else {
tempName = tempName ? tempName : new Date().getTime() + fileType tempName = tempName || new Date().getTime() + fileType
} }
if (isNotify && !res?.code) { if (isNotify && !res?.code) {
Notification.warning({ Notification.warning({
@@ -24,7 +24,7 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
content: '如果数据庞大会导致下载缓慢哦,请您耐心等待!' content: '如果数据庞大会导致下载缓慢哦,请您耐心等待!'
}) })
} }
if (res.status !== 200 || res.data === null || !(res.data instanceof Blob)) { if (res.status !== 200 || res.data == null || !(res.data instanceof Blob)) {
Message.error('导出失败,请稍后再试!') Message.error('导出失败,请稍后再试!')
return return
} }
@@ -44,6 +44,6 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
document.body.removeChild(exportFile) document.body.removeChild(exportFile)
window.URL.revokeObjectURL(blobUrl) window.URL.revokeObjectURL(blobUrl)
} catch (error) { } catch (error) {
console.log(error) // console.log(error)
} }
} }

View File

@@ -1,4 +1,4 @@
import { ref, type UnwrapRef } from 'vue' import { type UnwrapRef, ref } from 'vue'
import type { AxiosResponse } from 'axios' import type { AxiosResponse } from 'axios'
import { useLoading } from '@/hooks' import { useLoading } from '@/hooks'

View File

@@ -1,5 +1,5 @@
import type { TableInstance, TableData } from '@arco-design/web-vue' import type { TableData, TableInstance } from '@arco-design/web-vue'
import { Modal, Message } from '@arco-design/web-vue' import { Message, Modal } from '@arco-design/web-vue'
import { usePagination } from '@/hooks' import { usePagination } from '@/hooks'
interface Options<T> { interface Options<T> {
@@ -9,11 +9,12 @@ interface Options<T> {
rowKey?: keyof T rowKey?: keyof T
} }
type PaginationParams = { page: number; size: number } type PaginationParams = { page: number, size: number }
type Api<T> = (params: PaginationParams) => Promise<ApiRes<PageRes<T[]>>> type Api<T> = (params: PaginationParams) => Promise<ApiRes<PageRes<T[]>>>
export function useTable<T>(api: Api<T>, options?: Options<T>) { export function useTable<T>(api: Api<T>, options?: Options<T>) {
const { formatResult, onSuccess, immediate, rowKey } = options || {} const { formatResult, onSuccess, immediate, rowKey } = options || {}
// eslint-disable-next-line ts/no-use-before-define
const { pagination, setTotal } = usePagination(() => getTableData()) const { pagination, setTotal } = usePagination(() => getTableData())
const loading = ref(false) const loading = ref(false)
const tableData = ref<T[]>([]) const tableData = ref<T[]>([])
@@ -34,12 +35,6 @@ export function useTable<T>(api: Api<T>, options?: Options<T>) {
const isImmediate = immediate ?? true const isImmediate = immediate ?? true
isImmediate && getTableData() isImmediate && getTableData()
// 查询
const search = () => {
selectedKeys.value = []
pagination.onChange(1)
}
// 多选 // 多选
const selectedKeys = ref<(string | number)[]>([]) const selectedKeys = ref<(string | number)[]>([])
const select: TableInstance['onSelect'] = (rowKeys) => { const select: TableInstance['onSelect'] = (rowKeys) => {
@@ -53,10 +48,16 @@ export function useTable<T>(api: Api<T>, options?: Options<T>) {
selectedKeys.value = checked ? arr.map((i) => i[key as string]) : [] selectedKeys.value = checked ? arr.map((i) => i[key as string]) : []
} }
// 查询
const search = () => {
selectedKeys.value = []
pagination.onChange(1)
}
// 删除 // 删除
const handleDelete = async <T>( const handleDelete = async <T>(
deleteApi: () => Promise<ApiRes<T>>, deleteApi: () => Promise<ApiRes<T>>,
options?: { title?: string; content?: string; successTip?: string; showModal?: boolean } options?: { title?: string, content?: string, successTip?: string, showModal?: boolean }
): Promise<boolean | undefined> => { ): Promise<boolean | undefined> => {
const onDelete = async () => { const onDelete = async () => {
try { try {

View File

@@ -38,6 +38,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { RouteRecordRaw } from 'vue-router'
import { searchTree } from 'xe-utils'
import Main from './components/Main.vue' import Main from './components/Main.vue'
import Tabs from './components/Tabs/index.vue' import Tabs from './components/Tabs/index.vue'
import Menu from './components/Menu/index.vue' import Menu from './components/Menu/index.vue'
@@ -45,9 +47,7 @@ import HeaderRightBar from './components/HeaderRightBar/index.vue'
import Logo from './components/Logo.vue' import Logo from './components/Logo.vue'
import MenuFoldBtn from './components/MenuFoldBtn.vue' import MenuFoldBtn from './components/MenuFoldBtn.vue'
import { useAppStore, useRouteStore } from '@/stores' import { useAppStore, useRouteStore } from '@/stores'
import type { RouteRecordRaw } from 'vue-router'
import { isExternal } from '@/utils/validate' import { isExternal } from '@/utils/validate'
import { searchTree } from 'xe-utils'
import { filterTree } from '@/utils' import { filterTree } from '@/utils'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
@@ -63,23 +63,11 @@ const menuRoutes = filterTree(routeStore.routes, (i) => i.meta?.hidden === false
// 顶部一级菜单 // 顶部一级菜单
const topMenus = ref<RouteRecordRaw[]>([]) const topMenus = ref<RouteRecordRaw[]>([])
topMenus.value = JSON.parse(JSON.stringify(menuRoutes)) topMenus.value = JSON.parse(JSON.stringify(menuRoutes))
console.log('topMenus', toRaw(topMenus.value))
const getMenuIcon = (item: RouteRecordRaw) => { const getMenuIcon = (item: RouteRecordRaw) => {
return item.meta?.icon || item.children?.[0].meta?.icon return item.meta?.icon || item.children?.[0].meta?.icon
} }
const onMenuItemClick = (key: string) => {
if (isExternal(key)) {
window.open(key)
return
}
setTimeout(() => getLeftMenus(key))
const obj = topMenus.value.find((i) => i.path === key)
if (obj && obj.redirect === 'noRedirect') return
router.push({ path: key })
}
// 克隆是菜单的路由 // 克隆是菜单的路由
const cloneMenuRoutes: RouteRecordRaw[] = JSON.parse(JSON.stringify(menuRoutes)) const cloneMenuRoutes: RouteRecordRaw[] = JSON.parse(JSON.stringify(menuRoutes))
// 顶部一级菜单选中的 // 顶部一级菜单选中的
@@ -95,6 +83,17 @@ const getLeftMenus = (key: string) => {
leftMenus.value = obj ? (obj.children as RouteRecordRaw[]) : [] leftMenus.value = obj ? (obj.children as RouteRecordRaw[]) : []
} }
const onMenuItemClick = (key: string) => {
if (isExternal(key)) {
window.open(key)
return
}
setTimeout(() => getLeftMenus(key))
const obj = topMenus.value.find((i) => i.path === key)
if (obj && obj.redirect === 'noRedirect') return
router.push({ path: key })
}
watch( watch(
() => route.path, () => route.path,
(newPath) => { (newPath) => {

View File

@@ -23,9 +23,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore } from '@/stores'
import Menu from '../Menu/index.vue' import Menu from '../Menu/index.vue'
import Logo from '../Logo.vue' import Logo from '../Logo.vue'
import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
defineOptions({ name: 'Asider' }) defineOptions({ name: 'Asider' })

View File

@@ -82,10 +82,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore } from '@/stores'
import { ColorPicker } from 'vue-color-kit' import { ColorPicker } from 'vue-color-kit'
import 'vue-color-kit/dist/vue-color-kit.css' import 'vue-color-kit/dist/vue-color-kit.css'
import LayoutItem from './components/LayoutItem.vue' import LayoutItem from './components/LayoutItem.vue'
import { useAppStore } from '@/stores'
defineOptions({ name: 'SettingDrawer' }) defineOptions({ name: 'SettingDrawer' })
const appStore = useAppStore() const appStore = useAppStore()
@@ -133,8 +133,8 @@ const defaultColorList = [
type ColorObj = { type ColorObj = {
hex: string hex: string
hsv: { h: number; s: number; v: number } hsv: { h: number, s: number, v: number }
rgba: { r: number; g: number; b: number; a: number } rgba: { r: number, g: number, b: number, a: number }
} }
// 改变主题色 // 改变主题色

View File

@@ -67,15 +67,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { Modal } from '@arco-design/web-vue' import { Modal } from '@arco-design/web-vue'
import { useUserStore } from '@/stores' import { useFullscreen } from '@vueuse/core'
import SettingDrawer from './SettingDrawer.vue' import SettingDrawer from './SettingDrawer.vue'
import Message from './Message.vue' import Message from './Message.vue'
import { useUserStore } from '@/stores'
import { isMobile } from '@/utils' import { isMobile } from '@/utils'
import { useFullscreen } from '@vueuse/core'
defineOptions({ name: 'HeaderRight' })
const { isFullscreen, toggle } = useFullscreen() const { isFullscreen, toggle } = useFullscreen()
defineOptions({ name: 'HeaderRight' })
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const SettingDrawerRef = ref<InstanceType<typeof SettingDrawer>>() const SettingDrawerRef = ref<InstanceType<typeof SettingDrawer>>()

View File

@@ -9,6 +9,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
const props = withDefaults(defineProps<Props>(), {
collapsed: false
})
const appStore = useAppStore() const appStore = useAppStore()
const title = computed(() => appStore.getTitle()) const title = computed(() => appStore.getTitle())
const logo = computed(() => appStore.getLogo()) const logo = computed(() => appStore.getLogo())
@@ -16,10 +19,6 @@ const logo = computed(() => appStore.getLogo())
interface Props { interface Props {
collapsed?: boolean collapsed?: boolean
} }
const props = withDefaults(defineProps<Props>(), {
collapsed: false
})
const router = useRouter() const router = useRouter()
// 跳转首页 // 跳转首页
const toHome = () => { const toHome = () => {

View File

@@ -2,9 +2,9 @@
<template v-if="!item.meta?.hidden"> <template v-if="!item.meta?.hidden">
<a-menu-item <a-menu-item
v-if=" v-if="
isOneShowingChild && isOneShowingChild
(!onlyOneChild?.children || onlyOneChild?.meta?.noShowingChildren) && && (!onlyOneChild?.children || onlyOneChild?.meta?.noShowingChildren)
!item?.meta?.alwaysShow && !item?.meta?.alwaysShow
" "
v-bind="attrs" v-bind="attrs"
:key="onlyOneChild?.path" :key="onlyOneChild?.path"
@@ -32,14 +32,14 @@ import type { RouteRecordRaw } from 'vue-router'
import MenuIcon from './MenuIcon.vue' import MenuIcon from './MenuIcon.vue'
defineOptions({ name: 'MenuItem' }) defineOptions({ name: 'MenuItem' })
const props = withDefaults(defineProps<Props>(), {})
const attrs = useAttrs() const attrs = useAttrs()
interface Props { interface Props {
item: RouteRecordRaw item: RouteRecordRaw
} }
const props = withDefaults(defineProps<Props>(), {})
// 如果hidden: false那么代表这个路由项显示在左侧菜单栏中 // 如果hidden: false那么代表这个路由项显示在左侧菜单栏中
// 如果props.item的子项chidren只有一个hidden: false的子元素, 那么onlyOneChild就表示这个子元素 // 如果props.item的子项chidren只有一个hidden: false的子元素, 那么onlyOneChild就表示这个子元素
const onlyOneChild = ref<RouteRecordRaw | null>(null) const onlyOneChild = ref<RouteRecordRaw | null>(null)

View File

@@ -11,21 +11,23 @@
@menu-item-click="onMenuItemClick" @menu-item-click="onMenuItemClick"
@collapse="onCollapse" @collapse="onCollapse"
> >
<MenuItem v-for="(route, index) in sidebarRoutes" :key="route.path + index" :item="route"></MenuItem> <MenuItem v-for="(item, index) in sidebarRoutes" :key="item.path + index" :item="item"></MenuItem>
</a-menu> </a-menu>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore, useRouteStore } from '@/stores'
import MenuItem from './MenuItem.vue'
import { isExternal } from '@/utils/validate'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import MenuItem from './MenuItem.vue'
import { useAppStore, useRouteStore } from '@/stores'
import { isExternal } from '@/utils/validate'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
defineOptions({ name: 'Menu' }) defineOptions({ name: 'AppMenu' })
const props = withDefaults(defineProps<Props>(), {})
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'menuItemClickAfter'): void (e: 'menu-item-click-after'): void
}>() }>()
interface Props { interface Props {
@@ -33,15 +35,12 @@ interface Props {
menuStyle?: CSSProperties menuStyle?: CSSProperties
} }
const props = withDefaults(defineProps<Props>(), {})
const { isDesktop } = useDevice() const { isDesktop } = useDevice()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const appStore = useAppStore() const appStore = useAppStore()
const routeStore = useRouteStore() const routeStore = useRouteStore()
const sidebarRoutes = computed(() => (props.menus ? props.menus : routeStore.routes)) const sidebarRoutes = computed(() => (props.menus ? props.menus : routeStore.routes))
// console.log('sidebarRoutes', sidebarRoutes.value)
// 菜单垂直模式/水平模式 // 菜单垂直模式/水平模式
const mode = computed(() => { const mode = computed(() => {
@@ -73,7 +72,7 @@ const onMenuItemClick = (key: string) => {
return return
} }
router.push({ path: key }) router.push({ path: key })
emit('menuItemClickAfter') emit('menu-item-click-after')
} }
// 折叠状态改变时触发 // 折叠状态改变时触发

View File

@@ -20,7 +20,7 @@
:drawer-style="{ :drawer-style="{
'border-right': '1px solid var(--color-border-2)', 'border-right': '1px solid var(--color-border-2)',
'box-sizing': 'border-box', 'box-sizing': 'border-box',
'background-color': 'var(--color-bg-1)' 'background-color': 'var(--color-bg-1)',
}" }"
> >
<Logo :collapsed="false"></Logo> <Logo :collapsed="false"></Logo>

View File

@@ -41,8 +41,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { useTabsStore, useAppStore } from '@/stores'
import MagicIcon from './MagicIcon.vue' import MagicIcon from './MagicIcon.vue'
import { useAppStore, useTabsStore } from '@/stores'
defineOptions({ name: 'Tabs' }) defineOptions({ name: 'Tabs' })
const route = useRoute() const route = useRoute()
@@ -53,14 +53,6 @@ const tabsStore = useTabsStore()
// 重置, 同时把 affix: true 的路由筛选出来 // 重置, 同时把 affix: true 的路由筛选出来
tabsStore.reset() tabsStore.reset()
// 监听路由变化
watch(
() => route.path,
() => {
handleRouteChange()
}
)
// 路由发生改变触发 // 路由发生改变触发
const handleRouteChange = () => { const handleRouteChange = () => {
const item = { ...route } as unknown as RouteRecordRaw const item = { ...route } as unknown as RouteRecordRaw
@@ -72,6 +64,14 @@ const handleRouteChange = () => {
} }
handleRouteChange() handleRouteChange()
// 监听路由变化
watch(
() => route.path,
() => {
handleRouteChange()
}
)
// 点击页签 // 点击页签
const handleTabClick = (key: string) => { const handleTabClick = (key: string) => {
router.push({ path: key }) router.push({ path: key })

View File

@@ -1,22 +1,14 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import pinia from '@/stores' import ArcoVue, { Card, Modal } from '@arco-design/web-vue'
import App from './App.vue'
import router from './router'
// 引入 Arco Design 组件库以及自定义主题
import ArcoVue from '@arco-design/web-vue'
import '@/styles/arco-ui/index.less' import '@/styles/arco-ui/index.less'
import 'md-editor-v3/lib/style.css' import 'md-editor-v3/lib/style.css'
// import '@arco-themes/vue-gi-demo/index.less' // import '@arco-themes/vue-gi-demo/index.less'
// import '@arco-design/web-vue/dist/arco.css' // import '@arco-design/web-vue/dist/arco.css'
// 对特定组件进行默认配置
import { Card, Modal } from '@arco-design/web-vue'
Card.props.bordered = false
// 额外引入 Arco Design Icon图标库 // 额外引入 Arco Design Icon图标库
import ArcoVueIcon from '@arco-design/web-vue/es/icon' import ArcoVueIcon from '@arco-design/web-vue/es/icon'
import App from './App.vue'
import router from './router'
import '@/router/permission' import '@/router/permission'
@@ -34,6 +26,10 @@ import 'virtual:svg-icons-register'
// 自定义指令 // 自定义指令
import directives from './directives' import directives from './directives'
import pinia from '@/stores'
// 对特定组件进行默认配置
Card.props.bordered = false
const app = createApp(App) const app = createApp(App)
Modal._context = app._context Modal._context = app._context

View File

@@ -1,2 +1,2 @@
/** 省市区数据类型 */ /** 省市区数据类型 */
export type MockAreaItem = { label: string; code: string; children?: MockAreaItem[] } export type MockAreaItem = { label: string, code: string, children?: MockAreaItem[] }

View File

@@ -1,6 +1,6 @@
import { defineMock } from '../_base'
import { resultSuccess, getDelayTime } from '../_utils'
import { findTree } from 'xe-utils' import { findTree } from 'xe-utils'
import { defineMock } from '../_base'
import { getDelayTime, resultSuccess } from '../_utils'
import areaData from '../_data/area' import areaData from '../_data/area'
export default defineMock([ export default defineMock([

View File

@@ -1,4 +1,4 @@
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router' import { type RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
/** 默认布局 */ /** 默认布局 */
const Layout = () => import('@/layout/index.vue') const Layout = () => import('@/layout/index.vue')

View File

@@ -1,5 +1,5 @@
import router from '@/router' import router from '@/router'
import { useUserStore, useRouteStore } from '@/stores' import { useRouteStore, useUserStore } from '@/stores'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate' import { isHttp } from '@/utils/validate'
@@ -46,7 +46,7 @@ router.beforeEach(async (to, from, next) => {
} }
} else { } else {
// 如果没有 Token // 如果没有 Token
if (whiteList.indexOf(to.path) !== -1) { if (whiteList.includes(to.path)) {
// 如果在免登录的白名单中,则直接进入 // 如果在免登录的白名单中,则直接进入
next() next()
} else { } else {

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { listOptionDict, type BasicConfigResp } from '@/apis'
import { computed, reactive, toRefs } from 'vue' import { computed, reactive, toRefs } from 'vue'
import { generate, getRgbStr } from '@arco-design/color' import { generate, getRgbStr } from '@arco-design/color'
import { type BasicConfigResp, listOptionDict } from '@/apis'
import defaultSettings from '@/config/setting.json' import defaultSettings from '@/config/setting.json'
const storeSetup = () => { const storeSetup = () => {
@@ -20,6 +20,17 @@ const storeSetup = () => {
return obj return obj
}) })
// 设置主题色
const setThemeColor = (color: string) => {
if (!color) return
settingConfig.themeColor = color
const list = generate(settingConfig.themeColor, { list: true, dark: settingConfig.theme === 'dark' })
list.forEach((color: string, index: number) => {
const rgbStr = getRgbStr(color)
document.body.style.setProperty(`--primary-${index + 1}`, rgbStr)
})
}
// 切换主题 暗黑模式|简白模式 // 切换主题 暗黑模式|简白模式
const toggleTheme = (dark: boolean) => { const toggleTheme = (dark: boolean) => {
if (dark) { if (dark) {
@@ -32,17 +43,6 @@ const storeSetup = () => {
setThemeColor(settingConfig.themeColor) setThemeColor(settingConfig.themeColor)
} }
// 设置主题色
const setThemeColor = (color: string) => {
if (!color) return
settingConfig.themeColor = color
const list = generate(settingConfig.themeColor, { list: true, dark: settingConfig.theme === 'dark' })
list.forEach((color: string, index: number) => {
const rgbStr = getRgbStr(color)
document.body.style.setProperty(`--primary-${index + 1}`, rgbStr)
})
}
// 初始化主题 // 初始化主题
const initTheme = () => { const initTheme = () => {
if (!settingConfig.themeColor) return if (!settingConfig.themeColor) return

View File

@@ -1,11 +1,11 @@
import { ref } from 'vue' import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { constantRoutes } from '@/router'
import ParentView from '@/components/ParentView/index.vue'
import { getUserRoute, type RouteItem } from '@/apis'
import { mapTree, toTreeArray } from 'xe-utils' import { mapTree, toTreeArray } from 'xe-utils'
import { cloneDeep, omit } from 'lodash-es' import { cloneDeep, omit } from 'lodash-es'
import { constantRoutes } from '@/router'
import ParentView from '@/components/ParentView/index.vue'
import { type RouteItem, getUserRoute } from '@/apis'
import { transformPathToName } from '@/utils' import { transformPathToName } from '@/utils'
const Layout = () => import('@/layout/index.vue') const Layout = () => import('@/layout/index.vue')
@@ -58,7 +58,7 @@ const formatAsyncRoutes = (menus: RouteItem[]) => {
title: item.title, title: item.title,
hidden: item.isHidden, hidden: item.isHidden,
keepAlive: item.isCache, keepAlive: item.isCache,
alwaysShow: item.type == 1, alwaysShow: item.type === 1,
icon: item.icon icon: item.icon
} }
} }

View File

@@ -1,9 +1,9 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
import router from '@/router' import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import type { RouteRecordRaw, RouteRecordName } from 'vue-router'
import { useRouteStore } from '@/stores'
import _XEUtils_ from 'xe-utils' import _XEUtils_ from 'xe-utils'
import router from '@/router'
import { useRouteStore } from '@/stores'
const storeSetup = () => { const storeSetup = () => {
const tagList = ref<RouteRecordRaw[]>([]) // 保存页签tab的数组 const tagList = ref<RouteRecordRaw[]>([]) // 保存页签tab的数组

View File

@@ -1,19 +1,19 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, reactive, computed } from 'vue' import { computed, reactive, ref } from 'vue'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { import {
accountLogin as accountLoginApi,
phoneLogin as phoneLoginApi,
emailLogin as emailLoginApi,
socialLogin as socialLoginApi,
logout as logoutApi,
getUserInfo as getUserInfoApi,
type AccountLoginReq, type AccountLoginReq,
type PhoneLoginReq,
type EmailLoginReq, type EmailLoginReq,
type UserInfo type PhoneLoginReq,
type UserInfo,
accountLogin as accountLoginApi,
emailLogin as emailLoginApi,
getUserInfo as getUserInfoApi,
logout as logoutApi,
phoneLogin as phoneLoginApi,
socialLogin as socialLoginApi
} from '@/apis' } from '@/apis'
import { setToken, clearToken, getToken } from '@/utils/auth' import { clearToken, getToken, setToken } from '@/utils/auth'
import { resetHasRouteFlag } from '@/router/permission' import { resetHasRouteFlag } from '@/router/permission'
import getAvatar from '@/utils/avatar' import getAvatar from '@/utils/avatar'
@@ -76,6 +76,15 @@ const storeSetup = () => {
token.value = res.data.token token.value = res.data.token
} }
// 退出登录回调
const logoutCallBack = async () => {
roles.value = []
permissions.value = []
pwdExpiredShow.value = true
resetToken()
resetRouter()
}
// 退出登录 // 退出登录
const logout = async () => { const logout = async () => {
try { try {
@@ -87,15 +96,6 @@ const storeSetup = () => {
} }
} }
// 退出登录回调
const logoutCallBack = async () => {
roles.value = []
permissions.value = []
pwdExpiredShow.value = true
resetToken()
resetRouter()
}
// 获取用户信息 // 获取用户信息
const getInfo = async () => { const getInfo = async () => {
const res = await getUserInfoApi() const res = await getUserInfoApi()

View File

@@ -6,7 +6,7 @@ declare module 'vue-router' {
title?: string title?: string
/** 设置该路由的图标, 记得将svg导入 @/icons/svg */ /** 设置该路由的图标, 记得将svg导入 @/icons/svg */
svgIcon?: string svgIcon?: string
/** 设置该路由的图标, 直接使用Arco Design的Icon(与svgIcon同时设置时, svgIcon将优先生效)*/ /** 设置该路由的图标, 直接使用Arco Design的Icon(与svgIcon同时设置时, svgIcon将优先生效) */
icon?: string icon?: string
/** 默认false, 设置true的时候该路由不会在侧边栏出现 */ /** 默认false, 设置true的时候该路由不会在侧边栏出现 */
hidden?: boolean hidden?: boolean

View File

@@ -25,7 +25,7 @@ export function downloadByUrl({
isSameHost: boolean isSameHost: boolean
}): Promise<boolean> { }): Promise<boolean> {
// 是否同源 // 是否同源
const isSameHost = new URL(url).host == location.host const isSameHost = new URL(url).host === location.host
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
if (isSameHost) { if (isSameHost) {
const link = document.createElement('a') const link = document.createElement('a')
@@ -43,7 +43,7 @@ export function downloadByUrl({
return resolve(true) return resolve(true)
} }
if (url.indexOf('?') === -1) { if (!url.includes('?')) {
url += '?download' url += '?download'
} }

View File

@@ -15,9 +15,9 @@ export function encryptByMd5(txt: string) {
return md5(txt).toString() return md5(txt).toString()
} }
const publicKey = const publicKey
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u' + = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u'
'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ==' + 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
export function encryptByRsa(txt: string) { export function encryptByRsa(txt: string) {
const encryptor = new JSEncrypt() const encryptor = new JSEncrypt()

View File

@@ -1,12 +1,12 @@
import axios from 'axios' import axios from 'axios'
import qs from 'query-string' import qs from 'query-string'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import NProgress from 'nprogress'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import modalErrorWrapper from '@/utils/modal-error-wrapper' import modalErrorWrapper from '@/utils/modal-error-wrapper'
import messageErrorWrapper from '@/utils/message-error-wrapper' import messageErrorWrapper from '@/utils/message-error-wrapper'
import notificationErrorWrapper from '@/utils/notification-error-wrapper' import notificationErrorWrapper from '@/utils/notification-error-wrapper'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css' import 'nprogress/nprogress.css'
import router from '@/router' import router from '@/router'
@@ -103,11 +103,11 @@ http.interceptors.response.use(
(error) => { (error) => {
NProgress.done() NProgress.done()
const response = Object.assign({}, error.response) const response = Object.assign({}, error.response)
response && response
messageErrorWrapper({ && messageErrorWrapper({
content: StatusCodeMessage[response.status] || '服务器暂时未响应,请刷新页面并重试。若无法解决,请联系管理员', content: StatusCodeMessage[response.status] || '服务器暂时未响应,请刷新页面并重试。若无法解决,请联系管理员',
duration: 5 * 1000 duration: 5 * 1000
}) })
return Promise.reject(error) return Promise.reject(error)
} }
) )

View File

@@ -1,7 +1,7 @@
import { isExternal } from '@/utils/validate'
import { browse, mapTree } from 'xe-utils' import { browse, mapTree } from 'xe-utils'
import { upperFirst, camelCase } from 'lodash-es' import { camelCase, upperFirst } from 'lodash-es'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { isExternal } from '@/utils/validate'
export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key] return obj[key]
@@ -14,16 +14,17 @@ export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
* pos="both": 去除两边空格 * pos="both": 去除两边空格
* pos="left": 去除左边空格 * pos="left": 去除左边空格
* pos="right": 去除右边空格 * pos="right": 去除右边空格
* pos="all": 去除所有空格 */ * pos="all": 去除所有空格
*/
type Pos = 'both' | 'left' | 'right' | 'all' type Pos = 'both' | 'left' | 'right' | 'all'
export function trim(str: string, pos: Pos = 'both'): string { export function trim(str: string, pos: Pos = 'both'): string {
if (pos == 'both') { if (pos === 'both') {
return str.replace(/^\s+|\s+$/g, '') return str.replace(/^\s+|\s+$/g, '')
} else if (pos == 'left') { } else if (pos === 'left') {
return str.replace(/^\s*/, '') return str.replace(/^\s*/, '')
} else if (pos == 'right') { } else if (pos === 'right') {
return str.replace(/(\s*$)/g, '') return str.replace(/(\s*$)/g, '')
} else if (pos == 'all') { } else if (pos === 'all') {
return str.replace(/\s+/g, '') return str.replace(/\s+/g, '')
} else { } else {
return str return str
@@ -32,7 +33,8 @@ export function trim(str: string, pos: Pos = 'both'): string {
/** /**
* 根据数字获取对应的汉字 * 根据数字获取对应的汉字
* @param {number} num - 数字(0-10) */ * @param {number} num - 数字(0-10)
*/
export function getHanByNumber(num: number): string { export function getHanByNumber(num: number): string {
const str = '零一二三四五六七八九十' const str = '零一二三四五六七八九十'
return str.charAt(num) return str.charAt(num)
@@ -41,7 +43,8 @@ export function getHanByNumber(num: number): string {
/** /**
* 获取指定整数范围内的随机整数 * 获取指定整数范围内的随机整数
* @param {number} start - 开始范围 * @param {number} start - 开始范围
* @param {number} end - 结束范围 */ * @param {number} end - 结束范围
*/
export function getRandomInterger(start = 0, end: number): number { export function getRandomInterger(start = 0, end: number): number {
const range = end - start const range = end - start
return Math.floor(Math.random() * range + start) return Math.floor(Math.random() * range + start)
@@ -59,30 +62,31 @@ export function getTypeOf(value: any) {
/** /**
* @desc 格式化电话号码 * @desc 格式化电话号码
* @demo 183-7983-6654 */ @demo 183-7983-6654 */
export function formatPhone(mobile: string, formatStr = '-') { export function formatPhone(mobile: string, formatStr = '-') {
return mobile.replace(/(?=(\d{4})+$)/g, formatStr) return mobile.replace(/(?=(\d{4})+$)/g, formatStr)
} }
/** /**
* @desc 手机号脱敏 * @desc 手机号脱敏
* @demo 155****8810 */ @demo 155****8810 */
export function hidePhone(phone: string) { export function hidePhone(phone: string) {
return phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2') return phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
} }
/** @desc 检测数据是否为空数据 */ /** @desc 检测数据是否为空数据 */
export function isEmpty(data: unknown) { export function isEmpty(data: unknown) {
if (data === '' || data === 'undefined' || data === undefined || data === null || data === 'null') { if (data === '' || data === 'undefined' || data === undefined || data == null || data === 'null') {
return true return true
} }
return JSON.stringify(data) == '{}' || JSON.stringify(data) == '[]' || JSON.stringify(data) == '[{}]' return JSON.stringify(data) === '{}' || JSON.stringify(data) === '[]' || JSON.stringify(data) === '[{}]'
} }
/** /**
* @desc 大小写转换 * @desc 大小写转换
* @param {string} str 待转换的字符串 * @param {string} str 待转换的字符串
* @param {number} type 1:全大写 2:全小写 3:首字母大写 */ * @param {number} type 1:全大写 2:全小写 3:首字母大写
*/
export function toCase(str: string, type: number) { export function toCase(str: string, type: number) {
switch (type) { switch (type) {
case 1: case 1:
@@ -100,39 +104,39 @@ export function toCase(str: string, type: number) {
* @desc 获取随机数 * @desc 获取随机数
* @param {number} min 最小值 * @param {number} min 最小值
* @param {number} max 最大值 * @param {number} max 最大值
* */ */
export const randomNum = (min: number, max: number) => { export const randomNum = (min: number, max: number) => {
return Math.floor(min + Math.random() * (max + 1 - min)) return Math.floor(min + Math.random() * (max + 1 - min))
} }
/** /**
* @desc 获取最大值 */ @desc 获取最大值 */
export const max = (arr: number[]) => { export const max = (arr: number[]) => {
return Math.max.apply(null, arr) return Math.max.apply(null, arr)
} }
/** /**
* @desc 获取最小值 */ @desc 获取最小值 */
export const min = (arr: number[]) => { export const min = (arr: number[]) => {
return Math.min.apply(null, arr) return Math.min.apply(null, arr)
} }
/** /**
* @desc 求和 */ @desc 求和 */
export const sum = (arr: number[]) => { export const sum = (arr: number[]) => {
return arr.reduce((pre, cur) => pre + cur) return arr.reduce((pre, cur) => pre + cur)
} }
/** /**
* @desc 获取平均值 */ @desc 获取平均值 */
export const average = (arr: number[]) => { export const average = (arr: number[]) => {
return sum(arr) / arr.length return sum(arr) / arr.length
} }
/** /**
* @desc 深拷贝 */ @desc 深拷贝 */
export const deepClone = (data: any) => { export const deepClone = (data: any) => {
if (typeof data !== 'object' || data === null) return '不是对象' if (typeof data !== 'object' || data == null) return '不是对象'
const newData: any = Array.isArray(data) ? [] : {} const newData: any = Array.isArray(data) ? [] : {}
for (const key in data) { for (const key in data) {
newData[key] = typeof data[key] === 'object' ? deepClone(data[key]) : data[key] newData[key] = typeof data[key] === 'object' ? deepClone(data[key]) : data[key]
@@ -142,35 +146,38 @@ export const deepClone = (data: any) => {
/** /**
* @desc 判断是否是闰年 * @desc 判断是否是闰年
* @param {number} year 年份 */ * @param {number} year 年份
*/
export const isLeapYear = (year: number) => { export const isLeapYear = (year: number) => {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
} }
/** /**
* @desc 判断是否是奇数 * @desc 判断是否是奇数
* @param {number} num 数字 */ * @param {number} num 数字
*/
export const isOdd = (num: number) => { export const isOdd = (num: number) => {
return num % 2 !== 0 return num % 2 !== 0
} }
/** /**
* @desc 判断是否是偶数 * @desc 判断是否是偶数
* @param {number} num 数字 */ * @param {number} num 数字
*/
export const isEven = (num: number) => { export const isEven = (num: number) => {
return !isOdd(num) return !isOdd(num)
} }
/** /**
* @desc 将RGB转化为十六机制 */ @desc 将RGB转化为十六机制 */
export const rgbToHex = (r: number, g: number, b: number) => { export const rgbToHex = (r: number, g: number, b: number) => {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
} }
/** /**
* @desc 获取随机十六进制颜色 */ @desc 获取随机十六进制颜色 */
export const randomHex = () => { export const randomHex = () => {
return `#${Math.floor(Math.random() * 0xffffff) return `#${Math.floor(Math.random() * 0xFFFFFF)
.toString(16) .toString(16)
.padEnd(6, '0')}` .padEnd(6, '0')}`
} }
@@ -206,7 +213,7 @@ export const filterTree: FilterTree = (values, fn) => {
return data return data
} }
type SortTree = <T extends { sort: number; children?: T[] }>(array: T[]) => T[] type SortTree = <T extends { sort: number, children?: T[] }>(array: T[]) => T[]
/** /**
* @desc 排序树 * @desc 排序树
* @param values / * @param values /
@@ -238,7 +245,7 @@ export const formatFileSize = (fileSize: number) => {
} }
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
let index = 0 let index = 0
const srcSize = parseFloat(fileSize.toString()) const srcSize = Number.parseFloat(fileSize.toString())
index = Math.floor(Math.log(srcSize) / Math.log(1024)) index = Math.floor(Math.log(srcSize) / Math.log(1024))
const size = srcSize / 1024 ** index const size = srcSize / 1024 ** index
return `${size.toFixed(2)} ${unitArr[index]}` return `${size.toFixed(2)} ${unitArr[index]}`

View File

@@ -18,14 +18,14 @@ export const Code_6 = /^\d{6}$/
export const Code_4 = /^\d{4}$/ export const Code_4 = /^\d{4}$/
/** @desc 正则-url链接 */ /** @desc 正则-url链接 */
export const Url = export const Url
/(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/ = /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/
/** @desc 正则-16进颜色值 #333 #8c8c8c */ /** @desc 正则-16进颜色值 #333 #8c8c8c */
export const ColorRegex = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ export const ColorRegex = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
/** @desc 正则-只能是中文 */ /** @desc 正则-只能是中文 */
export const OnlyCh = /^[\u4e00-\u9fa5]+$/gi export const OnlyCh = /^[\u4E00-\u9FA5]+$/gi
/** @desc 正则-只能是英文 */ /** @desc 正则-只能是英文 */
export const OnlyEn = /^[a-zA-Z]*$/ export const OnlyEn = /^[a-zA-Z]*$/

View File

@@ -6,5 +6,5 @@ export const isExternal = (path: string) => {
/** 判断 url 是否是 http 或 https */ /** 判断 url 是否是 http 或 https */
export function isHttp(url: string) { export function isHttp(url: string) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 return url.includes('http://') || url.includes('https://')
} }

View File

@@ -23,13 +23,12 @@ import Icon500 from '@/components/icons/Icon500.vue'
defineOptions({ name: 'ErrorPage' }) defineOptions({ name: 'ErrorPage' })
interface Props {
code: number
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
code: 403 code: 403
}) })
interface Props {
code: number
}
const IconMap: Record<number, Component> = { const IconMap: Record<number, Component> = {
403: Icon403, 403: Icon403,
404: Icon404, 404: Icon404,

View File

@@ -9,5 +9,5 @@ const router = useRouter()
const { params, query } = route const { params, query } = route
const { path } = params const { path } = params
router.replace({ path: '/' + path, query }) router.replace({ path: `/${path}`, query })
</script> </script>

View File

@@ -13,11 +13,28 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { listDashboardAccessTrend, type DashboardAccessTrendResp } from '@/apis'
import VCharts from 'vue-echarts' import VCharts from 'vue-echarts'
import { graphic } from 'echarts' import { graphic } from 'echarts'
import { type DashboardAccessTrendResp, listDashboardAccessTrend } from '@/apis'
import { useChart } from '@/hooks' import { useChart } from '@/hooks'
// 提示框
const tooltipItemsHtmlString = (items) => {
return items
.map(
(el) => `<div class="content-panel">
<p>
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
<span>${el.seriesName}</span>
</p>
<span class="tooltip-value">
${el.value}
</span>
</div>`
)
.join('')
}
const xData = ref<string[]>([]) const xData = ref<string[]>([])
const pvStatisticsData = ref<number[]>([]) const pvStatisticsData = ref<number[]>([])
const ipStatisticsData = ref<number[]>([]) const ipStatisticsData = ref<number[]>([])
@@ -196,23 +213,6 @@ const onChange = (days: number) => {
getChartData(days) getChartData(days)
} }
// 提示框
const tooltipItemsHtmlString = (items) => {
return items
.map(
(el) => `<div class="content-panel">
<p>
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
<span>${el.seriesName}</span>
</p>
<span class="tooltip-value">
${el.value}
</span>
</div>`
)
.join('')
}
onMounted(() => { onMounted(() => {
getChartData(30) getChartData(30)
}) })

View File

@@ -2,7 +2,7 @@
<a-card title="快捷操作" :bordered="false" size="medium" class="card gi_card_title"> <a-card title="快捷操作" :bordered="false" size="medium" class="card gi_card_title">
<a-card-grid v-for="(item, index) in list" :key="item.name" class="card-grid-item" :style="{ width: '33.33%' }"> <a-card-grid v-for="(item, index) in list" :key="item.name" class="card-grid-item" :style="{ width: '33.33%' }">
<a-card :bordered="false" hoverable> <a-card :bordered="false" hoverable>
<a-row justify="center" align="center" :class="'animated-fade-up-' + (index + 1)"> <a-row justify="center" align="center" :class="`animated-fade-up-${index + 1}`">
<a-space direction="vertical" align="center" class="wrapper" @click="router.replace({ path: item.path })"> <a-space direction="vertical" align="center" class="wrapper" @click="router.replace({ path: item.path })">
<component :is="item.icon" :size="30" class="icon"></component> <component :is="item.icon" :size="30" class="icon"></component>
<a-typography-text class="text">{{ item.name }}</a-typography-text> <a-typography-text class="text">{{ item.name }}</a-typography-text>

View File

@@ -7,7 +7,7 @@
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="index"
align="right" align="right"
:class="'animated-fade-up-' + index" :class="`animated-fade-up-${index}`"
style="overflow: hidden" style="overflow: hidden"
> >
<template #content> <template #content>

View File

@@ -8,7 +8,7 @@
v-for="(item, index) in dataList" v-for="(item, index) in dataList"
:key="index" :key="index"
align="right" align="right"
:class="'animated-fade-up-' + index" :class="`animated-fade-up-${index}`"
style="overflow: hidden" style="overflow: hidden"
> >
<template #content> <template #content>
@@ -26,7 +26,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { listDashboardNotice, type DashboardNoticeResp } from '@/apis' import { type DashboardNoticeResp, listDashboardNotice } from '@/apis'
import { useDict } from '@/hooks/app' import { useDict } from '@/hooks/app'
import NoticeDetailModal from '@/views/system/notice/NoticeDetailModal.vue' import NoticeDetailModal from '@/views/system/notice/NoticeDetailModal.vue'

View File

@@ -3,7 +3,7 @@
<a-row align="stretch"> <a-row align="stretch">
<a-col v-for="(item, index) in list" :key="item.name" :xs="12" :sm="8" :md="8"> <a-col v-for="(item, index) in list" :key="item.name" :xs="12" :sm="8" :md="8">
<a-card-grid class="w-full h-full"> <a-card-grid class="w-full h-full">
<a-card :bordered="false" hoverable :class="'animated-fade-up-' + index"> <a-card :bordered="false" hoverable :class="`animated-fade-up-${index}`">
<a :href="item.url" target="_blank"> <a :href="item.url" target="_blank">
<section class="item"> <section class="item">
<div class="item__header"> <div class="item__header">

View File

@@ -26,10 +26,10 @@
<script setup lang="ts"> <script setup lang="ts">
import NowTime from './NowTime/index.vue' import NowTime from './NowTime/index.vue'
import SupportCard from './SupportCard.vue'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { goodTimeText } from '@/utils' import { goodTimeText } from '@/utils'
import SupportCard from './SupportCard.vue'
const { isDesktop } = useDevice() const { isDesktop } = useDevice()
const userStore = useUserStore() const userStore = useUserStore()

View File

@@ -31,19 +31,17 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-space direction="vertical" fill class="w-full"> <a-space direction="vertical" fill class="w-full">
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long <a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button>
>立即登录
</a-button>
</a-space> </a-space>
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getImageCaptcha } from '@/apis' import { type FormInstance, Message } from '@arco-design/web-vue'
import { Message, type FormInstance, Modal } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import { getImageCaptcha } from '@/apis'
import { useUserStore } from '@/stores'
import { encryptByRsa } from '@/utils/encrypt' import { encryptByRsa } from '@/utils/encrypt'
const loginConfig = useStorage('login-config', { const loginConfig = useStorage('login-config', {
@@ -62,13 +60,46 @@ const form = reactive({
uuid: '', uuid: '',
expired: false expired: false
}) })
const rules: FormInstance['rules'] = { const rules: FormInstance['rules'] = {
username: [{ required: true, message: '请输入用户名' }], username: [{ required: true, message: '请输入用户名' }],
password: [{ required: true, message: '请输入密码' }], password: [{ required: true, message: '请输入密码' }],
captcha: [{ required: true, message: '请输入验证码' }] captcha: [{ required: true, message: '请输入验证码' }]
} }
// 验证码过期定时器
let timer
const startTimer = (expireTime: number) => {
if (timer) {
clearTimeout(timer)
}
const remainingTime = expireTime - Date.now()
if (remainingTime <= 0) {
form.expired = true
return
}
timer = setTimeout(() => {
form.expired = true
}, remainingTime)
}
// 组件销毁时清理定时器
onBeforeUnmount(() => {
if (timer) {
clearTimeout(timer)
}
})
const captchaImgBase64 = ref()
// 获取验证码
const getCaptcha = () => {
getImageCaptcha().then((res) => {
const { uuid, img, expireTime } = res.data
form.uuid = uuid
captchaImgBase64.value = img
form.expired = false
startTimer(expireTime)
})
}
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const loading = ref(false) const loading = ref(false)
@@ -102,40 +133,6 @@ const handleLogin = async () => {
} }
} }
const captchaImgBase64 = ref()
// 获取验证码
const getCaptcha = () => {
getImageCaptcha().then((res) => {
const { uuid, img, expireTime } = res.data
form.uuid = uuid
captchaImgBase64.value = img
form.expired = false
startTimer(expireTime)
})
}
// 验证码过期定时器
let timer
const startTimer = (expireTime: number) => {
if (timer) {
clearTimeout(timer)
}
const remainingTime = expireTime - Date.now()
if (remainingTime <= 0) {
form.expired = true
return
}
timer = setTimeout(() => {
form.expired = true
}, remainingTime)
}
// 组件销毁时清理定时器
onBeforeUnmount(() => {
if (timer) {
clearTimeout(timer)
}
})
onMounted(() => { onMounted(() => {
getCaptcha() getCaptcha()
}) })

View File

@@ -25,8 +25,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-space direction="vertical" fill class="w-full"> <a-space direction="vertical" fill class="w-full">
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long <a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button
>立即登录</a-button
> >
</a-space> </a-space>
</a-form-item> </a-form-item>
@@ -35,7 +34,7 @@
<script setup lang="ts"> <script setup lang="ts">
// import { getEmailCaptcha } from '@/apis' // import { getEmailCaptcha } from '@/apis'
import { Message, type FormInstance } from '@arco-design/web-vue' import { type FormInstance, Message } from '@arco-design/web-vue'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import * as Regexp from '@/utils/regexp' import * as Regexp from '@/utils/regexp'

View File

@@ -25,8 +25,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-space direction="vertical" fill class="w-full"> <a-space direction="vertical" fill class="w-full">
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long <a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button
>立即登录</a-button
> >
</a-space> </a-space>
</a-form-item> </a-form-item>
@@ -35,7 +34,7 @@
<script setup lang="ts"> <script setup lang="ts">
// import { getSmsCaptcha } from '@/apis' // import { getSmsCaptcha } from '@/apis'
import { Message, type FormInstance } from '@arco-design/web-vue' import { type FormInstance, Message } from '@arco-design/web-vue'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import * as Regexp from '@/utils/regexp' import * as Regexp from '@/utils/regexp'

View File

@@ -89,11 +89,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { socialAuth } from '@/apis'
import Background from './components/background/index.vue' import Background from './components/background/index.vue'
import AccountLogin from './components/account/index.vue' import AccountLogin from './components/account/index.vue'
import PhoneLogin from './components/phone/index.vue' import PhoneLogin from './components/phone/index.vue'
import EmailLogin from './components/email/index.vue' import EmailLogin from './components/email/index.vue'
import { socialAuth } from '@/apis'
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'

View File

@@ -5,9 +5,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { bindSocialAccount } from '@/apis'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { bindSocialAccount } from '@/apis'
import { isLogin } from '@/utils/auth' import { isLogin } from '@/utils/auth'
const route = useRoute() const route = useRoute()

Some files were not shown because too many files have changed in this diff Show More