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
"editor.tabSize": 2,
// 保存的时候自动格式化
// Enable the ESlint flat config support
// (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,
// 默认格式化工具选择prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 开启自动修复
// Auto fix
"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 # 前端项目
├─ config # Vite 插件配置
├─ public # 公共静态资源favicon.ico、logo.svg
continew-admin-ui
├─ config # Vite 插件配置
├─ public # 公共静态资源favicon.ico、logo.svg
├─ src
│ ├─ apis # 请求接口
│ │ ├─ auth # 认证模块
@@ -275,13 +275,10 @@ continew-admin-ui # 前端项目
│ │ └─ user # 用户管理
│ ├─ App.vue
│ └─ main.ts
├─ .env.development
├─ .env.production
├─ .env.test
├─ .eslintignore
├─ .eslintrc.cjs
├─ .prettierignore
├─ .prettierrc.js
├─ .env.development # 开发环境配置
├─ .env.production # 生产环境配置
├─ .env.test # 测试环境配置
├─ eslint.config.js # ESLint 配置
├─ index.html
├─ package.json
├─ package-lock.json

View File

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

View File

@@ -6,6 +6,6 @@ export default function createComponents() {
dirs: ['src/components'],
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')],
// 指定 symbolId 格式
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",
"type": "module",
"version": "3.1.0-SNAPSHOT",
"private": "true",
"scripts": {
@@ -9,7 +10,7 @@
"preview": "vite preview --port 5050",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint src",
"fix": "eslint src --fix"
"lint:fix": "eslint src --fix"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
@@ -54,24 +55,21 @@
"xgplayer": "^2.31.6"
},
"devDependencies": {
"@antfu/eslint-config": "^2.16.3",
"@arco-design/web-vue": "^2.55.0",
"@rushstack/eslint-patch": "^1.3.1",
"@types/crypto-js": "^4.2.2",
"@types/lodash": "^4.14.195",
"@types/node": "^20.2.5",
"@types/query-string": "^6.3.0",
"@vitejs/plugin-vue": "^5.0.4",
"@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",
"eslint": "^8.41.0",
"eslint-plugin-vue": "^9.13.0",
"eslint": "^9.0.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"prettier": "^2.8.8",
"lint-staged": "^15.2.2",
"sass": "^1.62.1",
"sass-loader": "^13.2.2",
"simple-git-hooks": "^2.11.1",
"typescript": "~5.0.4",
"unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.1",
@@ -80,5 +78,11 @@
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"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 http from '@/utils/http'
/** @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)
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import type { TreeNodeData } from '@arco-design/web-vue'
import http from '@/utils/http'
import type { LabelValueState } from '@/types/global'
import type { TreeNodeData } from '@arco-design/web-vue'
import type { OptionQuery } from '@/apis'
const BASE_URL = '/common'
@@ -16,7 +16,7 @@ export function listMenuTree(query: { description: string }) {
}
/** @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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,22 +9,22 @@ export function uploadAvatar(data: FormData) {
}
/** @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)
}
/** @desc 修改密码 */
export function updateUserPassword(data: { oldPassword: string; newPassword: string }) {
export function updateUserPassword(data: { oldPassword: string, newPassword: string }) {
return http.patch(`${BASE_URL}/password`, data)
}
/** @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)
}
/** @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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,13 +21,13 @@
<script lang="ts" setup>
defineOptions({ name: 'GiCellTags' })
interface Props {
data: string[]
}
withDefaults(defineProps<Props>(), {
data: () => []
})
interface Props {
data: string[]
}
</script>
<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 { useAppStore } from '@/stores'
const props = withDefaults(defineProps<Props>(), {
type: 'javascript',
codeJson: ''
})
const appStore = useAppStore()
const isDark = computed(() => appStore.theme === 'dark')
@@ -24,11 +28,6 @@ interface Props {
type?: 'javascript' | 'vue'
codeJson: string
}
const props = withDefaults(defineProps<Props>(), {
type: 'javascript',
codeJson: ''
})
const defaultConfig = {
tabSize: 2,
basic: true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
aria-hidden="true"
:class="svgClass"
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>
</svg>
@@ -12,21 +12,21 @@
<script setup lang="ts">
defineOptions({ name: 'GiSvgIcon' })
interface Props {
name: string
color?: string
size?: string | number
}
const props = withDefaults(defineProps<Props>(), {
name: '',
color: '',
size: 20
})
interface Props {
name: string
color?: string
size?: string | number
}
// 判断传入的值是否带有单位如果没有就默认用px单位
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>(() => {

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<template>
<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)" />
</div>
</template>

View File

@@ -6,6 +6,7 @@
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
interface Props {
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[] = [
{ label: '启用', value: 1, color: 'green' },
{ 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'
/**

View File

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

View File

@@ -1,6 +1,6 @@
import { ref } from 'vue'
import { listDeptTree } from '@/apis'
import type { TreeNodeData } from '@arco-design/web-vue'
import { listDeptTree } from '@/apis'
/** 部门模块 */
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 { Modal, Message, type FormInstance } from '@arco-design/web-vue'
import { type FormInstance, Message, Modal } from '@arco-design/web-vue'
import { isEqual } from 'lodash-es'
type Option<T> = {

View File

@@ -1,6 +1,6 @@
import { ref } from 'vue'
import { listMenuTree } from '@/apis'
import type { TreeNodeData } from '@arco-design/web-vue'
import { listMenuTree } from '@/apis'
/** 菜单模块 */
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 type { ColProps } from '@arco-design/web-vue'

View File

@@ -16,7 +16,7 @@ export function useBreakpointIndex(callback: (v: number) => void, breakpointObj?
() => breakpoint.value,
(v) => {
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])
},
{ immediate: true }

View File

@@ -2,13 +2,13 @@ import { Message, Notification } from '@arco-design/web-vue'
/**
* @description 接收数据流生成 blob创建链接下载文件
* @param {Function} api 导出表格的api方法 (必传)
* @param {String} tempName 导出的文件名 (必传)
* @param {Object} params 导出的参数 (默认{})
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
* */
* @param {string} tempName 导出的文件名 (必传)
* @param {object} params 导出的参数 (默认{})
* @param {boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {string} fileType 导出的文件格式 (默认为.xlsx)
*/
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') => {
try {
@@ -16,7 +16,7 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
if (res.headers['content-disposition']) {
tempName = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1])
} else {
tempName = tempName ? tempName : new Date().getTime() + fileType
tempName = tempName || new Date().getTime() + fileType
}
if (isNotify && !res?.code) {
Notification.warning({
@@ -24,7 +24,7 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
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('导出失败,请稍后再试!')
return
}
@@ -44,6 +44,6 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
document.body.removeChild(exportFile)
window.URL.revokeObjectURL(blobUrl)
} 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 { useLoading } from '@/hooks'

View File

@@ -1,5 +1,5 @@
import type { TableInstance, TableData } from '@arco-design/web-vue'
import { Modal, Message } from '@arco-design/web-vue'
import type { TableData, TableInstance } from '@arco-design/web-vue'
import { Message, Modal } from '@arco-design/web-vue'
import { usePagination } from '@/hooks'
interface Options<T> {
@@ -9,11 +9,12 @@ interface Options<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[]>>>
export function useTable<T>(api: Api<T>, options?: Options<T>) {
const { formatResult, onSuccess, immediate, rowKey } = options || {}
// eslint-disable-next-line ts/no-use-before-define
const { pagination, setTotal } = usePagination(() => getTableData())
const loading = ref(false)
const tableData = ref<T[]>([])
@@ -34,12 +35,6 @@ export function useTable<T>(api: Api<T>, options?: Options<T>) {
const isImmediate = immediate ?? true
isImmediate && getTableData()
// 查询
const search = () => {
selectedKeys.value = []
pagination.onChange(1)
}
// 多选
const selectedKeys = ref<(string | number)[]>([])
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]) : []
}
// 查询
const search = () => {
selectedKeys.value = []
pagination.onChange(1)
}
// 删除
const handleDelete = async <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> => {
const onDelete = async () => {
try {

View File

@@ -38,6 +38,8 @@
</template>
<script setup lang="ts">
import type { RouteRecordRaw } from 'vue-router'
import { searchTree } from 'xe-utils'
import Main from './components/Main.vue'
import Tabs from './components/Tabs/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 MenuFoldBtn from './components/MenuFoldBtn.vue'
import { useAppStore, useRouteStore } from '@/stores'
import type { RouteRecordRaw } from 'vue-router'
import { isExternal } from '@/utils/validate'
import { searchTree } from 'xe-utils'
import { filterTree } from '@/utils'
import { useDevice } from '@/hooks'
@@ -63,23 +63,11 @@ const menuRoutes = filterTree(routeStore.routes, (i) => i.meta?.hidden === false
// 顶部一级菜单
const topMenus = ref<RouteRecordRaw[]>([])
topMenus.value = JSON.parse(JSON.stringify(menuRoutes))
console.log('topMenus', toRaw(topMenus.value))
const getMenuIcon = (item: RouteRecordRaw) => {
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))
// 顶部一级菜单选中的
@@ -95,6 +83,17 @@ const getLeftMenus = (key: string) => {
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(
() => route.path,
(newPath) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia'
import { listOptionDict, type BasicConfigResp } from '@/apis'
import { computed, reactive, toRefs } from 'vue'
import { generate, getRgbStr } from '@arco-design/color'
import { type BasicConfigResp, listOptionDict } from '@/apis'
import defaultSettings from '@/config/setting.json'
const storeSetup = () => {
@@ -20,6 +20,17 @@ const storeSetup = () => {
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) => {
if (dark) {
@@ -32,17 +43,6 @@ const storeSetup = () => {
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 = () => {
if (!settingConfig.themeColor) return

View File

@@ -1,11 +1,11 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
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 { 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'
const Layout = () => import('@/layout/index.vue')
@@ -58,7 +58,7 @@ const formatAsyncRoutes = (menus: RouteItem[]) => {
title: item.title,
hidden: item.isHidden,
keepAlive: item.isCache,
alwaysShow: item.type == 1,
alwaysShow: item.type === 1,
icon: item.icon
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { isExternal } from '@/utils/validate'
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 { isExternal } from '@/utils/validate'
export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
@@ -14,16 +14,17 @@ export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
* pos="both": 去除两边空格
* pos="left": 去除左边空格
* pos="right": 去除右边空格
* pos="all": 去除所有空格 */
* pos="all": 去除所有空格
*/
type Pos = 'both' | 'left' | 'right' | 'all'
export function trim(str: string, pos: Pos = 'both'): string {
if (pos == 'both') {
if (pos === 'both') {
return str.replace(/^\s+|\s+$/g, '')
} else if (pos == 'left') {
} else if (pos === 'left') {
return str.replace(/^\s*/, '')
} else if (pos == 'right') {
} else if (pos === 'right') {
return str.replace(/(\s*$)/g, '')
} else if (pos == 'all') {
} else if (pos === 'all') {
return str.replace(/\s+/g, '')
} else {
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 {
const str = '零一二三四五六七八九十'
return str.charAt(num)
@@ -41,7 +43,8 @@ export function getHanByNumber(num: number): string {
/**
* 获取指定整数范围内的随机整数
* @param {number} start - 开始范围
* @param {number} end - 结束范围 */
* @param {number} end - 结束范围
*/
export function getRandomInterger(start = 0, end: number): number {
const range = end - start
return Math.floor(Math.random() * range + start)
@@ -59,30 +62,31 @@ export function getTypeOf(value: any) {
/**
* @desc 格式化电话号码
* @demo 183-7983-6654 */
@demo 183-7983-6654 */
export function formatPhone(mobile: string, formatStr = '-') {
return mobile.replace(/(?=(\d{4})+$)/g, formatStr)
}
/**
* @desc 手机号脱敏
* @demo 155****8810 */
@demo 155****8810 */
export function hidePhone(phone: string) {
return phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
/** @desc 检测数据是否为空数据 */
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 JSON.stringify(data) == '{}' || JSON.stringify(data) == '[]' || JSON.stringify(data) == '[{}]'
return JSON.stringify(data) === '{}' || JSON.stringify(data) === '[]' || JSON.stringify(data) === '[{}]'
}
/**
* @desc 大小写转换
* @param {string} str 待转换的字符串
* @param {number} type 1:全大写 2:全小写 3:首字母大写 */
* @param {number} type 1:全大写 2:全小写 3:首字母大写
*/
export function toCase(str: string, type: number) {
switch (type) {
case 1:
@@ -100,39 +104,39 @@ export function toCase(str: string, type: number) {
* @desc 获取随机数
* @param {number} min 最小值
* @param {number} max 最大值
* */
*/
export const randomNum = (min: number, max: number) => {
return Math.floor(min + Math.random() * (max + 1 - min))
}
/**
* @desc 获取最大值 */
@desc 获取最大值 */
export const max = (arr: number[]) => {
return Math.max.apply(null, arr)
}
/**
* @desc 获取最小值 */
@desc 获取最小值 */
export const min = (arr: number[]) => {
return Math.min.apply(null, arr)
}
/**
* @desc 求和 */
@desc 求和 */
export const sum = (arr: number[]) => {
return arr.reduce((pre, cur) => pre + cur)
}
/**
* @desc 获取平均值 */
@desc 获取平均值 */
export const average = (arr: number[]) => {
return sum(arr) / arr.length
}
/**
* @desc 深拷贝 */
@desc 深拷贝 */
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) ? [] : {}
for (const key in data) {
newData[key] = typeof data[key] === 'object' ? deepClone(data[key]) : data[key]
@@ -142,35 +146,38 @@ export const deepClone = (data: any) => {
/**
* @desc 判断是否是闰年
* @param {number} year 年份 */
* @param {number} year 年份
*/
export const isLeapYear = (year: number) => {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
/**
* @desc 判断是否是奇数
* @param {number} num 数字 */
* @param {number} num 数字
*/
export const isOdd = (num: number) => {
return num % 2 !== 0
}
/**
* @desc 判断是否是偶数
* @param {number} num 数字 */
* @param {number} num 数字
*/
export const isEven = (num: number) => {
return !isOdd(num)
}
/**
* @desc 将RGB转化为十六机制 */
@desc 将RGB转化为十六机制 */
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 = () => {
return `#${Math.floor(Math.random() * 0xffffff)
return `#${Math.floor(Math.random() * 0xFFFFFF)
.toString(16)
.padEnd(6, '0')}`
}
@@ -206,7 +213,7 @@ export const filterTree: FilterTree = (values, fn) => {
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 排序树
* @param values /
@@ -238,7 +245,7 @@ export const formatFileSize = (fileSize: number) => {
}
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
let index = 0
const srcSize = parseFloat(fileSize.toString())
const srcSize = Number.parseFloat(fileSize.toString())
index = Math.floor(Math.log(srcSize) / Math.log(1024))
const size = srcSize / 1024 ** 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}$/
/** @desc 正则-url链接 */
export const Url =
/(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/
export const Url
= /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/
/** @desc 正则-16进颜色值 #333 #8c8c8c */
export const ColorRegex = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
/** @desc 正则-只能是中文 */
export const OnlyCh = /^[\u4e00-\u9fa5]+$/gi
export const OnlyCh = /^[\u4E00-\u9FA5]+$/gi
/** @desc 正则-只能是英文 */
export const OnlyEn = /^[a-zA-Z]*$/

View File

@@ -6,5 +6,5 @@ export const isExternal = (path: string) => {
/** 判断 url 是否是 http 或 https */
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' })
interface Props {
code: number
}
const props = withDefaults(defineProps<Props>(), {
code: 403
})
interface Props {
code: number
}
const IconMap: Record<number, Component> = {
403: Icon403,
404: Icon404,

View File

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

View File

@@ -13,11 +13,28 @@
</template>
<script lang="ts" setup>
import { listDashboardAccessTrend, type DashboardAccessTrendResp } from '@/apis'
import VCharts from 'vue-echarts'
import { graphic } from 'echarts'
import { type DashboardAccessTrendResp, listDashboardAccessTrend } from '@/apis'
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 pvStatisticsData = ref<number[]>([])
const ipStatisticsData = ref<number[]>([])
@@ -196,23 +213,6 @@ const onChange = (days: number) => {
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(() => {
getChartData(30)
})

View File

@@ -2,7 +2,7 @@
<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 :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 })">
<component :is="item.icon" :size="30" class="icon"></component>
<a-typography-text class="text">{{ item.name }}</a-typography-text>

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<a-row align="stretch">
<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 :bordered="false" hoverable :class="'animated-fade-up-' + index">
<a-card :bordered="false" hoverable :class="`animated-fade-up-${index}`">
<a :href="item.url" target="_blank">
<section class="item">
<div class="item__header">

View File

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

View File

@@ -31,19 +31,17 @@
</a-form-item>
<a-form-item>
<a-space direction="vertical" fill class="w-full">
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long
>立即登录
</a-button>
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { getImageCaptcha } from '@/apis'
import { Message, type FormInstance, Modal } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import { type FormInstance, Message } from '@arco-design/web-vue'
import { useStorage } from '@vueuse/core'
import { getImageCaptcha } from '@/apis'
import { useUserStore } from '@/stores'
import { encryptByRsa } from '@/utils/encrypt'
const loginConfig = useStorage('login-config', {
@@ -62,13 +60,46 @@ const form = reactive({
uuid: '',
expired: false
})
const rules: FormInstance['rules'] = {
username: [{ required: true, message: '请输入用户名' }],
password: [{ 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 router = useRouter()
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(() => {
getCaptcha()
})

View File

@@ -25,8 +25,7 @@
</a-form-item>
<a-form-item>
<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
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button
>
</a-space>
</a-form-item>
@@ -35,7 +34,7 @@
<script setup lang="ts">
// 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 * as Regexp from '@/utils/regexp'

View File

@@ -25,8 +25,7 @@
</a-form-item>
<a-form-item>
<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
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button
>
</a-space>
</a-form-item>
@@ -35,7 +34,7 @@
<script setup lang="ts">
// 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 * as Regexp from '@/utils/regexp'

View File

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

View File

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

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