Commit 397a9042 by hzhixuan

[初始化]<init>项目初始化

parents
.DS_Store
node_modules
dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
!.idea/icon.png
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
### vscode ###
.vscode
### github ###
.github
# lock files
yarn.lock
# 是否是微服务架构(重要)
VITE_IS_MICRO= false
# 前端访问前缀
VITE_PUBLIC_PATH = /
# 后端请求前缀
VITE_API_URL = /api
# OAUTH2 密码模式客户端信息
VITE_OAUTH2_PASSWORD_CLIENT='pig:pig'
# OAUTH2 短信客户端信息
VITE_OAUTH2_MOBILE_CLIENT='app:app'
# 是否开启前端验证码
VITE_VERIFY_ENABLE = true
# 前端加密密钥
VITE_PWD_ENC_KEY='thanks,pig4cloud'
# 是否开启websocket 消息接受,
VITE_WEBSOCKET_ENABLE = false
# 是否开启注册
VITE_REGISTER_ENABLE = true
# port 端口号
VITE_PORT = 8888
#浏览器自动打开
VITE_OPEN = true
# 本地环境
ENV = 'development'
# ADMIN 服务地址
VITE_ADMIN_PROXY_PATH = http://127.0.0.1:9999
# 代码生成服务地址 (单体架构有效)
VITE_GEN_PROXY_PATH = http://127.0.0.1:5003
*.sh
node_modules
lib
*.md
*.scss
*.woff
*.ttf
.vscode
.idea
dist
mock
public
bin
build
config
index.html
src/assets
\ No newline at end of file
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.vue'],
rules: {
'no-undef': 'off',
},
},
],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
// https://typescript-eslint.io/rules/no-unused-vars/
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/no-unused-vars': [2],
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/html-self-closing': 'off',
'vue/no-multiple-template-root': 'off',
'vue/require-default-prop': 'off',
'vue/no-v-model-argument': 'off',
'vue/no-arrow-functions-in-watch': 'off',
'vue/no-template-key': 'off',
'vue/no-v-html': 'off',
'vue/comment-directive': 'off',
'vue/no-mutating-props': 'off',
'vue/no-parsing-error': 'off',
'vue/no-deprecated-v-on-native-modifier': 'off',
'vue/multi-word-component-names': 'off',
'no-useless-escape': 'off',
'no-sparse-arrays': 'off',
'no-prototype-builtins': 'off',
'no-constant-condition': 'off',
'no-use-before-define': 'off',
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'generator-star-spacing': 'off',
'no-unreachable': 'off',
'no-multiple-template-root': 'off',
'no-unused-vars': 'error',
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
'no-redeclare': 'off',
'no-mixed-spaces-and-tabs': 'off',
},
};
.DS_Store
node_modules
dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
!.idea/icon.png
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
### vscode ###
.vscode
### github ###
.github
# lock files
yarn.lock
module.exports = {
// 一行最多多少个字符
printWidth: 150,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: true,
// 在语句末尾打印分号
semi: true,
// 使用单引号而不是双引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf',
};
This diff is collapsed. Click to expand it.
# pig-ui
## 概述
**pig-ui** 是一个为 PIGCLOUD 微服务开发平台开发的前端项目。它利用了现代 Web 技术,包括 Vue.js 3、Element Plus 和 Vite,提供了一个健壮且高效的开发环境。
## 功能特性
- **Vue 3**: 利用最新版本的 Vue.js 实现现代化的响应式体验。
- **Element Plus**: 集成了 Element Plus,提供丰富的 UI 组件。
- **Vite**: 使用 Vite 进行快速构建和模块热替换。
- **TypeScript**: 支持 TypeScript,提升代码质量和可维护性。
- **Tailwind CSS**: 使用 Tailwind CSS 进行样式设计。
## 快速开始
### 先决条件
- **Node.js**: 版本 18.0.0。
- **npm**: 版本 8.0.0 或更高。
### 安装
1. 克隆仓库:
```bash
git clone https://gitee.com/log4j/pig-ui.git
cd pig-ui
```
2. 安装依赖:
```bash
npm install --registry=https://registry.npmmirror.com
```
### 开发
```bash
npm run dev
```
### 构建
为生产环境构建项目:
```bash
npm run build
```
为 Docker 构建项目:
```bash
npm run build:docker
```
## 浏览器支持
- 现代浏览器的最后两个版本。
- 不支持 IE 11 及更低版本。
## 贡献
欢迎贡献!在开始之前,请阅读[贡献指南](https://www.yuque.com/pig4cloud/pig/lceu0v)
## 许可证
本项目采用 Apache-2.0 许可证。
## 问题和反馈
如果遇到任何问题,请在 [PIGCLOUD 问题追踪](https://gitee.com/log4j/pig/issues)上报告。
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveDirective: typeof import('vue')['resolveDirective']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useLink: typeof import('vue-router')['useLink']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/nginx
COPY ./dist /data
RUN rm /etc/nginx/conf.d/default.conf
ADD pig-ui.conf /etc/nginx/conf.d/default.conf
RUN /bin/bash -c 'echo init ok'
version: '3'
services:
pig-ui:
build:
context: .
restart: always
container_name: pig-ui
image: pig-ui
networks:
- spring_cloud_default
external_links:
- pig-gateway
ports:
- 80:80
# 加入到后端网络, 默认为 spring_cloud_default | docker network ls 查看
networks:
spring_cloud_default:
external: true
server {
listen 80;
server_name localhost;
gzip on;
gzip_static on; # 需要http_gzip_static_module 模块
gzip_min_length 1k;
gzip_comp_level 4;
gzip_proxied any;
gzip_types text/plain text/xml text/css;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
# 前端打包好的dist目录文件
root /data/;
location ^~/api/ {
proxy_pass http://pig-gateway:9999/; #注意/后缀
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header from "";
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="keywords"
content="
微服务,pig开发平台,oauth2,微服务框架,java微服务框架,微服务开发框架,微服务治理框架,开源微服务框架,微服务器框架,微服务框架图,微服务快速开发框架,微服务java框架,微服务开源框架,微服务开源框架,微服务架构框架,微服务基础框架,微服务常见框架,主流微服务框架,微服务框架搭建,java主流微服务框架,java微服务开发框架,微服务前端框架,Spring
Boot,Spring Cloud,Spring"
/>
<meta
name="description"
content="PIG应用微服务、容器、DevOps等云原生技术,封装了大量技术开发包、技术应用组件、技术场景实现能力,并支持SaaS模式应用,提供了一个可支持企业各业务系统或产品快速开发实现的微服务应用数字化融合平台,富含各类开箱即用的组件、微服务业务系统,助力企业跨越Cloud(IaaS/PaaS)与自身数字化的鸿沟,共享业务服务的组合重用,为企业服务化中台整合、数字化转型提供强力支撑,也为企业提供了最佳架构实践。"
/>
<!--避免微信管理防盗链机制-->
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="/favicon.ico" />
<title>PIG 微服务快速开发平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "pig-ui",
"version": "3.8.0",
"description": "PIGCLOUD微服务开发平台",
"author": "pig4cloud",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"dev": "vite --force",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"build:docker": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build --outDir ./docker/dist/",
"lint:eslint": "eslint --fix --ext .js,.cjs,.mjs,.ts,.vue ./src",
"prettier": "prettier --write ."
},
"dependencies": {
"@element-plus/icons-vue": "2.3.1",
"@wangeditor/editor": "5.1.23",
"@wangeditor/editor-for-vue": "5.1.12",
"autoprefixer": "10.4.20",
"axios": "1.7.7",
"china-area-data": "^5.0.1",
"codemirror": "5.65.17",
"crypto-js": "4.2.0",
"driver.js": "1.3.1",
"echarts": "5.5.1",
"element-plus": "2.8.3",
"js-cookie": "3.0.5",
"mitt": "3.0.1",
"nprogress": "0.2.0",
"pinia": "2.2.2",
"postcss": "8.4.47",
"qs": "6.13.0",
"screenfull": "6.0.2",
"sm-crypto": "0.3.13",
"sortablejs": "1.15.3",
"splitpanes": "3.1.5",
"tailwindcss": "3.4.11",
"vue": "3.5.6",
"vue-clipboard3": "2.0.0",
"vue-echarts": "7.0.3",
"vue-i18n": "9.14.0",
"vue-router": "4.4.5",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@swc/core": "~1.6.13",
"@types/node": "^20.0.0",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/compiler-sfc": "^3.4.3",
"consola": "^3.0.0",
"cross-env": "7.0.3",
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"pinia-plugin-persist": "^1.0.0",
"prettier": "3.3.3",
"sass": "^1.58.3",
"terser": "^5.31.1",
"typescript": "^5.0.0",
"unplugin-auto-import": "^0.18.0",
"vite": "^5.3.3",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-top-level-await": "^1.4.1",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-eslint-parser": "^9.1.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"bugs": {
"url": "https://pig4cloud.com"
},
"engines": {
"node": ">=18.0.0",
"npm": ">= 8.0.0"
},
"keywords": [
"vue",
"vue3",
"vuejs/vue-next",
"element-ui",
"element-plus"
],
"repository": {
"type": "git",
"url": "https://gitee.com/log4j/pig-ui"
},
"resolutions": {
"@swc/core": "~1.6.13"
}
}
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
This source diff could not be displayed because it is too large. You can view the blob instead.
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="setLockScreen" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="settingRef" v-show="themeConfig.lockScreenTime > 1" />
<CloseFull v-if="!themeConfig.isLockScreen" />
</el-config-provider>
</template>
<script setup lang="ts" name="app">
import { useI18n } from 'vue-i18n';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
import setIntroduction from '/@/utils/setIconfont';
// 引入组件
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
const Setings = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue'));
const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue'));
// 定义变量内容
const { messages, locale } = useI18n();
const settingRef = ref();
const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 设置锁屏时组件显示隐藏
const setLockScreen = computed(() => {
// 防止锁屏后,刷新出现不相关界面
// https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P
return themeConfig.value.isLockScreen ? themeConfig.value.lockScreenTime > 1 : themeConfig.value.lockScreenTime >= 0;
});
// 获取全局组件大小
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();
});
// 获取全局 i18n
const getGlobalI18n = computed(() => {
return messages.value[locale.value];
});
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
});
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配'置弹窗点击打开
mittBus.on('openSetingsDrawer', () => {
settingRef.value.openDrawer();
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
storesThemeConfig.setThemeConfig({ themeConfig: Local.get('themeConfig') });
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
// 获取缓存中的全屏配置
if (Session.get('isTagsViewCurrenFull')) {
stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
}
});
});
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
mittBus.off('openSetingsDrawer', () => {});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
other.useTitle();
},
{
deep: true,
}
);
</script>
import request from "/@/utils/request"
/**
* 根据分页查询参数获取列表数据。
* @param {Object} [query] - 查询参数。
* @returns {Promise} 请求的 Promise 分页对象。
*/
export function fetchList(query?: Object) {
return request({
url: '/admin/ckTest/page',
method: 'get',
params: query
})
}
/**
* 添加一个新对象。
* @param {Object} [obj] - 要添加的对象。
* @returns {Promise} 请求的 Promise 对象 (true/false)。
*/
export function addObj(obj?: Object) {
return request({
url: '/admin/ckTest',
method: 'post',
data: obj
})
}
/**
* 根据查询参数获取对象详情。
* @param {Object} [obj] - 查询参数。
* @returns {Promise} 请求的 Promise 对象数组。
*/
export function getObj(obj?: Object) {
return request({
url: '/admin/ckTest/details',
method: 'get',
params: obj
})
}
/**
* 根据 ID 删除对象。
* @param {Object} [ids] - 要删除的对象 ID。
* @returns {Promise} 请求的 Promise 对象。
*/
export function delObjs(ids?: Object) {
return request({
url: '/admin/ckTest',
method: 'delete',
data: ids
})
}
/**
* 更新一个已存在的对象。
* @param {Object} [obj] - 要更新的对象。
* @returns {Promise} 请求的 Promise 对象。
*/
export function putObj(obj?: Object) {
return request({
url: '/admin/ckTest',
method: 'put',
data: obj
})
}
/**
* 验证某个字段的值是否已经存在。
* @param {Object} rule - 验证规则对象。
* @param {*} value - 要验证的值。
* @param {Function} callback - 验证完成后的回调函数。
* @param {boolean} isEdit - 当前操作是否为编辑。
*
* 示例用法:
* 字段名: [
* {
* validator: (rule, value, callback) => {
* validateExist(rule, value, callback, form.id !== '');
* },
* trigger: 'blur',
* },
* ]
*/
export function validateExist(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObj({ [rule.field]: value }).then((response) => {
const result = response.data;
if (result !== null && result.length > 0) {
callback(new Error('数据已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/admin/client/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/admin/client',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/admin/client/' + id,
method: 'get',
});
}
export function delObj(ids?: object) {
return request({
url: '/admin/client',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/admin/client',
method: 'put',
data: obj,
});
}
export function refreshCache() {
return request({
url: '/admin/client/sync',
method: 'put',
});
}
export function getDetails(obj: Object) {
return request({
url: '/admin/client/getClientDetailsById/' + obj,
method: 'get',
});
}
export function validateclientId(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getDetails(value).then((res) => {
const result = res.data;
if (result !== null) {
callback(new Error('编号已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export const deptTree = (params?: Object) => {
return request({
url: '/admin/dept/tree',
method: 'get',
params,
});
};
export const addObj = (obj: Object) => {
return request({
url: '/admin/dept',
method: 'post',
data: obj,
});
};
export const getObj = (id: string) => {
return request({
url: '/admin/dept/' + id,
method: 'get',
});
};
export const delObj = (id: string) => {
return request({
url: '/admin/dept/' + id,
method: 'delete',
});
};
export const putObj = (obj: Object) => {
return request({
url: '/admin/dept',
method: 'put',
data: obj,
});
};
export const syncUser = () => {
return request({
url: '/admin/connect/sync/ding/user',
method: 'post',
});
};
export const syncDept = () => {
return request({
url: '/admin/connect/sync/ding/dept',
method: 'post',
});
};
export const syncCpUser = () => {
return request({
url: '/admin/connect/sync/cp/user',
method: 'post',
});
};
export const syncCpDept = () => {
return request({
url: '/admin/connect/sync/cp/dept',
method: 'post',
});
};
import request from '/@/utils/request';
export const getDicts = (type: String) => {
return request({
url: `/admin/dict/type/${type}`,
method: 'get',
});
};
export function fetchList(query: any) {
return request({
url: '/admin/dict/list',
method: 'get',
params: query,
});
}
export function fetchItemList(query: any) {
return request({
url: '/admin/dict/item/page',
method: 'get',
params: query,
});
}
export function addItemObj(obj: any) {
return request({
url: '/admin/dict/item',
method: 'post',
data: obj,
});
}
export function getItemObj(id: string) {
return request({
url: '/admin/dict/item/details/' + id,
method: 'get',
});
}
export function getItemDetails(obj: object) {
return request({
url: '/admin/dict/item/details',
method: 'get',
params: obj,
});
}
export function delItemObj(id: string) {
return request({
url: '/admin/dict/item/' + id,
method: 'delete',
});
}
export function putItemObj(obj: any) {
return request({
url: '/admin/dict/item',
method: 'put',
data: obj,
});
}
export function addObj(obj: any) {
return request({
url: '/admin/dict',
method: 'post',
data: obj,
});
}
export function getObj(id: string) {
return request({
url: '/admin/dict/details/' + id,
method: 'get',
});
}
export function getObjDetails(obj: object) {
return request({
url: '/admin/dict/details',
method: 'get',
params: obj,
});
}
export function delObj(ids: Object) {
return request({
url: '/admin/dict',
method: 'delete',
data: ids,
});
}
export function putObj(obj: any) {
return request({
url: '/admin/dict',
method: 'put',
data: obj,
});
}
export function refreshCache() {
return request({
url: '/admin/dict/sync',
method: 'put',
});
}
export function validateDictType(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ dictType: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('字典类型已经存在'));
} else {
callback();
}
});
}
export function validateDictItemLabel(rule: any, value: any, callback: any, type: string, isEdit: boolean) {
if (isEdit) {
return callback();
}
getItemDetails({ dictType: type, label: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('标签已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/admin/sys-file/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/admin/sys-file',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/admin/sys-file/' + id,
method: 'get',
});
}
export function delObj(ids?: Object) {
return request({
url: '/admin/sys-file',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/admin/sys-file',
method: 'put',
data: obj,
});
}
import request from '/@/utils/request';
import axios from 'axios';
export function fetchList(query?: Object) {
return request({
url: '/admin/i18n/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/admin/i18n',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/admin/i18n/details/' + id,
method: 'get',
});
}
export function getObjDetails(obj?: object) {
return request({
url: '/admin/i18n/details',
method: 'get',
params: obj,
});
}
export function delObj(ids?: object) {
return request({
url: '/admin/i18n',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/admin/i18n',
method: 'put',
data: obj,
});
}
export function refreshCache() {
return request({
url: '/admin/i18n/sync',
method: 'put',
});
}
/**
* 注意这里使用原声axios对象进行操作,request 实例中依赖i18n 所以还没有初始化会报错
* @returns
*/
export function info() {
return axios.get(import.meta.env.VITE_API_URL + '/admin/i18n/info');
}
export function validateName(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ name: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('国际化编码已经存在'));
} else {
callback();
}
});
}
export function validateZhCn(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ zhCn: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('国际化中文已经存在'));
} else {
callback();
}
});
}
export function validateEn(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ en: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('国际化英文已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export const pageList = (params?: Object) => {
return request({
url: '/admin/log/page',
method: 'get',
params,
});
};
export const delObj = (ids: object) => {
return request({
url: '/admin/log',
method: 'delete',
data: ids,
});
};
import request from '/@/utils/request';
export const pageList = (params?: Object) => {
return request({
url: '/admin/menu/tree',
method: 'get',
params,
});
};
export const info = (id: String) => {
return request({
url: `/admin/menu/${id}`,
method: 'get',
});
};
export const save = (data: Object) => {
return request({
url: '/admin/menu',
method: 'post',
data: data,
});
};
export const putObj = (data: Object) => {
return request({
url: '/admin/menu',
method: 'put',
data: data,
});
};
export const addObj = (data: Object) => {
return request({
url: '/admin/menu',
method: 'post',
data: data,
});
};
export const delObj = (id: string) => {
return request({
url: '/admin/menu/' + id,
method: 'delete',
});
};
/**
* 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
* @method getAdminMenu 获取后端动态路由菜单(admin)
*/
export function useMenuApi() {
return {
getAdminMenu: (params?: object) => {
return request({
url: '/admin/menu',
method: 'get',
params,
});
},
};
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/admin/param/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/admin/param',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/admin/param/details/' + id,
method: 'get',
});
}
export function delObj(ids?: Object) {
return request({
url: '/admin/param',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/admin/param',
method: 'put',
data: obj,
});
}
export function refreshCache() {
return request({
url: '/admin/param/sync',
method: 'put',
});
}
export function getObjDetails(obj?: object) {
return request({
url: '/admin/param/details',
method: 'get',
params: obj,
});
}
export function validateParamsCode(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ publicKey: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('参数编码已经存在'));
} else {
callback();
}
});
}
export function validateParamsName(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ publicName: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('参数名称已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/admin/post/page',
method: 'get',
params: query,
});
}
export const list = (params?: Object) => {
return request({
url: '/admin/post/list',
method: 'get',
params,
});
};
export function addObj(obj?: Object) {
return request({
url: '/admin/post',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/admin/post/details/' + id,
method: 'get',
});
}
export function getObjDetails(obj?: object) {
return request({
url: '/admin/post/details',
method: 'get',
params: obj,
});
}
export function delObj(ids?: object) {
return request({
url: '/admin/post',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/admin/post',
method: 'put',
data: obj,
});
}
export function validatePostName(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ postName: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('岗位名称已经存在'));
} else {
callback();
}
});
}
export function validatePostCode(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ postCode: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('岗位编码已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export const list = (params?: Object) => {
return request({
url: '/admin/role/list',
method: 'get',
params,
});
};
export const pageList = (params?: Object) => {
return request({
url: '/admin/role/page',
method: 'get',
params,
});
};
export const deptRoleList = () => {
return request({
url: '/admin/role/list',
method: 'get',
});
};
export const getObj = (id: string) => {
return request({
url: '/admin/role/details/' + id,
method: 'get',
});
};
export const getObjDetails = (obj: object) => {
return request({
url: '/admin/role/details',
method: 'get',
params: obj,
});
};
export const addObj = (obj: Object) => {
return request({
url: '/admin/role',
method: 'post',
data: obj,
});
};
export const putObj = (obj: Object) => {
return request({
url: '/admin/role',
method: 'put',
data: obj,
});
};
export const delObj = (ids: Object) => {
return request({
url: '/admin/role',
method: 'delete',
data: ids,
});
};
export const permissionUpd = (roleId: string, menuIds: string) => {
return request({
url: '/admin/role/menu',
method: 'put',
data: {
roleId: roleId,
menuIds: menuIds,
},
});
};
export const fetchRoleTree = (roleId: string) => {
return request({
url: '/admin/menu/tree/' + roleId,
method: 'get',
});
};
export function validateRoleCode(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ roleCode: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('角色标识已经存在'));
} else {
callback();
}
});
}
export function validateRoleName(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ roleName: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('角色名称已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
// 系统缓存监控
export function systemCache() {
return request({
url: '/admin/system/cache',
method: 'get',
});
}
import request from '/@/utils/request';
export function fetchList(query: object) {
return request({
url: '/admin/sys-token/page',
method: 'post',
data: query,
});
}
export function delObj(accessTokens: string[]) {
return request({
url: '/admin/sys-token/delete',
method: 'delete',
data: accessTokens,
});
}
import request from '/@/utils/request';
export const pageList = (params?: Object) => {
return request({
url: '/admin/user/page',
method: 'get',
params,
});
};
export const addObj = (obj: Object) => {
return request({
url: '/admin/user',
method: 'post',
data: obj,
});
};
export const getObj = (id: String) => {
return request({
url: '/admin/user/details/' + id,
method: 'get',
});
};
export const delObj = (ids: Object) => {
return request({
url: '/admin/user',
method: 'delete',
data: ids,
});
};
export const putObj = (obj: Object) => {
return request({
url: '/admin/user',
method: 'put',
data: obj,
});
};
export function getDetails(obj: Object) {
return request({
url: '/admin/user/details',
method: 'get',
params: obj,
});
}
// 更改个人信息
export function editInfo(obj: Object) {
return request({
url: '/admin/user/edit',
method: 'put',
data: obj,
});
}
export function password(obj: Object) {
return request({
url: '/admin/user/password',
method: 'put',
data: obj,
});
}
export function UnbindingUser(type) {
return request({
url: '/admin/user/unbinding',
method: 'post',
params: {
type,
},
});
}
export function checkPassword(password: string) {
return request({
url: '/admin/user/check',
method: 'post',
params: {
password,
},
});
}
/**
* 注册用户
*/
export const registerUser = (userInfo: object) => {
return request({
url: '/admin/register/user',
method: 'post',
data: userInfo,
});
};
export function validateUsername(rule: any, value: any, callback: any, isEdit: boolean) {
const flag = new RegExp(/^([a-z\d]+?)$/).test(value);
if (!flag) {
callback(new Error('用户名支持小写英文、数字'));
}
if (isEdit) {
return callback();
}
getDetails({ username: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('用户名已经存在'));
} else {
callback();
}
});
}
export function validatePhone(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getDetails({ phone: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('手机号已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export function fetchList(query: any) {
return request({
url: '/job/sys-job-log/page',
method: 'get',
params: query,
});
}
export function delObjs(ids: object) {
return request({
url: '/job/sys-job-log',
method: 'delete',
data: ids,
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/job/sys-job/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/job/sys-job',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/job/sys-job/' + id,
method: 'get',
});
}
export function delObj(id?: string) {
return request({
url: '/job/sys-job/' + id,
method: 'delete',
});
}
export function putObj(obj?: Object) {
return request({
url: '/job/sys-job',
method: 'put',
data: obj,
});
}
export function startJobRa(jobId: string) {
return request({
url: '/job/sys-job/start-job/' + jobId,
method: 'post',
});
}
export function runJobRa(jobId: string) {
return request({
url: '/job/sys-job/run-job/' + jobId,
method: 'post',
});
}
export function shutDownJobRa(jobId: string) {
return request({
url: '/job/sys-job/shutdown-job/' + jobId,
method: 'post',
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/gen/dsconf/page',
method: 'get',
params: query,
});
}
export function list(query?: Object) {
return request({
url: '/gen/dsconf/list',
method: 'get',
params: query,
});
}
export function listTable(query?: Object) {
return request({
url: '/gen/dsconf/table/list',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/gen/dsconf',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/gen/dsconf/' + id,
method: 'get',
});
}
export function delObj(ids?: Object) {
return request({
url: '/gen/dsconf',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/gen/dsconf',
method: 'put',
data: obj,
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/gen/fieldtype/page',
method: 'get',
params: query,
});
}
export function list(query?: Object) {
return request({
url: '/gen/fieldtype/list',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/gen/fieldtype',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/gen/fieldtype/details/' + id,
method: 'get',
});
}
export function getObjDetails(obj?: object) {
return request({
url: '/gen/fieldtype/details',
method: 'get',
params: obj,
});
}
export function delObj(ids?: object) {
return request({
url: '/gen/fieldtype',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/gen/fieldtype',
method: 'put',
data: obj,
});
}
export function validateColumnType(rule: any, value: any, callback: any, isEdit: boolean) {
if (isEdit) {
return callback();
}
getObjDetails({ columnType: value }).then((response) => {
const result = response.data;
if (result !== null) {
callback(new Error('类型已经存在'));
} else {
callback();
}
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/gen/group/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/gen/group',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/gen/group/' + id,
method: 'get',
});
}
export function delObjs(ids?: Object) {
return request({
url: '/gen/group',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/gen/group',
method: 'put',
data: obj,
});
}
export function list() {
return request({
url: '/gen/group/list',
method: 'get',
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/gen/table/page',
method: 'get',
params: query,
});
}
export function addObj(obj?: Object) {
return request({
url: '/gen/table',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/gen/table/' + id,
method: 'get',
});
}
export function delObj(id?: string) {
return request({
url: '/gen/table/' + id,
method: 'delete',
});
}
export function putObj(obj?: Object) {
return request({
url: '/gen/table',
method: 'put',
data: obj,
});
}
export const useSyncTableApi = (dsName: string, tableName: string) => {
return request.get('/gen/table/sync/' + dsName + '/' + tableName);
};
export const useTableApi = (dsName: string, tableName: string) => {
return request.get('/gen/table/' + dsName + '/' + tableName);
};
export const useListTableApi = (dsName: string) => {
return request.get('/gen/table/list/' + dsName);
};
export const useListTableColumnApi = (dsName: string, tableName: string) => {
return request.get('/gen/table/column/' + dsName + '/' + tableName);
};
export const useTableFieldSubmitApi = (dsName: string, tableName: string, fieldList: any) => {
return request.put('/gen/table/field/' + dsName + '/' + tableName, fieldList);
};
export const useGeneratorCodeApi = (tableIds: any) => {
return request({
url: '/gen/generator/code',
method: 'get',
params: { tableIds: tableIds },
});
};
export const useGeneratorVFormApi = (dsName: any, tableName: any) => {
return request({
url: '/gen/generator/vform',
method: 'get',
params: { dsName: dsName, tableName: tableName },
});
};
export const useGeneratorVFormSfcApi = (id: string) => {
return request({
url: '/gen/generator/vform/sfc',
method: 'get',
params: { formId: id },
});
};
export const useGeneratorPreviewApi = (tableId: any) => {
return request({
url: '/gen/generator/preview',
method: 'get',
params: { tableId: tableId },
});
};
export function fetchDictList() {
return request({
url: '/admin/dict/list',
method: 'get',
});
}
export function useFormConfSaveApi(obj?: Object) {
return request({
url: '/gen/form',
method: 'post',
data: obj,
});
}
export function fetchFormList(query?: Object) {
return request({
url: '/gen/form/page',
method: 'get',
params: query,
});
}
export function fetchFormById(id?: string) {
return request({
url: '/gen/form/' + id,
method: 'get',
});
}
export function delFormObj(id?: string) {
return request({
url: '/gen/form/' + id,
method: 'delete',
});
}
import request from '/@/utils/request';
export function fetchList(query?: Object) {
return request({
url: '/gen/template/page',
method: 'get',
params: query,
});
}
export function list() {
return request({
url: '/gen/template/list',
method: 'get',
});
}
export function online() {
return request({
url: '/gen/template/online',
method: 'get',
});
}
export function checkVersion() {
return request({
url: '/gen/template/checkVersion',
method: 'get',
});
}
export function addObj(obj?: Object) {
return request({
url: '/gen/template',
method: 'post',
data: obj,
});
}
export function getObj(id?: string) {
return request({
url: '/gen/template/' + id,
method: 'get',
});
}
export function delObjs(ids?: Object) {
return request({
url: '/gen/template',
method: 'delete',
data: ids,
});
}
export function putObj(obj?: Object) {
return request({
url: '/gen/template',
method: 'put',
data: obj,
});
}
import request from '/@/utils/request';
import {Session} from '/@/utils/storage';
import {validateNull} from '/@/utils/validate';
import {useUserInfo} from '/@/stores/userInfo';
import other from '/@/utils/other';
/**
* https://www.ietf.org/rfc/rfc6749.txt
* OAuth 协议 4.3.1 要求格式为 form 而不是 JSON 注意!
*/
const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded';
/**
* 登录
* @param data
*/
export const login = (data: any) => {
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_PASSWORD_CLIENT);
Session.set('basicAuth', basicAuth);
let encPassword = data.password;
// 密码加密
if (import.meta.env.VITE_PWD_ENC_KEY) {
encPassword = other.encryption(data.password, import.meta.env.VITE_PWD_ENC_KEY);
}
return request({
url: '/auth/oauth2/token',
method: 'post',
data: {...data, password: encPassword},
headers: {
skipToken: true,
Authorization: basicAuth,
'Content-Type': FORM_CONTENT_TYPE,
},
});
};
export const loginByMobile = (mobile: any, code: any) => {
const grant_type = 'mobile';
const scope = 'server';
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_MOBILE_CLIENT);
Session.set('basicAuth', basicAuth);
return request({
url: '/auth/oauth2/token',
headers: {
skipToken: true,
Authorization: basicAuth,
'Content-Type': FORM_CONTENT_TYPE,
},
method: 'post',
data: {mobile: mobile, code: code, grant_type, scope},
});
};
export const loginBySocial = (state: string, code: string) => {
const grant_type = 'mobile';
const scope = 'server';
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_SOCIAL_CLIENT);
Session.set('basicAuth', basicAuth);
return request({
url: '/auth/oauth2/token',
headers: {
skipToken: true,
Authorization: basicAuth,
'Content-Type': FORM_CONTENT_TYPE,
},
method: 'post',
data: {mobile: state + '@' + code, code: code, grant_type, scope},
});
};
export const sendMobileCode = (mobile: any) => {
return request({
url: '/admin/mobile/' + mobile,
method: 'get',
});
};
export const refreshTokenApi = (refresh_token: string) => {
const grant_type = 'refresh_token';
const scope = 'server';
// 获取当前选中的 basic 认证信息
const basicAuth = Session.get('basicAuth');
return request({
url: '/auth/oauth2/token',
headers: {
skipToken: true,
Authorization: basicAuth,
'Content-Type': FORM_CONTENT_TYPE,
},
method: 'post',
data: {refresh_token, grant_type, scope},
});
};
/**
* 校验令牌,若有效期小于半小时自动续期
* @param refreshLock
*/
export const checkToken = (refreshTime: number, refreshLock: boolean) => {
const basicAuth = Session.get('basicAuth');
request({
url: '/auth/token/check_token',
headers: {
skipToken: true,
Authorization: basicAuth,
'Content-Type': FORM_CONTENT_TYPE,
},
method: 'get',
params: {token: Session.getToken()},
})
.then((response) => {
if (validateNull(response) || response.code === 1) {
clearInterval(refreshTime);
return;
}
const expire = Date.parse(response.data.expiresAt);
if (expire) {
const expiredPeriod = expire - new Date().getTime();
//小于半小时自动续约
if (expiredPeriod <= 30 * 60 * 1000) {
if (!refreshLock) {
refreshLock = true;
useUserInfo()
.refreshToken()
.catch(() => {
clearInterval(refreshTime);
});
refreshLock = false;
}
}
}
})
.catch(() => {
// 发生异常关闭定时器
clearInterval(refreshTime);
});
};
/**
* 获取用户信息
*/
export const getUserInfo = () => {
return request({
url: '/admin/user/info',
method: 'get',
});
};
export const logout = () => {
return request({
url: '/auth/token/logout',
method: 'delete',
});
};
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680436859217" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12985" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="45"><path d="M784.66 462.13V336.37l-168.79 95.52v160.12l168.79 96.49V562.74H927v-100.6H784.66zM615.87 851.55h109.47l1.49-122.18-110.96-65.05v187.23z m116.35-558.98L734.61 97H479.13l80.97 13.55 55.54 9.21 0.23 238.82 116.35-66.01zM106.16 851.55L548.53 927V172.46L97 97l9.16 754.55z" fill="#8a8a8a" p-id="12986"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680436627707" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4215" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="45"><path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m259.2-569.6H480c-12.8 0-25.6 12.8-25.6 25.6v64c0 12.8 12.8 25.6 25.6 25.6h176c12.8 0 25.6 12.8 25.6 25.6v12.8c0 41.6-35.2 76.8-76.8 76.8h-240c-12.8 0-25.6-12.8-25.6-25.6V416c0-41.6 35.2-76.8 76.8-76.8h355.2c12.8 0 25.6-12.8 25.6-25.6v-64c0-12.8-12.8-25.6-25.6-25.6H416c-105.6 0-188.8 86.4-188.8 188.8V768c0 12.8 12.8 25.6 25.6 25.6h374.4c92.8 0 169.6-76.8 169.6-169.6v-144c0-12.8-12.8-25.6-25.6-25.6z" fill="#888888" p-id="4216"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680436662087" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5326" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="45"><path d="M518.4 691.2c-99.2 0-182.4-64-185.6-185.6-6.4-112 92.8-188.8 188.8-192 108.8-3.2 176 121.6 176 121.6l281.6-102.4S864 32 547.2 32C252.8 35.2 48 236.8 48 512c0 243.2 192 489.6 489.6 476.8C867.2 976 979.2 688 979.2 688L688 592c3.2 3.2-57.6 99.2-169.6 99.2" fill="#8a8a8a" p-id="5327"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680436602191" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4038" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="45"><path d="M824.8 613.2c-16-51.4-34.4-94.6-62.7-165.3C766.5 262.2 689.3 112 511.5 112 331.7 112 256.2 265.2 261 447.9c-28.4 70.8-46.7 113.7-62.7 165.3-34 109.5-23 154.8-14.6 155.8 18 2.2 70.1-82.4 70.1-82.4 0 49 25.2 112.9 79.8 159-26.4 8.1-85.7 29.9-71.6 53.8 11.4 19.3 196.2 12.3 249.5 6.3 53.3 6 238.1 13 249.5-6.3 14.1-23.8-45.3-45.7-71.6-53.8 54.6-46.2 79.8-110.1 79.8-159 0 0 52.1 84.6 70.1 82.4 8.5-1.1 19.5-46.4-14.5-155.8z" p-id="4039"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680436062848" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2406" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M188.8704 576.192a187.712 187.712 0 0 1 188.928 189.12 189.184 189.184 0 0 1-188.928 190.912A189.184 189.184 0 0 1 0.0064 765.312a187.712 187.712 0 0 1 188.864-189.12z m359.68 250.176v81.472h-61.44a40.128 40.128 0 0 1-39.296-34.688l-0.448-6.016c0-20.48 14.72-37.376 33.92-40.32l5.888-0.384 61.44-0.064z m-359.68-169.664c-60.288 0-109.376 46.848-109.376 108.608 0 61.76 49.088 110.144 109.44 110.144 60.288 0 109.312-48.384 109.312-110.08 0-61.824-49.024-108.672-109.376-108.672z m359.68-59.392v81.408h-61.44a40.128 40.128 0 0 1-39.296-34.624L447.3664 638.08c0-20.48 14.72-37.376 33.92-40.32l5.888-0.448h61.44zM188.8704 64C293.1264 64 377.7984 149.568 377.7984 256.256A188.032 188.032 0 0 1 188.8064 445.76 188.032 188.032 0 0 1 0.0064 256.256C0.0064 149.568 84.7424 64 188.8704 64zM984.3264 342.784a40.32 40.32 0 0 1 39.744 40.704c0 20.48-14.72 37.376-33.92 40.32l-5.824 0.448H487.1744a40.32 40.32 0 0 1-39.808-40.768c0-20.416 14.72-37.376 33.92-40.256l5.888-0.448h497.088zM188.8064 147.584c-60.288 0-109.376 46.912-109.376 108.608 0 61.76 49.088 108.16 109.44 108.16 60.288 0 109.312-46.4 109.312-108.16 0-61.696-49.024-108.608-109.376-108.608z m795.392-33.92a40.32 40.32 0 0 1 39.744 40.768c0 20.416-14.72 37.312-33.92 40.256l-5.824 0.448H487.1744a40.32 40.32 0 0 1-39.808-40.704c0-20.48 14.72-37.376 33.92-40.32l5.888-0.448h497.088zM816.2624 918.528V847.36c0-10.56-16.192-15.872-30.08-15.872-13.824 0-33.152 3.84-33.152 15.872v71.168c0 9.088-6.144 15.104-15.36 15.104h-97.152c-23.104 0-40.064-16.64-40.064-37.824V752c0-9.088 12.8-15.104 22.08-15.104 9.216 0 21.76 6.016 21.76 15.104v133.12c0 4.48 3.072 7.552 9.216 7.552h59.392v-45.312c0-35.456 43.968-56.32 73.28-56.32 29.312 0 72 20.864 72 56.32v45.312h63.168c4.608 0 7.68 0 7.68-9.088V752c0-9.088 12.16-15.104 21.44-15.104 9.28 0 21.44 6.016 21.44 15.104v143.808c-1.536 22.656-16.96 37.824-40.064 37.824h-100.16c-9.28 0-15.424-6.016-15.424-15.104zM582.4064 712.256c-6.08-6.528-9.216-27.904-3.136-32.768l178.88-157.696c16.64-13.056 40.96-13.056 56.064 0l178.88 156.096c6.08 4.864 6.08 27.84 0 34.368-6.016 6.464-27.2 6.464-33.28 0l-163.776-142.72a18.24 18.24 0 0 0-18.176 0L615.0464 716.416c-3.008 3.264-6.016 3.264-9.088 3.264-4.48 0-20.544-2.56-23.616-7.424z" fill="#707070" p-id="2407"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1680436429435" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2757" width="45" height="45" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M405.333333 170.666667C228.693333 170.666667 85.333333 285.44 85.333333 426.666667c0 80.64 46.08 151.893333 118.613334 198.826666L170.666667 725.333333l106.666666-64c37.973333 13.226667 79.786667 21.333333 124.16 21.333334A222.72 222.72 0 0 1 384 597.333333c0-141.226667 133.546667-256 298.666667-256 8.106667 0 16.213333 0 23.893333 1.28C663.04 242.773333 545.28 170.666667 405.333333 170.666667m-128 106.666666c23.466667 0 42.666667 19.2 42.666667 42.666667s-19.2 42.666667-42.666667 42.666667-42.666667-19.2-42.666666-42.666667 19.2-42.666667 42.666666-42.666667m213.333334 0c23.466667 0 42.666667 19.2 42.666666 42.666667s-19.2 42.666667-42.666666 42.666667-42.666667-19.2-42.666667-42.666667 19.2-42.666667 42.666667-42.666667M682.666667 384c-141.226667 0-256 95.573333-256 213.333333s114.773333 213.333333 256 213.333334c28.586667 0 55.893333-3.413333 81.493333-10.666667L853.333333 853.333333l-26.453333-79.786666C893.866667 734.72 938.666667 670.293333 938.666667 597.333333c0-117.76-114.773333-213.333333-256-213.333333m-85.333334 106.666667c23.466667 0 42.666667 19.2 42.666667 42.666666s-19.2 42.666667-42.666667 42.666667-42.666667-19.2-42.666666-42.666667 19.2-42.666667 42.666666-42.666666m170.666667 0c23.466667 0 42.666667 19.2 42.666667 42.666666s-19.2 42.666667-42.666667 42.666667-42.666667-19.2-42.666667-42.666667 19.2-42.666667 42.666667-42.666666z" fill="#8a8a8a" p-id="2758"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1677809803390" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2360" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M891.2 326.4l-121.6 69.6v-144c0-33.6-27.2-60.8-60.8-60.8h-560c-33.6 0-60.8 27.2-60.8 60.8v545.6c0 33.6 27.2 60.8 60.8 60.8h560.8c33.6 0 60.8-27.2 60.8-60.8v-144l121.6 69.6c20 12 45.6-3.2 45.6-26.4v-344c-0.8-23.2-25.6-37.6-46.4-26.4z m-188.8 464H156V260h545.6v530.4z m166.4-157.6L770.4 576V475.2l98.4-56.8v214.4z" p-id="2361"></path><path d="M426.4 600c-0.8 0.8-1.6 1.6-1.6 2.4-5.6 8-10.4 16.8-16.8 24.8-7.2 10.4-16 20.8-25.6 29.6-8 7.2-16.8 13.6-27.2 17.6-6.4 2.4-13.6 3.2-20.8 2.4-6.4-0.8-12-4-16.8-8.8-6.4-5.6-11.2-12.8-15.2-20.8-3.2-6.4-7.2-13.6-9.6-20-3.2-8-7.2-16.8-9.6-25.6-3.2-10.4-6.4-20-9.6-30.4-3.2-10.4-5.6-21.6-8-32-2.4-8.8-4-17.6-5.6-26.4-1.6-8-2.4-16-4-24-0.8-6.4-1.6-13.6-2.4-20 0-2.4 0-4-0.8-6.4-0.8-12-0.8-24 0-35.2 0.8-9.6 2.4-18.4 6.4-27.2s9.6-16 18.4-20.8c7.2-4 14.4-4.8 21.6-3.2 8.8 1.6 16.8 5.6 24.8 11.2 8.8 6.4 16 13.6 23.2 20.8 8 8.8 15.2 17.6 22.4 27.2 8 10.4 15.2 21.6 22.4 32.8 9.6 15.2 18.4 30.4 28 45.6 2.4 3.2 4 6.4 5.6 10.4 0 0.8 0.8 0.8 0.8 1.6 2.4-4 4.8-7.2 7.2-11.2 10.4-17.6 21.6-35.2 33.6-52.8 10.4-15.2 20.8-30.4 32.8-44.8 8-9.6 16-18.4 25.6-25.6 7.2-5.6 14.4-10.4 23.2-12.8 12-4 23.2-2.4 33.6 6.4 5.6 4.8 9.6 10.4 12 16.8 3.2 8 4.8 16 6.4 24 1.6 12 0.8 23.2 0 35.2-0.8 7.2-0.8 13.6-2.4 20.8-1.6 12-3.2 23.2-5.6 35.2-1.6 8-3.2 16.8-5.6 24.8l-7.2 28.8c-2.4 8.8-5.6 18.4-8.8 27.2-4 12.8-9.6 25.6-15.2 37.6-4 8-8 16-12.8 23.2-4 5.6-8.8 10.4-14.4 14.4-6.4 4.8-14.4 5.6-22.4 4.8-12-1.6-21.6-7.2-31.2-14.4-11.2-8.8-20.8-20-29.6-32-6.4-8.8-12.8-18.4-18.4-28 0.8-1.6 0-2.4-0.8-3.2z m139.2-156.8c-0.8-5.6-0.8-11.2-1.6-16.8l-2.4-9.6c-0.8-3.2-3.2-4-6.4-2.4-1.6 0.8-2.4 1.6-4 2.4-7.2 4.8-12.8 11.2-18.4 16.8-12.8 13.6-23.2 28.8-33.6 44-9.6 14.4-18.4 28.8-27.2 43.2-7.2 12-14.4 24-21.6 35.2-3.2 4.8-3.2 4.8 0 10.4 4.8 9.6 10.4 19.2 16.8 28.8 8 12.8 16.8 24.8 28 35.2 2.4 2.4 5.6 4.8 8.8 7.2 4 2.4 8 1.6 10.4-2.4 0-0.8 0.8-0.8 0.8-1.6 6.4-9.6 10.4-20.8 15.2-31.2l9.6-26.4c2.4-6.4 4-12.8 5.6-19.2 3.2-12.8 6.4-24.8 9.6-37.6 2.4-9.6 4-20 5.6-29.6 1.6-8 2.4-15.2 3.2-23.2 0.8-8 1.6-16 1.6-23.2z m-160 117.6c-0.8-0.8-0.8-2.4-1.6-3.2-4-6.4-7.2-12.8-11.2-19.2-8.8-14.4-17.6-29.6-26.4-44-8.8-14.4-18.4-28.8-28.8-42.4-8-11.2-16.8-21.6-27.2-31.2-3.2-3.2-6.4-5.6-9.6-8-3.2-2.4-6.4-1.6-8 2.4-0.8 1.6-1.6 3.2-1.6 4.8-0.8 4.8-1.6 10.4-1.6 15.2-0.8 10.4 0.8 21.6 1.6 32 0.8 8.8 2.4 17.6 4 27.2 1.6 10.4 4 20.8 6.4 31.2 2.4 9.6 4.8 20 7.2 29.6 3.2 10.4 6.4 20.8 9.6 32 4 12 8 23.2 13.6 34.4 2.4 4 4.8 8.8 7.2 12.8 1.6 3.2 5.6 4.8 9.6 2.4 3.2-2.4 7.2-4.8 10.4-8 10.4-9.6 18.4-20 25.6-32 5.6-8 10.4-16 15.2-24.8 1.6-2.4 3.2-6.4 5.6-11.2z" p-id="2362"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1677809829053" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3364" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M873.066667 533.333333h-39.466667c-4.202667 0-8.256 0.576-12.266667 1.237334V433.066667C821.333333 264.853333 682.56 128 512 128S202.666667 264.853333 202.666667 433.066667v101.504A75.434667 75.434667 0 0 0 190.378667 533.333333H150.954667A76.373333 76.373333 0 0 0 74.666667 609.6v210.133333A76.373333 76.373333 0 0 0 150.954667 896h39.445333a76.373333 76.373333 0 0 0 76.288-76.266667V433.066667C266.666667 300.138667 376.725333 192 512 192s245.333333 108.138667 245.333333 241.066667v386.666666A76.352 76.352 0 0 0 833.6 896h39.466667a76.352 76.352 0 0 0 76.266666-76.266667v-210.133333A76.373333 76.373333 0 0 0 873.066667 533.333333zM202.666667 714.666667v105.066666a12.288 12.288 0 0 1-12.288 12.266667H150.954667a12.288 12.288 0 0 1-12.288-12.266667v-210.133333c0-6.762667 5.504-12.266667 12.288-12.266667h39.445333c6.762667 0 12.266667 5.504 12.266667 12.266667V714.666667z m682.666666 105.066666a12.288 12.288 0 0 1-12.266666 12.266667h-39.466667a12.288 12.288 0 0 1-12.266667-12.266667v-210.133333c0-6.762667 5.504-12.266667 12.266667-12.266667h39.466667c6.762667 0 12.266667 5.504 12.266666 12.266667v210.133333z" fill="#232323" p-id="3365"></path><path d="M682.666667 578.666667h-85.333334c-12.394667 0-23.658667 7.168-28.949333 18.368l-9.472 20.138666-52.501333-125.546666c-5.056-12.074667-17.642667-19.477333-30.101334-19.648a32.021333 32.021333 0 0 0-29.376 20.736l-32.362666 85.930666H341.333333a32 32 0 1 0 0 64h95.381334a32 32 0 0 0 29.952-20.736l11.776-31.274666 49.216 117.696c4.885333 11.669333 16.170667 19.349333 28.821333 19.648h0.704a32 32 0 0 0 28.949333-18.368l31.530667-66.965334H682.666667a32 32 0 0 0 0-63.978666z" fill="#232323" p-id="3366"></path></svg>
\ No newline at end of file
<svg width="100" height="969"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" shape-rendering="auto" preserveAspectRatio="none" class="layout-footer-waves">
<g id="Layer_1">
<title>Layer 1</title>
<g transform="rotate(2.18686 -4.09974 506.2)" stroke="null" id="svg_5">
<defs stroke="null" transform="translate(0.00121616 0.0635973) translate(-0.844727 0.185777) translate(-4.69751 0.0674522) translate(4.80739 0.470252) translate(4.47211 0.0666505) translate(-47.1425 4.28569) translate(7.74305 4.90188) translate(-28.5712 -22.857) translate(-0.219291 4.06319) translate(-1.32311 -4.516) translate(22.5706 18.2109) translate(-49.4342 -5.96675) translate(-0.909089 0) translate(0.909089 0) translate(-0.909089 0) translate(-0.909089 0) translate(-0.909089 0) translate(-3.33335 0) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(0 -3.33335) translate(-7.34665 -0.396492) translate(-1.66666 0) translate(-1.66666 0) translate(-0.12551 2.32567) translate(-0.707827 0.218105) translate(-1.66666 0) translate(1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-2.15731 0.0264734) translate(1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(0 -1.66666) translate(0 -1.66666) translate(1.66666 0) translate(0 -1.66666) translate(1.66666 0) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(1.66666 0) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(0 -1.66666) translate(0 -1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(-1.66666 0) translate(0 1.66666) translate(-1.66666 0) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(0 -1.66666) translate(0 -1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(0 -1.66666) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.66666 0) translate(-1.56355 1.02885) translate(-7.50004 -25.0001) translate(-3.19245 0.846904) translate(5.00003 -97.5005) translate(-9.62431 -0.829817) translate(27.5002 -220.001) translate(-6.96615 1.62281) translate(1.10472 -2.23004) translate(-9.63847 0.899196) translate(7.04019 0.0560654) translate(-11.25 -16.25) translate(2.11523 2.02597) translate(-27.1193 -20.3394) translate(-10.8798 -0.397069) translate(-44.0688 -110.172) translate(-1.69495 5.08486) translate(-8.57191 2.416) translate(-67.7982 -69.4931) translate(-0.127168 3.4846) translate(-16.9495 -283.057) translate(-4.64027 -0.169348) translate(1 0) translate(71.5022 0) translate(0 127.337) translate(-78.8263 -168.947) scale(0.564803 0.937256) translate(78.8263 168.947) translate(-139.564 1126.64) scale(1 0.752632) translate(139.564 -1126.64) translate(-139.564 190.036) scale(1 0.902375) translate(139.564 -190.036) translate(-139.564 210.595) scale(1.30003 1) translate(139.564 -210.595) translate(-107.355 1517.49) scale(1 1.16096) translate(107.355 -1517.49) translate(-107.355 0.205247) scale(1 1.13183) translate(107.355 -0.205247) translate(-136.758 -338.436) scale(1.50148 1) translate(136.758 338.436) translate(-152.694 -417.354) scale(1.2612 1.21266) translate(152.694 417.354) translate(-160.148 -445.389) scale(1 1.22519) translate(160.148 445.389) translate(-187.477 -380.211) scale(1.1479 0.963065) translate(187.477 380.211) translate(-169.043 -406.673) scale(0.986706 0.876627) translate(169.043 406.673) translate(-166.85 -463.854) scale(1.08382 1.19196) translate(166.85 463.854) translate(-159.592 918.456) scale(0.859634 1.01716) translate(159.592 -918.456) translate(-184.899 -405.678) scale(1.135 1.11385) translate(184.899 405.678) translate(-150.577 -518.346) scale(0.986699 1.09676) translate(150.577 518.346) translate(-155.421 -535.896) scale(1.04426 1.02871) translate(155.421 535.896) translate(-155.064 -536.049) scale(1.0463 1.01338) translate(155.064 536.049) translate(-165.783 -521.136) scale(1.00568 1.01887) translate(165.783 521.136) translate(-170.657 -511.469) scale(1.01013 1.00598) translate(170.657 511.469) translate(-169.334 -508.298) scale(1.09126 1) translate(169.334 508.298) translate(-156.911 -506.897) scale(1 1.06292) translate(156.911 506.897) translate(-163.644 765.54) scale(0.881707 0.600266) translate(163.644 -765.54) translate(45.8836 -37.1657) scale(0.309 0.684766) translate(-45.8836 37.1657) translate(-69.5385 1277.7) scale(0.415094 0.969376) translate(69.5385 -1277.7) translate(-173.403 4.77105) scale(2.29193 1) translate(173.403 -4.77105) translate(-131.465 -21.955) scale(1.62026 0.838547) translate(131.465 21.955) translate(-128.274 -10.6013) scale(0.965372 1.26492) translate(128.274 10.6013) translate(-127.333 -8.29175) scale(0.874565 1.22473) translate(127.333 8.29175) translate(-138.784 -6.2554) scale(1.00883 0.82026) translate(138.784 6.2554) translate(-144.168 -7.53605) scale(1.05722 1.04501) translate(144.168 7.53605) translate(-137.487 -6.97423) scale(1.01704 0.999876) translate(137.487 6.97423)">
<path stroke="null" id="svg_3" d="m-160,44c30,0 58,-18 88,-18s58,18 88,18s58,-18 88,-18s58,18 88,18l0,44l-352,0l0,-44z"/>
</defs>
<g stroke="null" id="svg_6">
<title stroke="null">Layer 1</title>
<g stroke="null" transform="matrix(-0.0217456 0.782606 -0.765199 -0.0222404 584.591 1129.22)" class="layout-footer-waves-g" id="svg_1">
<use stroke="null" href="#svg_3" x="-328.61098" y="405.36815" fill="rgba(211, 239, 255, 1)" id="svg_4" transform="matrix(3.69628 0 0 3.42743 381.903 -787.25)"/>
<use stroke="null" href="#svg_3" x="-333.61098" y="401.76815" fill="rgba(211, 239, 255, 0.5)" id="svg_2" transform="matrix(3.69628 0 0 3.42743 381.903 -787.25)"/>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64px" height="64px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:0.996" fill="#80bb58" d="M 63.5,35.5 C 63.5,37.8333 63.5,40.1667 63.5,42.5C 62.0168,46.1468 59.3501,48.6468 55.5,50C 43.1211,50.186 30.7877,50.686 18.5,51.5C 9.28752,51.7428 2.95418,47.7428 -0.5,39.5C -0.5,37.5 -0.5,35.5 -0.5,33.5C 1.21619,26.6281 5.71619,22.6281 13,21.5C 20.6091,11.8561 29.7758,10.0227 40.5,16C 44.3698,19.3546 46.7031,23.5212 47.5,28.5C 54.366,27.6047 59.6994,29.9381 63.5,35.5 Z M 25.5,17.5 C 36.8229,16.822 42.8229,22.1553 43.5,33.5C 47.5138,33.3345 51.5138,33.5012 55.5,34C 59.5,37.6667 59.5,41.3333 55.5,45C 44.4272,45.9308 33.4272,45.5975 22.5,44C 12.5525,33.8629 13.5525,25.0296 25.5,17.5 Z"/></g>
<g><path style="opacity:0.991" fill="#86be5f" d="M 39.5,33.5 C 43.0131,34.0282 44.0131,35.8615 42.5,39C 37.2894,39.5464 36.2894,37.713 39.5,33.5 Z"/></g>
</svg>
// base color
$blue: #324157;
$light-blue: #3a71a8;
$red: #c03639;
$pink: #e65d6e;
$green: #30b08f;
$tiffany: #4ab7bd;
$yellow: #fec171;
$panGreen: #30b08f;
// 默认菜单主题风格
$base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528;
// 自定义暗色菜单风格
/**
$base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff;
$base-menu-background:#001529;
$base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528;
*/
$--color-primary: #409eff;
$--color-success: #67c23a;
$--color-warning: #e6a23c;
$--color-danger: #f56c6c;
$--color-info: #909399;
$base-sidebar-width: 200px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuColor: $base-menu-color;
menuLightColor: $base-menu-light-color;
menuColorActive: $base-menu-color-active;
menuBackground: $base-menu-background;
menuLightBackground: $base-menu-light-background;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color;
primaryColor: $--color-primary;
successColor: $--color-success;
dangerColor: $--color-danger;
infoColor: $--color-info;
warningColor: $--color-warning;
}
<template>
<div></div>
</template>
<script setup lang="ts" name="check-token">
import { checkToken } from '/@/api/login';
const refreshLock = ref(false);
const refreshTime = ref();
onMounted(() => {
refreshToken();
});
const refreshToken = () => {
refreshTime.value = setInterval(() => {
checkToken(refreshTime.value, refreshLock.value);
}, 60000);
};
</script>
<template>
<el-cascader :options="optionsData" :disabled="disabled" v-model="selectedOptions" @change="handleChange" />
</template>
<script setup lang="ts" name="china-area">
import { provinceAndCityData, provinceAndCityDataPlus, regionData, regionDataPlus } from '/@/utils/chinaArea';
const emit = defineEmits(['update:modelValue', 'change']);
const optionsData = ref();
const props = defineProps({
// 当前的值
modelValue: String,
// 类型 (二级联动,三级联动)
type: {
type: Number,
default: 3,
},
plus: {
type: Boolean,
default: false,
},
// 是否禁用
disabled: {
type: Boolean,
default: () => false,
},
});
const selectedOptions = computed({
get: () => {
return props.modelValue?.split(',');
},
set: (val) => {
emit('update:modelValue', val?.join(','));
},
});
const handleChange = (value: String[]) => {
emit('change', value.join(','));
};
onMounted(() => {
const { plus, type } = props;
if (plus) {
optionsData.value = type === 2 ? provinceAndCityDataPlus : regionDataPlus;
} else {
optionsData.value = type === 2 ? provinceAndCityData : regionData;
}
});
</script>
<!--
* @Descripttion: 代码编辑器
* @version: 1.0
* @Author: sakuya
* @Date: 2022年5月20日21:46:29
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="code-editor" :style="{ height: _height }">
<textarea ref="textarea" v-model="contentValue"></textarea>
</div>
</template>
<script>
import { markRaw } from 'vue';
//框架
import CodeMirror from 'codemirror';
import 'codemirror/lib/codemirror.css';
//主题
import 'codemirror/theme/idea.css';
import 'codemirror/theme/darcula.css';
//功能
import 'codemirror/addon/selection/active-line';
//语言
import 'codemirror/mode/velocity/velocity';
import 'codemirror/mode/go/go';
export default {
props: {
modelValue: {
type: String,
default: '',
},
mode: {
type: String,
default: 'go',
},
height: {
type: [String, Number],
default: 300,
},
options: {
type: Object,
default: () => {},
},
theme: {
type: String,
default: 'idea',
},
readOnly: {
type: Boolean,
default: false,
},
},
data() {
return {
contentValue: this.modelValue,
coder: null,
opt: {
theme: this.theme, //主题
styleActiveLine: true, //高亮当前行
lineNumbers: true, //行号
lineWrapping: false, //自动换行
tabSize: 4, //Tab缩进
indentUnit: 4, //缩进单位
indentWithTabs: true, //自动缩进
mode: this.mode, //语言
readOnly: this.readOnly, //只读
...this.options,
},
};
},
computed: {
_height() {
return Number(this.height) ? Number(this.height) + 'px' : this.height;
},
},
watch: {
modelValue(val) {
this.contentValue = val;
if (val !== this.coder.getValue()) {
this.coder.setValue(val);
}
},
},
mounted() {
this.init();
//获取挂载的所有modes
//console.log(CodeMirror.modes)
},
methods: {
init() {
this.coder = markRaw(CodeMirror.fromTextArea(this.$refs.textarea, this.opt));
this.coder.on('change', (coder) => {
this.contentValue = coder.getValue();
this.$emit('update:modelValue', this.contentValue);
});
},
formatStrInJson(strValue) {
return JSON.stringify(JSON.parse(strValue), null, 4);
},
},
};
</script>
<style scoped>
.code-editor {
font-size: 14px;
border: 1px solid #ddd;
line-height: 150%;
}
.code-editor:deep(.CodeMirror) {
height: 100%;
}
</style>
<template>
<div class="color-picker flex flex-1">
<el-color-picker v-model="color" :predefine="predefineColors" />
<el-input v-model="color" class="mx-[10px] flex-1" type="text" readonly />
<el-button type="text" @click="reset">重置</el-button>
</div>
</template>
<script lang="ts" setup>
const props = defineProps({
modelValue: {
type: String,
},
defaultColor: {
type: String,
},
});
const emit = defineEmits<{
(event: 'update:modelValue', value: any): void;
}>();
const color = computed({
get() {
return props.modelValue;
},
set(value) {
emit('update:modelValue', value);
},
});
const predefineColors = ['#409EFF', '#28C76F', '#EA5455', '#FF9F43', '#01CFE8', '#4A5DFF'];
const reset = () => {
color.value = props.defaultColor;
};
</script>
<template>
<div class="del-wrap">
<slot></slot>
<div v-if="showClose" class="icon-close" @click.stop="handleClose">
<el-icon><CloseBold /></el-icon>
</div>
</div>
</template>
<script lang="ts">
export default defineComponent({
props: {
showClose: {
type: Boolean,
default: true,
},
},
emits: ['close'],
setup(props, { emit }) {
const handleClose = () => {
emit('close');
};
return {
handleClose,
};
},
});
</script>
<style scoped lang="scss">
.del-wrap {
position: relative;
&:hover > .icon-close {
display: flex;
}
.icon-close {
display: none;
position: absolute;
top: -8px;
right: -8px;
width: 16px;
height: 16px;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
border-radius: 50%;
color: #fff;
cursor: pointer;
}
}
</style>
<template>
<div>
<template v-for="(item, index) in props.options">
<template v-if="values.includes(item.value || item)">
<span v-if="item.elTagType == 'default' || item.elTagType == ''" :key="index" :index="index" :class="item.elTagClass">{{
item.label || item
}}</span>
<el-tag
v-else
:disable-transitions="true"
:key="index * 2"
:index="index"
:type="item.elTagType === 'primary' ? '' : item.elTagType"
:class="item.elTagClass"
>{{ item.label || item }}</el-tag
>
</template>
</template>
</div>
</template>
<script setup lang="ts" name="dict-tag">
import { computed } from 'vue';
const props = defineProps({
// 数据
options: {
type: Array as any,
default: null,
},
// 当前的值
value: [Number, String, Array],
});
const values = computed(() => {
if (props.value !== null && typeof props.value !== 'undefined') {
return Array.isArray(props.value) ? props.value : [String(props.value)];
} else {
return [];
}
});
</script>
<style scoped>
.el-tag + .el-tag {
margin-left: 10px;
}
</style>
<template>
<div class="flex flex-col border border-br" :style="styles">
<Toolbar class="border-b border-br" :editor="editorRef" :mode="mode" />
<Editor
class="flex-1 overflow-y-auto"
:mode="mode"
:defaultConfig="state.editorConfig"
v-model="state.editorVal"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</template>
<script setup lang="ts" name="wngEditor">
import '@wangeditor/editor/dist/css/style.css';
import { reactive, shallowRef, watch, onBeforeUnmount, CSSProperties } from 'vue';
// @ts-ignore
import { IDomEditor } from '@wangeditor/editor';
import { Toolbar, Editor } from '@wangeditor/editor-for-vue';
import { Session } from '/@/utils/storage';
import other from '/@/utils/other';
const { proxy } = getCurrentInstance();
// 定义父组件传过来的值
const props = defineProps({
// 是否禁用
disable: {
type: Boolean,
default: () => false,
},
// 内容框默认 placeholder
placeholder: {
type: String,
default: () => '请输入内容...',
},
// https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F
// 模式,可选 <default|simple>,默认 default
mode: {
type: String,
default: () => 'default',
},
// 高度
height: {
type: String,
default: () => '310',
},
// 宽度
width: {
type: String,
default: () => 'auto',
},
// 双向绑定,用于获取 editor.getHtml()
getHtml: String,
// 双向绑定,用于获取 editor.getText()
getText: String,
uploadFileUrl: {
type: String,
default: `/admin/sys-file/upload`,
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['update:getHtml', 'update:getText']);
// 定义上传需要的请求头信息
const headers = computed(() => {
return {
Authorization: 'Bearer ' + Session.get('token'),
'TENANT-ID': Session.getTenant(),
};
});
// 定义上传需要的字段信息
const uploadAttr = reactive({
fieldName: 'file',
server: proxy.baseURL + props.uploadFileUrl,
headers: headers,
customInsert(res, insertFn) {
insertFn(proxy.baseURL + res.data.url);
},
});
const editorRef = shallowRef();
const state = reactive({
editorConfig: {
placeholder: props.placeholder,
MENU_CONF: {
uploadImage: uploadAttr,
uploadVideo: uploadAttr,
},
},
editorVal: props.getHtml,
});
const styles = computed<CSSProperties>(() => ({
height: other.addUnit(props.height),
width: other.addUnit(props.width),
}));
// 编辑器回调函数
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor;
};
// 编辑器内容改变时
const handleChange = (editor: IDomEditor) => {
emit('update:getHtml', editor.getHtml());
emit('update:getText', editor.getText());
};
// 页面销毁时
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
// 监听是否禁用改变
watch(
() => props.disable,
(bool) => {
const editor = editorRef.value;
if (editor == null) return;
bool ? editor.disable() : editor.enable();
},
{
deep: true,
}
);
// 监听双向绑定值改变,用于回显
watch(
() => props.getHtml,
(val) => {
state.editorVal = val;
},
{
deep: true,
}
);
</script>
<template>
<div class="form-table" ref="scFormTable">
<el-table
:data="data"
ref="table"
border
stripe
:cell-style="{ textAlign: 'center' }"
:header-cell-style="{
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)',
}"
>
<el-table-column type="index" width="50" fixed="left">
<template #header>
<el-button v-if="!hideAdd" type="primary" icon="el-icon-plus" size="small" circle @click="rowAdd"></el-button>
<el-tooltip v-else content="序号" placement="top"> # </el-tooltip>
</template>
<template #default="scope">
<div :class="['form-table-handle', { 'form-table-handle-delete': !hideDelete }]">
<span>{{ scope.$index + 1 }}</span>
<el-button
v-if="!hideDelete"
type="danger"
icon="el-icon-delete"
size="small"
plain
circle
@click="rowDel(scope.row, scope.$index)"
></el-button>
</div>
</template>
</el-table-column>
<el-table-column label="" width="50" v-if="dragSort">
<template #header>
<el-icon>
<el-tooltip content="拖动排序" placement="top">
<WarningFilled />
</el-tooltip>
</el-icon>
</template>
<template #default>
<div class="move" style="cursor: move">
<el-icon>
<Sort />
</el-icon>
</div>
</template>
</el-table-column>
<slot></slot>
<template #empty>
{{ placeholder }}
</template>
</el-table>
</div>
</template>
<script>
import Sortable from 'sortablejs';
export default {
props: {
/**
* 表格数据
*/
modelValue: { type: Array, default: () => [] },
/**
* 新增行模板
*/
addTemplate: { type: Object, default: () => {} },
/**
* 无数据时的提示语
*/
placeholder: { type: String, default: '暂无数据' },
/**
* 是否启用拖拽排序
*/
dragSort: { type: Boolean, default: false },
/**
* 是否隐藏新增按钮
*/
hideAdd: { type: Boolean, default: false },
/**
* 是否隐藏删除按钮
*/
hideDelete: { type: Boolean, default: false },
},
data() {
return {
/**
* 表格数据
*/
data: [],
};
},
mounted() {
this.data = this.modelValue;
if (this.dragSort) {
this.rowDrop();
}
},
watch: {
modelValue() {
this.data = this.modelValue;
},
data: {
handler() {
/**
* 更新表格数据
* @event update:modelValue
* @type {Array}
*/
this.$emit('update:modelValue', this.data);
},
deep: true,
},
},
methods: {
/**
* 启用表格行拖拽排序
*/
rowDrop() {
const _this = this;
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody');
Sortable.create(tbody, {
handle: '.move',
animation: 300,
ghostClass: 'ghost',
onEnd({ newIndex, oldIndex }) {
_this.data.splice(newIndex, 0, _this.data.splice(oldIndex, 1)[0]);
const newArray = _this.data.slice(0);
const tmpHeight = _this.$refs.scFormTable.offsetHeight;
_this.$refs.scFormTable.style.setProperty('height', tmpHeight + 'px');
_this.data = [];
_this.$nextTick(() => {
_this.data = newArray;
_this.$nextTick(() => {
_this.$refs.scFormTable.style.removeProperty('height');
});
});
},
});
},
/**
* 新增一行
*/
rowAdd() {
const temp = JSON.parse(JSON.stringify(this.addTemplate));
this.data.push(temp);
},
/**
* 删除一行
* @param {Object} row - 要删除的行数据
* @param {number} index - 要删除的行的索引
*/
rowDel(row, index) {
this.data.splice(index, 1);
this.$emit('delete', row);
},
/**
* 插入一行
* @param {Object} row - 要插入的行数据,默认为新增行模板
*/
pushRow(row) {
const temp = row || JSON.parse(JSON.stringify(this.addTemplate));
this.data.push(temp);
},
/**
* 根据索引删除一行
* @param {number} index - 要删除的行的索引
*/
deleteRow(index) {
this.data.splice(index, 1);
},
},
};
</script>
<style scoped>
.form-table {
width: 100%;
}
.form-table .form-table-handle {
text-align: center;
}
.form-table .form-table-handle span {
display: inline-block;
}
.form-table .form-table-handle button {
display: none;
}
.form-table .hover-row .form-table-handle-delete span {
display: none;
}
.form-table .hover-row .form-table-handle-delete button {
display: inline-block;
}
.form-table .move {
text-align: center;
font-size: 14px;
margin-top: 3px;
}
</style>
import { readFileSync, readdirSync } from 'fs';
let idPerfix = '';
const iconNames: string[] = [];
const svgTitle = /<svg([^>+].*?)>/;
const clearHeightWidth = /(width|height)="([^>+].*?)"/g;
const hasViewBox = /(viewBox="[^>+].*?")/g;
const clearReturn = /(\r)|(\n)/g;
// 清理 svg 的 fill
const clearFill = /(fill="[^>+].*?")/g;
function findSvgFile(dir: string): string[] {
const svgRes = [] as any;
const dirents = readdirSync(dir, {
withFileTypes: true,
});
for (const dirent of dirents) {
iconNames.push(`${idPerfix}-${dirent.name.replace('.svg', '')}`);
if (dirent.isDirectory()) {
svgRes.push(...findSvgFile(dir + dirent.name + '/'));
} else {
const svg = readFileSync(dir + dirent.name)
.toString()
.replace(clearReturn, '')
.replace(clearFill, 'fill=""')
.replace(svgTitle, ($1, $2) => {
let width = 0;
let height = 0;
let content = $2.replace(clearHeightWidth, (s1: string, s2: string, s3: number) => {
if (s2 === 'width') {
width = s3;
} else if (s2 === 'height') {
height = s3;
}
return '';
});
if (!hasViewBox.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`;
}
return `<symbol id="${idPerfix}-${dirent.name.replace('.svg', '')}" ${content}>`;
})
.replace('</svg>', '</symbol>');
svgRes.push(svg);
}
}
return svgRes;
}
export const svgBuilder = (path: string, perfix = 'local') => {
if (path === '') return;
idPerfix = perfix;
const res = findSvgFile(path);
return {
name: 'svg-transform',
transformIndexHtml(html: string) {
/* eslint-disable */
return html.replace(
'<body>',
`
<body>
<svg id="local-icon" data-icon-name="${iconNames.join(
','
)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join('')}
</svg>
`
);
/* eslint-enable */
},
};
};
<template>
<div class="icon-selector w100 h100">
<el-input
v-model="state.fontIconSearch"
:placeholder="state.fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
@clear="onClearFontIcon"
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<SvgIcon :name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14" />
</template>
</el-input>
<el-popover
placement="bottom"
:width="state.fontIconWidth"
transition="el-zoom-in-top"
popper-class="icon-selector-popper"
trigger="click"
:virtual-ref="inputWidthRef"
virtual-triggering
>
<template #default>
<div class="icon-selector-warp">
<div class="icon-selector-warp-title">{{ title }}</div>
<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
<el-tab-pane lazy label="ali" name="ali">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="ele" name="ele">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="awe" name="awe">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="local" name="local">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
</el-tabs>
</div>
</template>
</el-popover>
</div>
</template>
<script setup lang="ts" name="iconSelector">
import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
import type { TabsPaneContext } from 'element-plus';
import initIconfont from '/@/utils/getStyleSheets';
import '/@/theme/iconSelector.scss';
// 定义父组件传过来的值
const props = defineProps({
// 输入框前置内容
prepend: {
type: String,
default: () => 'ele-Pointer',
},
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'default',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 是否可清空
clearable: {
type: Boolean,
default: () => true,
},
// 自定义空状态描述文字
emptyDescription: {
type: String,
default: () => '无相关图标',
},
// 双向绑定值,默认为 modelValue,
// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
modelValue: String,
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['update:modelValue', 'get', 'clear']);
// 引入组件
const IconList = defineAsyncComponent(() => import('/@/components/IconSelector/list.vue'));
// 定义变量内容
const inputWidthRef = ref();
const state = reactive({
fontIconPrefix: '',
fontIconWidth: 0,
fontIconSearch: '',
fontIconPlaceholder: '',
fontIconTabActive: 'ali',
fontIconList: {
ali: [],
ele: [],
awe: [],
local: [],
},
});
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
if (!props.modelValue) return false;
state.fontIconSearch = '';
state.fontIconPlaceholder = props.modelValue;
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
const list = fontIconTabNameList();
setTimeout(() => {
const icon = list.filter((icon: string) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = '';
}, 300);
};
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
const list = fontIconTabNameList();
if (!state.fontIconSearch) return list;
let search = state.fontIconSearch.trim().toLowerCase();
return list.filter((item: string) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 根据 tab name 类型设置图标
const fontIconTabNameList = () => {
let iconList: any = [];
if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
else if (state.fontIconTabActive === 'local') iconList = state.fontIconList.local;
return iconList;
};
// 处理 icon 双向绑定数值回显
const initModeValueEcho = () => {
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
(<string | undefined>state.fontIconPrefix) = props.modelValue;
};
// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
const initFontIconName = () => {
let name = 'ali';
if (!props.modelValue) {
return name;
}
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
else if (props.modelValue!.indexOf('local') > -1) name = 'local';
// 初始化 tab 高亮回显
state.fontIconTabActive = name;
return name;
};
// 初始化数据
const initFontIconData = async (name: string) => {
if (name === 'ali') {
// 阿里字体图标使用 `iconfont xxx`
if (state.fontIconList.ali.length > 0) return;
await initIconfont.ali().then((res: any) => {
state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
});
} else if (name === 'ele') {
// element plus 图标
if (state.fontIconList.ele.length > 0) return;
await initIconfont.ele().then((res: any) => {
state.fontIconList.ele = res;
});
} else if (name === 'awe') {
// fontawesome字体图标使用 `fa xxx`
if (state.fontIconList.awe.length > 0) return;
await initIconfont.awe().then((res: any) => {
state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
});
} else if (name === 'local') {
if (state.fontIconList.local.length > 0) return;
await initIconfont.local().then((res: any) => {
state.fontIconList.local = res.map((i: string) => `${i}`);
});
}
// 初始化 input 的 placeholder
// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
};
// 图标点击切换
const onIconClick = (pane: TabsPaneContext) => {
initFontIconData(pane.paneName as string);
inputWidthRef.value.focus();
};
// 获取当前点击的 icon 图标
const onColClick = (v: string) => {
state.fontIconPlaceholder = v;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
inputWidthRef.value.focus();
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
});
};
// 监听页面宽度改变
const initResize = () => {
window.addEventListener('resize', () => {
getInputWidth();
});
};
// 页面加载时
onMounted(() => {
initFontIconData(initFontIconName());
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
initFontIconName();
}
);
</script>
<template>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="props.list.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
<SvgIcon :name="v" />
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
</el-scrollbar>
</div>
</template>
<script setup lang="ts" name="iconSelectorList">
// 定义父组件传过来的值
const props = defineProps({
// 图标列表数据
list: {
type: Array,
default: () => [],
},
// 自定义空状态描述文字
empty: {
type: String,
default: () => '无相关图标',
},
// 高亮当前选中图标
prefix: {
type: String,
default: () => '',
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['get-icon']);
// 当前 icon 图标点击时
const onColClick = (v: unknown | string) => {
emit('get-icon', v);
};
</script>
<style scoped lang="scss">
.icon-selector-warp-row {
height: 230px;
overflow: hidden;
.el-row {
padding: 15px;
}
.el-scrollbar__bar.is-horizontal {
display: none;
}
.icon-selector-warp-item {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--el-border-color);
border-radius: 5px;
margin-bottom: 10px;
height: 30px;
i {
font-size: 20px;
color: var(--el-text-color-regular);
}
&:hover {
cursor: pointer;
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
i {
color: var(--el-color-primary);
}
}
}
.icon-selector-active {
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
i {
color: var(--el-color-primary);
}
}
}
</style>
$d-type: (
flex: flex,
block: block,
none: none,
);
$flex-jc: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
);
$flex-ai: (
start: flex-start,
end: flex-end,
center: center,
stretch: stretch,
);
//spacing
$spacing-types: (
m: margin,
p: padding,
);
$spacing-directions: (
t: top,
r: right,
b: bottom,
l: left,
);
$spacing-base-size: 5px;
$spacing-sizes: (
0: 0,
1: 1,
2: 2,
3: 3,
4: 4,
5: 5,
);
@each $key, $value in $d-type {
.d-#{$key} {
display: $value;
}
}
.flex-column {
flex-direction: column;
}
.text-ellipsis {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.w-100 {
width: 100%;
}
.h-100 {
height: 100%;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-grow-1 {
flex: 1;
}
@each $dir in(top, bottom, right, left) {
.border-#{$dir} {
border-#{$dir}: 1px solid;
}
}
@each $key, $value in $flex-jc {
.jc-#{$key} {
justify-content: $value;
}
}
@each $key, $value in $flex-ai {
.ai-#{$key} {
align-items: $value;
}
}
//text
@each $var in (left, center, right) {
.text-#{$var} {
text-align: $var !important;
}
}
@each $typeKey, $type in $spacing-types {
@each $sizeKey, $size in $spacing-sizes {
.#{$typeKey}-#{$sizeKey} {
#{$type}: $size * $spacing-base-size;
}
}
@each $sizeKey, $size in $spacing-sizes {
.#{$typeKey}x-#{$sizeKey} {
#{$type}-left: $size * $spacing-base-size;
#{$type}-right: $size * $spacing-base-size;
}
.#{$typeKey}y-#{$sizeKey} {
#{$type}-top: $size * $spacing-base-size;
#{$type}-bottom: $size * $spacing-base-size;
}
}
@each $directionKey, $direction in $spacing-directions {
@each $sizeKey, $size in $spacing-sizes {
.#{$typeKey}#{$directionKey}-#{$sizeKey} {
#{$type}-#{$direction}: $size * $spacing-base-size;
}
}
}
}
<template>
<div :class="mode == 'square' ? 'chatface' : 'brround avatar cover-image'" :style="transform" style="overflow: hidden; width: 40px; height: 40px">
<img v-if="faceUrl && !num" :src="faceUrl" class="w-100 h-100" />
<div v-else :style="styles" class="w-100 h-100 d-flex ai-center jc-center">{{ text }}</div>
</div>
</template>
<script>
export default {
name: 'nameAvatar',
props: {
scale: {
type: String,
default: '1',
},
num: [Number, String],
name: String,
mode: {
type: String,
default: '',
},
fontColor: {
type: String,
default: '#fff',
},
backgroundColor: {
type: String,
default: '#3696F2',
},
faceUrl: {
type: String,
default: '',
},
},
data() {
return {};
},
watch: {},
computed: {
// eslint-disable-next-line vue/return-in-computed-property
text() {
if (this.num !== undefined) {
return `+${this.num}`;
} else {
if (this.name) {
return this.name.slice(-2);
}
}
},
transform() {
let style = {};
if (this.scale) {
style['transform'] = `scale(${this.scale}, ${this.scale})`;
}
return style;
},
styles() {
let style = {};
if (this.size) {
style['font-size'] = '12px';
}
if (this.fontColor) {
style.color = this.fontColor;
}
if (this.backgroundColor) {
style['background'] = this.backgroundColor;
}
return style;
},
},
methods: {},
};
</script>
<style lang="scss" scoped>
@import './base.scss';
.avatar {
display: inline-block;
position: relative;
text-align: center;
vertical-align: bottom;
font-size: 8px;
user-select: none;
z-index: 10;
&:hover {
z-index: 100;
}
}
.brround {
border-radius: 50%;
}
.chatface {
display: block;
border-radius: 8px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
</style>
<template>
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!state.isMode">
<div class="notice-bar-warp" :style="{ color, fontSize: `${size}px` }">
<i v-if="leftIcon" class="notice-bar-warp-left-icon" :class="leftIcon"></i>
<div class="notice-bar-warp-text-box" ref="noticeBarWarpRef">
<div class="notice-bar-warp-text" ref="noticeBarTextRef" v-if="!scrollable">{{ text }}</div>
<div class="notice-bar-warp-slot" v-else><slot /></div>
</div>
<SvgIcon :name="rightIcon" v-if="rightIcon" class="notice-bar-warp-right-icon" @click="onRightIconClick" />
</div>
</div>
</template>
<script setup lang="ts" name="noticeBar">
import { reactive, ref, onMounted, nextTick } from 'vue';
// 定义父组件传过来的值
const props = defineProps({
// 通知栏模式,可选值为 closeable link
mode: {
type: String,
default: () => '',
},
// 通知文本内容
text: {
type: String,
default: () => '',
},
// 通知文本颜色
color: {
type: String,
default: () => 'var(--el-color-warning)',
},
// 通知背景色
background: {
type: String,
default: () => 'var(--el-color-warning-light-9)',
},
// 字体大小,单位px
size: {
type: [Number, String],
default: () => 14,
},
// 通知栏高度,单位px
height: {
type: Number,
default: () => 40,
},
// 动画延迟时间 (s)
delay: {
type: Number,
default: () => 1,
},
// 滚动速率 (px/s)
speed: {
type: Number,
default: () => 100,
},
// 是否开启垂直滚动
scrollable: {
type: Boolean,
default: () => false,
},
// 自定义左侧图标
leftIcon: {
type: String,
default: () => '',
},
// 自定义右侧图标
rightIcon: {
type: String,
default: () => '',
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['close', 'link']);
// 定义变量内容
const noticeBarWarpRef = ref();
const noticeBarTextRef = ref();
const state = reactive({
order: 1,
oneTime: 0,
twoTime: 0,
warpOWidth: 0,
textOWidth: 0,
isMode: false,
});
// 初始化 animation 各项参数
const initAnimation = () => {
nextTick(() => {
state.warpOWidth = noticeBarWarpRef.value.offsetWidth;
state.textOWidth = noticeBarTextRef.value.offsetWidth;
document.styleSheets[0].insertRule(`@keyframes oneAnimation {0% {left: 0px;} 100% {left: -${state.textOWidth}px;}}`);
document.styleSheets[0].insertRule(`@keyframes twoAnimation {0% {left: ${state.warpOWidth}px;} 100% {left: -${state.textOWidth}px;}}`);
computeAnimationTime();
setTimeout(() => {
changeAnimation();
}, props.delay * 1000);
});
};
// 计算 animation 滚动时长
const computeAnimationTime = () => {
state.oneTime = state.textOWidth / props.speed;
state.twoTime = (state.textOWidth + state.warpOWidth) / props.speed;
};
// 改变 animation 动画调用
const changeAnimation = () => {
if (state.order === 1) {
noticeBarTextRef.value.style.cssText = `animation: oneAnimation ${state.oneTime}s linear; opactity: 1;}`;
state.order = 2;
} else {
noticeBarTextRef.value.style.cssText = `animation: twoAnimation ${state.twoTime}s linear infinite; opacity: 1;`;
}
};
// 监听 animation 动画的结束
const listenerAnimationend = () => {
noticeBarTextRef.value.addEventListener(
'animationend',
() => {
changeAnimation();
},
false
);
};
// 右侧 icon 图标点击
const onRightIconClick = () => {
if (!props.mode) return false;
if (props.mode === 'closeable') {
state.isMode = true;
emit('close');
} else if (props.mode === 'link') {
emit('link');
}
};
// 页面加载时
onMounted(() => {
if (props.scrollable) return false;
initAnimation();
listenerAnimationend();
});
</script>
<style scoped lang="scss">
.notice-bar {
padding: 0 15px;
width: 100%;
border-radius: 4px;
.notice-bar-warp {
display: flex;
align-items: center;
width: 100%;
height: inherit;
.notice-bar-warp-text-box {
flex: 1;
height: inherit;
display: flex;
align-items: center;
overflow: hidden;
position: relative;
.notice-bar-warp-text {
white-space: nowrap;
position: absolute;
left: 0;
}
.notice-bar-warp-slot {
width: 100%;
white-space: nowrap;
:deep(.el-carousel__item) {
display: flex;
align-items: center;
}
}
}
.notice-bar-warp-left-icon {
width: 24px;
font-size: inherit !important;
}
.notice-bar-warp-right-icon {
width: 24px;
text-align: right;
font-size: inherit !important;
&:hover {
cursor: pointer;
}
}
}
}
</style>
<template>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
class="mt15"
:pager-count="5"
:page-sizes="props.pageSizes"
:current-page="props.current"
background
:page-size="props.size"
:layout="props.layout"
:total="props.total"
>
</el-pagination>
</template>
<script setup lang="ts" name="pagination">
const emit = defineEmits(['sizeChange', 'currentChange']);
const props = defineProps({
current: {
type: Number,
default: 1,
},
size: {
type: Number,
default: 10,
},
total: {
type: Number,
default: 0,
},
pageSizes: {
type: Array as () => number[],
default: () => {
return [1, 10, 20, 50, 100, 200];
},
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper',
},
});
// 分页改变
const sizeChangeHandle = (val: number) => {
emit('sizeChange', val);
};
// 分页改变
const currentChangeHandle = (val: number) => {
emit('currentChange', val);
};
</script>
<template>
<div @mouseenter="inPopover = true" @mouseleave="inPopover = false">
<el-popover
placement="top"
v-model:visible="visible"
:width="width"
trigger="contextmenu"
class="popover-input"
:teleported="teleported"
:persistent="false"
popper-class="!p-0"
>
<div class="flex p-3" @click.stop="">
<div class="popover-input__input mr-[10px] flex-1">
<el-select class="flex-1" :size="size" v-if="type == 'select'" v-model="inputValue" :teleported="teleported">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
<el-input
v-else
v-model.trim="inputValue"
:maxlength="maxlength"
:show-word-limit="showLimit"
:type="type"
:size="size"
clearable
:placeholder="placeholder"
/>
</div>
<div class="flex-none popover-input__btns">
<el-button link @click="close">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" :size="size" @click="handleConfirm">{{ $t('common.confirmButtonText') }}</el-button>
</div>
</div>
<template #reference>
<div class="inline" @click.stop="handleOpen">
<slot></slot>
</div>
</template>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { useEventListener } from '@vueuse/core';
import type { PropType } from 'vue';
const props = defineProps({
modelValue: {
type: String,
},
type: {
type: String,
default: 'text',
},
width: {
type: [Number, String],
default: '300px',
},
placeholder: String,
disabled: {
type: Boolean,
default: false,
},
options: {
type: Array as PropType<any[]>,
default: () => [],
},
size: {
type: String as PropType<'default' | 'small' | 'large'>,
default: 'default',
},
limit: {
type: Number,
default: 200,
},
maxlength: {
type: Number,
default: 20,
},
showLimit: {
type: Boolean,
default: false,
},
teleported: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['confirm', 'update:modelValue']);
const visible = ref(false);
const inPopover = ref(false);
const inputValue = ref();
const handleConfirm = () => {
close();
emit('confirm', inputValue.value);
emit('update:modelValue', inputValue.value);
};
const handleOpen = () => {
if (props.disabled) {
return;
}
visible.value = true;
inputValue.value = '';
};
const close = () => {
visible.value = false;
};
watch(
() => inputValue.value,
(value) => {
emit('update:modelValue', value);
},
{
immediate: true,
}
);
useEventListener(document.documentElement, 'click', () => {
if (inPopover.value) return;
close();
});
</script>
<style scoped lang="scss"></style>
<template>
<div class="dialog">
<div class="dialog__trigger" @click="open">
<!-- 触发弹窗 -->
<slot name="trigger"></slot>
</div>
<el-dialog
v-model="visible"
:custom-class="customClass"
:center="center"
:append-to-body="true"
:width="width"
:close-on-click-modal="clickModalClose"
@closed="close"
>
<!-- 弹窗内容 -->
<template v-if="title" #header>{{ title }}</template>
<!-- 自定义内容 -->
<slot>{{ content }}</slot>
<!-- 底部弹窗页脚 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="handleEvent('cancel')">
{{ $t('common.cancelButtonText') }}
</el-button>
<el-button type="primary" @click="handleEvent('confirm')">
{{ $t('common.confirmButtonText') }}
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
export default defineComponent({
props: {
title: {
// 弹窗标题
type: String,
default: '',
},
content: {
// 弹窗内容
type: String,
default: '',
},
width: {
// 弹窗的宽度
type: String,
default: '400px',
},
disabled: {
// 是否禁用
type: Boolean,
default: false,
},
async: {
// 是否开启异步关闭
type: Boolean,
default: false,
},
clickModalClose: {
// 点击遮罩层关闭对话窗口
type: Boolean,
default: false,
},
center: {
// 是否居中布局
type: Boolean,
default: false,
},
customClass: {
type: String,
default: '',
},
},
emits: ['confirm', 'cancel', 'close', 'open'],
setup(props, { emit }) {
const visible = ref(false);
const handleEvent = (type: 'confirm' | 'cancel') => {
emit(type);
if (!props.async || type === 'cancel') {
close();
}
};
const close = () => {
visible.value = false;
nextTick(() => {
emit('close');
});
};
const open = () => {
if (props.disabled) {
return;
}
emit('open');
visible.value = true;
};
provide('visible', visible);
return {
visible,
handleEvent,
close,
open,
};
},
});
</script>
<style scoped lang="scss">
.dialog-body {
white-space: pre-line;
}
</style>
export default {
queryTree: {
hideSearch: 'hideSearch',
displayTheSearch: 'displayTheSearch',
refresh: 'refresh',
print: 'print',
view: 'view'
},
};
export default {
queryTree: {
hideSearch: '隐藏搜索',
displayTheSearch: '显示搜索',
refresh: '刷新',
print: '打印',
view: '视图'
},
};
<template>
<div class="head-container">
<div class="head-container-header">
<div class="head-container-header-input">
<el-input v-model="searchName" suffix-icon="search" :placeholder="placeholder" clearable @change="getdeptTree" />
</div>
<div class="head-container-header-dropdown" v-if="showExpand">
<el-dropdown :hide-on-click="false">
<el-icon style="transform: rotate(90deg)">
<MoreFilled />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button
:class="buttonClass"
link
type="primary"
:icon="isExpand ? 'expand' : 'fold'"
@click="toggleRowExpansionAll(isExpand ? false : true)"
>
{{ isExpand ? '折叠' : '展开' }}
</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<el-tree
class="mt20"
:data="state.List"
:props="props.props"
:expand-on-click-node="false"
ref="deptTreeRef"
:loading="state.localLoading"
node-key="id"
highlight-current
default-expand-all
@node-click="handleNodeClick"
>
<template #default="{ node, data }" v-if="$slots.default">
<slot :node="node" :data="data"></slot>
</template>
</el-tree>
</div>
</template>
<script setup lang="ts" name="query-tree">
import { useMessage } from '/@/hooks/message';
const emit = defineEmits(['search', 'nodeClick']);
const props = defineProps({
/**
* 树结构属性配置。
*
* @default { label: 'name', children: 'children', value: 'id' }
*/
props: {
type: Object,
default: () => {
return {
label: 'name',
children: 'children',
value: 'id',
};
},
},
/**
* 输入框占位符。
*
* @default ''
*/
placeholder: {
type: String,
default: '',
},
/**
* 是否显示加载中状态。
*
* @default false
*/
loading: {
type: Boolean,
default: false,
},
/**
* 查询函数,必须返回 Promise 类型数据。
*/
query: {
type: Function,
required: true,
},
/**
* 是否显示折叠控制
*/
showExpand: {
type: Boolean,
default: false,
},
});
const state = reactive({
List: [], // 树形结构列表数据
localLoading: props.loading, // 是否加载中
});
const deptTreeRef = ref(); // 部门树形结构组件实例引用
const searchName = ref(); // 查询关键字
const isExpand = ref(true); // 是否展开所有节点
const buttonClass = computed(() => {
return ['!h-[20px]', 'reset-margin', '!text-gray-500', 'dark:!text-white', 'dark:hover:!text-primary'];
});
/**
* 点击树形结构节点触发的事件。
*
* @param item 被点击的节点数据。
*/
const handleNodeClick = (item: any) => {
emit('nodeClick', item);
};
/**
* 获取部门树形结构数据。
*/
const getdeptTree = () => {
if (props.query instanceof Function) {
state.localLoading = true;
// 调用传入的查询函数,并将查询关键字作为参数传入
const result = props.query(unref(searchName));
// 如果查询结果为 Promise 类型,则进行后续处理
if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
result
.then((r: any) => {
state.List = r.data;
})
.catch((err) => {
useMessage().error(err.msg);
});
}
}
};
/**
* 切换所有节点的展开/收起状态。
*
* @param status 目标状态,true 为展开,false 为收起。
*/
const toggleRowExpansionAll = (status) => {
isExpand.value = status;
const nodes = deptTreeRef.value.store._getAllNodes();
for (let i = 0; i < nodes.length; i++) {
nodes[i].expanded = status;
}
};
onMounted(() => {
getdeptTree();
});
// 方便父组件调用刷新树方法
defineExpose({
getdeptTree,
});
</script>
<style lang="scss" scoped>
.head-container {
&-header {
display: flex;
align-items: center;
&-input {
width: 90%;
}
&-dropdown {
flex: 1;
margin-left: 5%;
}
}
}
</style>
<template>
<div class="top-right-btn" :style="style">
<el-row>
<!-- 搜索框控制 -->
<el-tooltip
class="item"
effect="dark"
:content="showSearch ? $t('queryTree.hideSearch') : $t('queryTree.displayTheSearch')"
placement="top"
v-if="search"
>
<el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip>
<!-- 导出 -->
<el-tooltip class="item" effect="dark" :content="$t('common.exportBtn')" placement="top" v-if="isExport()">
<el-button circle icon="Download" @click="handleExport()" />
</el-tooltip>
<!-- 刷新功能 -->
<el-tooltip class="item" effect="dark" :content="$t('queryTree.refresh')" placement="top">
<el-button circle icon="Refresh" @click="handleRefresh()" />
</el-tooltip>
<!-- 插槽 -->
<slot></slot>
</el-row>
</div>
</template>
<script setup name="right-toolbar">
import { auth } from '/@/utils/authFunction';
/**
* 通过 defineProps 函数定义组件 props
*/
const props = defineProps({
/**
* 是否显示搜索框
*/
showSearch: {
type: Boolean,
default: true,
},
/**
* 是否导出
*/
export: {
type: [String, Boolean],
default: null,
},
/**
* 是否显示搜索框
*/
search: {
type: Boolean,
default: true,
},
/**
* 列表项之间的间距
*/
gutter: {
type: Number,
default: 10,
},
});
const emits = defineEmits(['update:showSearch', 'queryTable', 'exportExcel']);
const style = computed(() => {
const ret = {};
// 如果props中有传入gutter属性,则计算出marginRight
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
}
return ret; // 返回计算后的样式对象
});
// 搜索
const toggleSearch = () => {
emits('update:showSearch', !props.showSearch);
};
// 刷新
const handleRefresh = () => {
emits('queryTable');
};
// 导出excel
const handleExport = () => {
emits('exportExcel');
};
// 是否导出
const isExport = () => {
if (props.export === true) {
return true;
}
// 字符串鉴权
return props.export && auth(props.export);
};
</script>
<style lang="scss" scoped>
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;
margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
margin-bottom: 10px;
}
.my-el-transfer {
text-align: center;
}
</style>
<template>
<div class="container">
<el-tag :color="randomColor()" class="container-tag">
<SvgIcon :name="props.icon" :size="25" color="#ffffff" />
</el-tag>
<span class="container-span">{{ $t(props.label) }}</span>
</div>
</template>
<script setup name="shortcut">
const props = defineProps({
icon: {
type: String,
default: () => 'menu-outlined',
required: false,
},
label: {
type: String,
default: () => '快捷方式',
required: false,
},
color: {
type: String,
default: () => '',
required: false,
},
});
// 颜色列表
const colorList = ['#7265E6', '#FFBF00', '#00A2AE', '#F56A00', '#1890FF', '#606D80'];
// 获取随机颜色
const randomColor = () => {
if (props.color) {
return props.color;
}
return colorList[randomNum(0, colorList.length - 1)];
};
// 获取minNum到maxNum内的随机数
const randomNum = (minNum, maxNum) => {
switch (arguments.length) {
case 1:
return parseInt(Math.random() * minNum + 1, 10);
// eslint-disable-next-line no-unreachable
break;
case 2:
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
// eslint-disable-next-line no-unreachable
break;
default:
return 0;
// eslint-disable-next-line no-unreachable
break;
}
};
</script>
<style scoped>
.container {
height: 60px;
/*border:1px solid var(--border-color-split);*/
border-radius: 5px;
display: flex;
align-items: center;
cursor: pointer;
/*实现渐变(时间变化效果)*/
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
-ms-transition: all 0.5s;
-o-transition: all 0.5s;
transition: all 0.5s;
}
.container:hover {
background: var(--border-color-split);
}
.container-tag {
width: 42px;
height: 42px;
border-radius: 10px;
display: flex;
align-items: center;
margin-left: 10px;
font-size: 24px;
}
.container-span {
max-width: 60%;
font-weight: 500;
margin-left: 10px;
color: #6d6b6b;
}
</style>
<template>
<div class="prefixCls relative" style="width: 100%">
<el-input v-model="innerValueRef" v-bind="$attrs" type="password" show-password @change="handleChange" style="width: 100%">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</el-input>
<div class="prefixCls-bar">
<div class="prefixCls-bar--fill" :data-score="getPasswordStrength"></div>
</div>
</div>
</template>
<script setup lang="ts" name="StrengthMeter">
import { verifyPasswordStrength } from '/@/utils/toolsValidate';
const props = defineProps({
value: {
type: String,
},
showInput: {
type: Boolean,
default: () => {
return true;
},
},
disabled: {
type: Boolean,
},
});
const emit = defineEmits(['score', 'change', 'update:value']);
// 计算密码强度
const getPasswordStrength = computed(() => {
const { disabled } = props;
if (disabled) return -1;
const innerValue = unref(innerValueRef);
const score = innerValue ? verifyPasswordStrength(innerValue) : -1;
emit('score', score);
return score;
});
const innerValueRef = ref();
const handleChange = (e: any) => {
innerValueRef.value = e;
};
watchEffect(() => {
innerValueRef.value = props.value || '';
});
watch(
() => unref(innerValueRef),
(val) => {
emit('update:value', val);
emit('change', val);
}
);
</script>
<style scoped lang="scss">
.prefixCls {
&-bar {
position: relative;
height: 6px;
margin: 10px auto 6px;
background-color: grey;
border-radius: 6px;
&::before,
&::after {
position: absolute;
z-index: 10;
display: block;
width: 33%;
height: inherit;
background-color: transparent;
border-color: white;
border-style: solid;
border-width: 0 5px;
content: '';
}
&::before {
left: 33%;
}
//
//&::after {
// right: 33%;
//}
&--fill {
position: absolute;
width: 0;
height: inherit;
background-color: transparent;
border-radius: inherit;
transition: width 0.5s ease-in-out, background 0.25s;
&[data-score='1'] {
width: 33%;
background-color: var(--el-color-danger);
}
&[data-score='2'] {
width: 67%;
background-color: var(--el-color-warning);
}
&[data-score='3'] {
width: 100%;
background-color: var(--el-color-success);
}
}
}
}
</style>
<template>
<i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
<component :is="getIconName" />
</i>
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
<img :src="getIconName" :style="setIconSvgInsStyle" />
</div>
<svg v-else-if="isShowLocalSvg" class="svg-icon icon" :style="setIconImgOutStyle">
<use :href="`#${getIconName}`" />
</svg>
<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>
<script setup lang="ts" name="svgIcon">
import { computed } from 'vue';
// 定义父组件传过来的值
const props = defineProps({
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
},
});
// 在线链接、本地引入地址前缀
const linesString = ['https', 'http', '/src', '/assets', 'data:image', import.meta.env.VITE_PUBLIC_PATH];
// 获取 icon 图标名称
const getIconName = computed(() => {
return props?.name;
});
// 用于判断 element plus 自带 svg 图标的显示、隐藏
const isShowIconSvg = computed(() => {
return props?.name?.startsWith('ele-');
});
// 用于判断在线链接、本地引入等图标显示、隐藏
const isShowIconImg = computed(() => {
return linesString.find((str) => props.name?.startsWith(str));
});
const isShowLocalSvg = computed(() => {
return props?.name?.startsWith('local-');
});
// 设置图标样式
const setIconSvgStyle = computed(() => {
return `font-size: ${props.size}px;color: ${props.color};`;
});
// 设置图片样式
const setIconImgOutStyle = computed(() => {
return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
});
// 设置图片样式
const setIconSvgInsStyle = computed(() => {
const filterStyle: string[] = [];
const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
});
</script>
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment