mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-07 10:57:08 +08:00
refactor: 更换 ESLint 配置为 @antfu/eslint-config
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
node_modules
|
@@ -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: {
|
||||
// eslint(https://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方式
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
/dist/*
|
||||
.local
|
||||
/node_modules/**
|
||||
|
||||
**/*.sh
|
||||
|
||||
/public/*
|
@@ -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
52
.vscode/settings.json
vendored
@@ -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"
|
||||
]
|
||||
}
|
||||
|
17
README.md
17
README.md
@@ -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
|
||||
|
@@ -6,7 +6,7 @@ export default function createAutoImport() {
|
||||
imports: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
'pinia',
|
||||
'pinia'
|
||||
],
|
||||
dts: './src/types/auto-imports.d.ts'
|
||||
})
|
||||
|
@@ -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'
|
||||
})
|
||||
}
|
||||
|
@@ -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
55
eslint.config.js
Normal 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
|
||||
}
|
||||
}
|
||||
)
|
20
package.json
20
package.json
@@ -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
10361
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http'
|
||||
import type * as Auth from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/auth'
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http'
|
||||
import type * as Common from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/captcha'
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http'
|
||||
import type * as Common from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/dashboard'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -261,7 +261,7 @@ export interface SecurityConfigResp {
|
||||
password_update_interval: OptionResp
|
||||
}
|
||||
|
||||
/** 绑定三方账号信息*/
|
||||
/** 绑定三方账号信息 */
|
||||
export interface BindSocialAccountRes {
|
||||
source: string
|
||||
description: string
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http'
|
||||
import type * as Tool from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/generator'
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
|
@@ -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'
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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>[]
|
||||
|
@@ -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('') // 搜索词
|
||||
|
||||
// 图标列表
|
||||
|
@@ -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')
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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>(() => {
|
||||
|
@@ -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[]>([])
|
||||
|
||||
// 重置配置列
|
||||
|
@@ -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'
|
||||
|
@@ -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>
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface Props {
|
||||
value: any
|
||||
}
|
||||
|
@@ -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' }
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { DirectiveBinding, Directive } from 'vue'
|
||||
import type { Directive, DirectiveBinding } from 'vue'
|
||||
import { useUserStore } from '@/stores'
|
||||
|
||||
/**
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { DirectiveBinding, Directive } from 'vue'
|
||||
import type { Directive, DirectiveBinding } from 'vue'
|
||||
import { useUserStore } from '@/stores'
|
||||
|
||||
/**
|
||||
|
@@ -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 }) {
|
||||
|
@@ -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> = {
|
||||
|
@@ -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 }) {
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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 }
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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) => {
|
||||
|
@@ -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' })
|
||||
|
@@ -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 }
|
||||
}
|
||||
|
||||
// 改变主题色
|
||||
|
@@ -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>>()
|
||||
|
@@ -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 = () => {
|
||||
|
@@ -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)
|
||||
|
@@ -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')
|
||||
}
|
||||
|
||||
// 折叠状态改变时触发
|
||||
|
@@ -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>
|
||||
|
@@ -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 })
|
||||
|
18
src/main.ts
18
src/main.ts
@@ -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
|
||||
|
@@ -1,2 +1,2 @@
|
||||
/** 省市区数据类型 */
|
||||
export type MockAreaItem = { label: string; code: string; children?: MockAreaItem[] }
|
||||
export type MockAreaItem = { label: string, code: string, children?: MockAreaItem[] }
|
||||
|
@@ -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([
|
||||
|
@@ -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')
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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的数组
|
||||
|
@@ -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()
|
||||
|
2
src/types/router.d.ts
vendored
2
src/types/router.d.ts
vendored
@@ -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
|
||||
|
@@ -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'
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
}
|
||||
)
|
||||
|
@@ -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]}`
|
||||
|
@@ -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]*$/
|
||||
|
@@ -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://')
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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>
|
||||
|
@@ -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)
|
||||
})
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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">
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
})
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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
Reference in New Issue
Block a user