mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-19 03:30:06 +08:00
commit
7f0c011123
@ -28,15 +28,15 @@
|
||||
<dynamic-datasource.version>3.5.2</dynamic-datasource.version>
|
||||
<redisson.version>3.18.0</redisson.version>
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.2</lock4j.version>
|
||||
<lock4j.version>2.2.3</lock4j.version>
|
||||
<resilience4j.version>1.7.1</resilience4j.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>8.12.0</skywalking.version>
|
||||
<spring-boot-admin.version>2.7.7</spring-boot-admin.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<!-- Test 测试相关 -->
|
||||
<podam.version>7.2.9.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.0.4</jedis-mock.version>
|
||||
<podam.version>7.2.11.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.0.5</jedis-mock.version>
|
||||
<mockito-inline.version>4.8.0</mockito-inline.version>
|
||||
<!-- Bpm 工作流相关 -->
|
||||
<flowable.version>6.7.2</flowable.version>
|
||||
@ -50,19 +50,19 @@
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<guice.version>5.1.0</guice.version>
|
||||
<transmittable-thread-local.version>2.14.0</transmittable-thread-local.version>
|
||||
<transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
|
||||
<commons-net.version>3.8.0</commons-net.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<tika-core.version>2.5.0</tika-core.version>
|
||||
<aj-captcha.version>1.3.0</aj-captcha.version>
|
||||
<netty-all.version>4.1.82.Final</netty-all.version>
|
||||
<netty-all.version>4.1.85.Final</netty-all.version>
|
||||
<!-- 三方云服务相关 -->
|
||||
<okio.version>3.0.0</okio.version>
|
||||
<okhttp3.version>4.10.0</okhttp3.version>
|
||||
<minio.version>8.4.6</minio.version>
|
||||
<aliyun-java-sdk-core.version>4.6.2</aliyun-java-sdk-core.version>
|
||||
<aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
|
||||
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
|
||||
<tencentcloud-sdk-java.version>3.1.635</tencentcloud-sdk-java.version>
|
||||
<tencentcloud-sdk-java.version>3.1.637</tencentcloud-sdk-java.version>
|
||||
<justauth.version>1.4.0</justauth.version>
|
||||
<jimureport.version>1.5.4</jimureport.version>
|
||||
<xercesImpl.version>2.12.2</xercesImpl.version>
|
||||
|
@ -1,12 +1,10 @@
|
||||
package cn.iocoder.yudao.framework.common.util.object;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@ -47,6 +45,7 @@ public class ObjectUtils {
|
||||
return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> T defaultIfNull(T... array) {
|
||||
for (T item : array) {
|
||||
if (item != null) {
|
||||
@ -56,6 +55,7 @@ public class ObjectUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> boolean equalsAny(T obj, T... array) {
|
||||
return Arrays.asList(array).contains(obj);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-sdk-java</artifactId>
|
||||
<version>4.33.44.ALL</version>
|
||||
<version>4.34.71.ALL</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
|
@ -46,7 +46,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.13.2.2</version>
|
||||
<version>2.13.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
|
@ -48,7 +48,7 @@ public interface BpmTaskConvert {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
T newInstance = target.newInstance();
|
||||
T newInstance = target.getDeclaredConstructor().newInstance();
|
||||
BeanUtils.copyProperties(source, newInstance);
|
||||
return newInstance;
|
||||
} catch (Exception e) {
|
||||
|
@ -5,7 +5,7 @@ export interface ${simpleClassName}VO {
|
||||
#if ($column.createOperation || $column.updateOperation)
|
||||
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double")
|
||||
${column.javaField}: number
|
||||
#elseif(${column.javaType.toLowerCase()} == "date")
|
||||
#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
${column.javaField}: Date
|
||||
#else
|
||||
${column.javaField}: ${column.javaType.toLowerCase()}
|
||||
@ -19,7 +19,7 @@ export interface ${simpleClassName}PageReqVO extends PageParam {
|
||||
#if (${column.listOperation})##查询操作
|
||||
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double")
|
||||
${column.javaField}?: number
|
||||
#elseif(${column.javaType.toLowerCase()} == "date")
|
||||
#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
${column.javaField}?: Date[]
|
||||
#else
|
||||
${column.javaField}?: ${column.javaType.toLowerCase()}
|
||||
@ -33,8 +33,8 @@ export interface ${simpleClassName}ExcelReqVO {
|
||||
#if (${column.listOperation})##查询操作
|
||||
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double")
|
||||
${column.javaField}?: number
|
||||
#elseif(${column.javaType.toLowerCase()} == "date")
|
||||
${column.javaField}?: string[]
|
||||
#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
${column.javaField}?: Date[]
|
||||
#else
|
||||
${column.javaField}?: ${column.javaType.toLowerCase()}
|
||||
#end
|
||||
|
@ -28,6 +28,9 @@ const crudSchemas = reactive<VxeCrudSchema>({
|
||||
{
|
||||
title: '${column.columnComment}',
|
||||
field: '${column.javaField}',
|
||||
#if (!$column.listOperationResult)
|
||||
isTable: false,
|
||||
#end
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
dictType: DICT_TYPE.$dictType.toUpperCase(),
|
||||
#if (${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")
|
||||
@ -39,7 +42,8 @@ const crudSchemas = reactive<VxeCrudSchema>({
|
||||
#if (!$column.createOperation && !$column.updateOperation)
|
||||
isForm: false,
|
||||
#elseif(!("" != $column.dictType))
|
||||
#if ($column.htmlType == "datetime")## 时间框
|
||||
#if (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
formatter: 'formatDate',
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
@ -73,9 +77,20 @@ const crudSchemas = reactive<VxeCrudSchema>({
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
#elseif($column.htmlType == "imageUpload")## 图片上传
|
||||
form: {
|
||||
component: 'UploadImg',
|
||||
componentProps: {
|
||||
limit: 1
|
||||
}
|
||||
},
|
||||
#elseif($column.htmlType == "fileUpload")## 图片上传
|
||||
form: {
|
||||
component: 'UploadFile'
|
||||
},
|
||||
#end
|
||||
#end
|
||||
#if ($column.listOperationResult)
|
||||
#if ($column.listOperation)
|
||||
#if($column.htmlType == "input")
|
||||
isSearch: true,
|
||||
#elseif("" != $dictType)
|
||||
|
@ -74,7 +74,7 @@
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="${table.moduleName}">
|
||||
<script setup lang="ts" name="${simpleClassName}">
|
||||
// 全局相关的 import
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
@ -91,7 +91,7 @@ const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
|
||||
const { gridOptions, reloadList, deleteData, exportList } = useVxeGrid<${simpleClassName}Api.${simpleClassName}VO>({
|
||||
const { gridOptions, getList, deleteData, exportList } = useVxeGrid<${simpleClassName}Api.${simpleClassName}VO>({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: ${simpleClassName}Api.get${simpleClassName}PageApi,
|
||||
deleteApi: ${simpleClassName}Api.delete${simpleClassName}Api,
|
||||
@ -169,7 +169,7 @@ const submitForm = async () => {
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reloadList(xGrid)
|
||||
await getList(xGrid)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -6,5 +6,4 @@
|
||||
/vite.config.ts
|
||||
/src/types/env.d.ts
|
||||
/docs/**/*
|
||||
/plop/**/*
|
||||
CHANGELOG
|
||||
|
76
yudao-ui-admin-vue3/build/vite/index.ts
Normal file
76
yudao-ui-admin-vue3/build/vite/index.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { resolve } from 'path'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import VueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import VueI18n from '@intlify/vite-plugin-vue-i18n'
|
||||
import WindiCSS from 'vite-plugin-windicss'
|
||||
import progress from 'vite-plugin-progress'
|
||||
import EslintPlugin from 'vite-plugin-eslint'
|
||||
import PurgeIcons from 'vite-plugin-purge-icons'
|
||||
import { ViteEjsPlugin } from 'vite-plugin-ejs'
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import {
|
||||
createStyleImportPlugin,
|
||||
ElementPlusResolve,
|
||||
VxeTableResolve
|
||||
} from 'vite-plugin-style-import'
|
||||
export function createVitePlugins(VITE_APP_TITLE: string) {
|
||||
const root = process.cwd()
|
||||
// 路径查找
|
||||
function pathResolve(dir: string) {
|
||||
return resolve(root, '.', dir)
|
||||
}
|
||||
return [
|
||||
Vue(),
|
||||
VueJsx(),
|
||||
WindiCSS(),
|
||||
progress(),
|
||||
PurgeIcons(),
|
||||
vueSetupExtend(),
|
||||
createStyleImportPlugin({
|
||||
resolves: [ElementPlusResolve(), VxeTableResolve()],
|
||||
libs: [
|
||||
{
|
||||
libraryName: 'element-plus',
|
||||
esModule: true,
|
||||
resolveStyle: (name) => {
|
||||
return `element-plus/es/components/${name.substring(3)}/style/css`
|
||||
}
|
||||
},
|
||||
{
|
||||
libraryName: 'vxe-table',
|
||||
esModule: true,
|
||||
resolveStyle: (name) => {
|
||||
return `vxe-table/es/${name}/style.css`
|
||||
}
|
||||
}
|
||||
]
|
||||
}),
|
||||
EslintPlugin({
|
||||
cache: false,
|
||||
include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
|
||||
}),
|
||||
VueI18n({
|
||||
runtimeOnly: true,
|
||||
compositionOnly: true,
|
||||
include: [resolve(__dirname, 'src/locales/**')]
|
||||
}),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [pathResolve('src/assets/svgs')],
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
svgoOptions: true
|
||||
}),
|
||||
viteCompression({
|
||||
verbose: true, // 是否在控制台输出压缩结果
|
||||
disable: false, // 是否禁用
|
||||
threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b
|
||||
algorithm: 'gzip', // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
|
||||
ext: '.gz', // 生成的压缩包后缀
|
||||
deleteOriginFile: false //压缩后是否删除源文件
|
||||
}),
|
||||
ViteEjsPlugin({
|
||||
title: VITE_APP_TITLE
|
||||
})
|
||||
]
|
||||
}
|
40
yudao-ui-admin-vue3/build/vite/optimize.ts
Normal file
40
yudao-ui-admin-vue3/build/vite/optimize.ts
Normal file
@ -0,0 +1,40 @@
|
||||
const include = [
|
||||
'qs',
|
||||
'url',
|
||||
'vue',
|
||||
'sass',
|
||||
'mitt',
|
||||
'axios',
|
||||
'pinia',
|
||||
'dayjs',
|
||||
'qrcode',
|
||||
'windicss',
|
||||
'vue-router',
|
||||
'vue-types',
|
||||
'vue-i18n',
|
||||
'xe-utils',
|
||||
'crypto-js',
|
||||
'lodash-es',
|
||||
'vxe-table',
|
||||
'nprogress',
|
||||
'animate.css',
|
||||
'vxe-table/es/style',
|
||||
'web-storage-cache',
|
||||
'element-plus',
|
||||
'element-plus/es/locale/lang/zh-cn',
|
||||
'element-plus/es/locale/lang/en',
|
||||
'@iconify/iconify',
|
||||
'@vueuse/core',
|
||||
'@zxcvbn-ts/core',
|
||||
'echarts/core',
|
||||
'echarts/charts',
|
||||
'echarts/components',
|
||||
'echarts/renderers',
|
||||
'echarts-wordcloud',
|
||||
'@wangeditor/editor',
|
||||
'@wangeditor/editor-for-vue'
|
||||
]
|
||||
|
||||
const exclude = ['@iconify/json']
|
||||
|
||||
export { include, exclude }
|
18
yudao-ui-admin-vue3/build/vite/styleImport.ts
Normal file
18
yudao-ui-admin-vue3/build/vite/styleImport.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const styleImportPlugin = {
|
||||
libs: [
|
||||
{
|
||||
libraryName: 'element-plus',
|
||||
esModule: true,
|
||||
resolveStyle: (name) => {
|
||||
return `element-plus/es/components/${name.substring(3)}/style/css`
|
||||
}
|
||||
},
|
||||
{
|
||||
libraryName: 'vxe-table',
|
||||
esModule: true,
|
||||
resolveStyle: (name) => {
|
||||
return `vxe-table/es/${name}/style.css`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ruoyi-vue-pro-vue3",
|
||||
"version": "1.6.4.1863",
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "1.6.5.1874",
|
||||
"description": "基于vue3、vite3、element-plus、typesScript",
|
||||
"author": "xingyu",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "vite --mode base --open",
|
||||
"dev": "vite --mode base",
|
||||
"ts:check": "vue-tsc --noEmit",
|
||||
"build:pro": "vite build --mode pro",
|
||||
"build:dev": "vite build --mode dev",
|
||||
@ -21,8 +21,7 @@
|
||||
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"lint:lint-staged": "lint-staged -c ",
|
||||
"lint:pretty": "pretty-quick --staged",
|
||||
"p": "plop"
|
||||
"lint:pretty": "pretty-quick --staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/iconify": "^3.0.1",
|
||||
@ -31,14 +30,13 @@
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^2.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.2.0",
|
||||
"axios": "^1.2.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.6",
|
||||
"dayjs": "^1.11.7",
|
||||
"echarts": "^5.4.0",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "2.2.25",
|
||||
"element-plus": "2.2.26",
|
||||
"intro.js": "^6.0.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"jsencrypt": "^3.3.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
@ -59,52 +57,54 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.3.0",
|
||||
"@commitlint/config-conventional": "^17.3.0",
|
||||
"@iconify/json": "^2.1.145",
|
||||
"@iconify/json": "^2.1.149",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.0",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/node": "^18.11.11",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.1",
|
||||
"@typescript-eslint/parser": "^5.45.1",
|
||||
"@vitejs/plugin-legacy": "^2.3.1",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.28.0",
|
||||
"consola": "^2.15.3",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-define-config": "^1.12.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"lint-staged": "^13.0.4",
|
||||
"plop": "^3.1.1",
|
||||
"lint-staged": "^13.1.0",
|
||||
"postcss": "^8.4.19",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^3.5.0",
|
||||
"rollup": "^3.6.0",
|
||||
"sass": "^1.56.1",
|
||||
"stylelint": "^14.15.0",
|
||||
"stylelint": "^14.16.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier": "^9.0.4",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"terser": "^5.16.1",
|
||||
"typescript": "4.9.3",
|
||||
"vite": "3.2.4",
|
||||
"vite": "3.2.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-ejs": "^1.6.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-progress": "^0.0.6",
|
||||
"vite-plugin-purge-icons": "^0.9.1",
|
||||
"vite-plugin-style-import": "2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vite-plugin-windicss": "^1.8.8",
|
||||
"vue-tsc": "^1.0.10",
|
||||
"vue-tsc": "^1.0.11",
|
||||
"windicss": "^3.5.6"
|
||||
},
|
||||
"engines": {
|
||||
@ -116,7 +116,7 @@
|
||||
"url": "git+https://gitee.com/zhijiantianya/ruoyi-vue-pro"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I57XOQ"
|
||||
"url": "https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues"
|
||||
},
|
||||
"homepage": "https://gitee.com/zhijiantianya/ruoyi-vue-pro"
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('{{ name }}')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`${prefixCls}-{{ name }}`">{{ upperFirstName }}</div>
|
||||
</template>
|
@ -1,3 +0,0 @@
|
||||
import {{ upperFirstName }} from './src/{{ upperFirstName }}.vue'
|
||||
|
||||
export { {{ upperFirstName }} }
|
@ -1,38 +0,0 @@
|
||||
const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
|
||||
|
||||
module.exports = {
|
||||
description: 'Create vue component',
|
||||
prompts: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: '请输入组件名称(Please enter the component name)'
|
||||
}
|
||||
],
|
||||
actions: (data) => {
|
||||
const { name } = data
|
||||
const upperFirstName = toUpperCase(name)
|
||||
|
||||
const actions = []
|
||||
if (name) {
|
||||
actions.push({
|
||||
type: 'add',
|
||||
path: `./src/components/${upperFirstName}/src/${upperFirstName}.vue`,
|
||||
templateFile: './plop/component/component.hbs',
|
||||
data: {
|
||||
name,
|
||||
upperFirstName
|
||||
}
|
||||
}, {
|
||||
type: 'add',
|
||||
path: `./src/components/${upperFirstName}/index.ts`,
|
||||
templateFile: './plop/component/index.hbs',
|
||||
data: {
|
||||
upperFirstName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
|
||||
|
||||
module.exports = {
|
||||
description: 'Create vue view',
|
||||
prompts: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
message: '请输入路径(Please enter a path)',
|
||||
default: 'views'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: '请输入模块名称(Please enter module name)'
|
||||
}
|
||||
],
|
||||
actions: (data) => {
|
||||
const { name, path } = data
|
||||
const upperFirstName = toUpperCase(name)
|
||||
|
||||
const actions = []
|
||||
if (name) {
|
||||
actions.push({
|
||||
type: 'add',
|
||||
path: `./src/${path}/${upperFirstName}.vue`,
|
||||
templateFile: './plop/view/view.hbs',
|
||||
data: {
|
||||
name,
|
||||
upperFirstName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<ContentWrap title="{{ upperFirstName }}"> {{ name }} </ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="{{ name }}">
|
||||
</script>
|
@ -1,7 +0,0 @@
|
||||
const viewGenerator = require('./plop/view/prompt.js')
|
||||
const componentGenerator = require('./plop/component/prompt.js')
|
||||
|
||||
module.exports = function (plop) {
|
||||
plop.setGenerator('view', viewGenerator)
|
||||
plop.setGenerator('component', componentGenerator)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { isDark } from '@/utils/is'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ConfigGlobal } from '@/components/ConfigGlobal'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('app')
|
||||
@ -15,15 +15,10 @@ const { wsCache } = useCache()
|
||||
|
||||
// 根据浏览器当前主题设置系统主题色
|
||||
const setDefaultTheme = () => {
|
||||
if (wsCache.get('isDark')) {
|
||||
if (wsCache.get('isDark') || wsCache.get('isDark') === 'true') {
|
||||
appStore.setIsDark(true)
|
||||
} else {
|
||||
appStore.setIsDark(false)
|
||||
}
|
||||
return
|
||||
let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
|
||||
if (isDarkTheme === null) {
|
||||
isDarkTheme = isDark()
|
||||
}
|
||||
const isDarkTheme = isDark()
|
||||
appStore.setIsDark(isDarkTheme)
|
||||
}
|
||||
setDefaultTheme()
|
||||
|
@ -42,7 +42,7 @@ export const getFileConfigApi = (id: number) => {
|
||||
|
||||
// 更新文件配置为主配置
|
||||
export const updateFileConfigMasterApi = (id: number) => {
|
||||
return request.get({ url: '/infra/file-config/update-master?id=' + id })
|
||||
return request.put({ url: '/infra/file-config/update-master?id=' + id })
|
||||
}
|
||||
|
||||
// 新增文件配置
|
||||
|
@ -57,7 +57,7 @@ export const smsLoginApi = (data: SmsLoginVO) => {
|
||||
}
|
||||
|
||||
// 社交授权的跳转
|
||||
export const socialAuthRedirectApi = (type: string, redirectUri: string) => {
|
||||
export const socialAuthRedirectApi = (type: number, redirectUri: string) => {
|
||||
return request.get({
|
||||
url: '/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri
|
||||
})
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
} from 'element-plus'
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import { UploadImg, UploadFile } from '@/components/UploadFile'
|
||||
import { ComponentName } from '@/types/components'
|
||||
|
||||
const componentMap: Recordable<Component, ComponentName> = {
|
||||
@ -45,7 +46,9 @@ const componentMap: Recordable<Component, ComponentName> = {
|
||||
TreeSelect: ElTreeSelect,
|
||||
RadioButton: ElRadioGroup,
|
||||
InputPassword: InputPassword,
|
||||
Editor: Editor
|
||||
Editor: Editor,
|
||||
UploadImg: UploadImg,
|
||||
UploadFile: UploadFile
|
||||
}
|
||||
|
||||
export { componentMap }
|
||||
|
@ -1,3 +1,4 @@
|
||||
import UploadImg from './src/UploadImg.vue'
|
||||
import UploadFile from './src/UploadFile.vue'
|
||||
|
||||
export { UploadImg }
|
||||
export { UploadImg, UploadFile }
|
||||
|
186
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue
Normal file
186
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="upload-file">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:multiple="props.limit > 1"
|
||||
name="file"
|
||||
v-model="valueRef"
|
||||
v-model:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
:auto-upload="autoUpload"
|
||||
:action="updateUrl"
|
||||
:headers="uploadHeaders"
|
||||
:limit="props.limit"
|
||||
:drag="drag"
|
||||
:before-upload="beforeUpload"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="excelUploadError"
|
||||
:on-remove="handleRemove"
|
||||
:on-preview="handlePreview"
|
||||
class="upload-file-uploader"
|
||||
>
|
||||
<el-button type="primary"><Icon icon="ep:upload-filled" />选取文件</el-button>
|
||||
<template v-if="isShowTip" #tip>
|
||||
<div style="font-size: 8px">
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
||||
</div>
|
||||
<div style="font-size: 8px">
|
||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: propTypes.oneOfType([String, Object, Array]),
|
||||
title: propTypes.string.def('文件上传'),
|
||||
updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
|
||||
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
fileSize: propTypes.number.def(5), // 大小限制(MB)
|
||||
limit: propTypes.number.def(5), // 数量限制
|
||||
autoUpload: propTypes.bool.def(true), // 自动上传
|
||||
drag: propTypes.bool.def(false), // 拖拽上传
|
||||
isShowTip: propTypes.bool.def(true) // 是否显示提示
|
||||
})
|
||||
// ========== 上传相关 ==========
|
||||
const valueRef = ref(props.modelValue)
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const uploadList = ref<UploadUserFile[]>([])
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
const uploadNumber = ref<number>(0)
|
||||
const uploadHeaders = ref({
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
})
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
// 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
|
||||
const list = Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
: Array.isArray(props.modelValue?.split(','))
|
||||
? props.modelValue?.split(',')
|
||||
: Array.of(props.modelValue)
|
||||
// 然后将数组转为对象数组
|
||||
fileList.value = list.map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
// edit by 芋道源码
|
||||
item = { name: item, url: item }
|
||||
}
|
||||
return item
|
||||
})
|
||||
} else {
|
||||
fileList.value = []
|
||||
return []
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// 文件上传之前判断
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
|
||||
if (fileList.value.length >= props.limit) {
|
||||
message.error(`上传文件数量不能超过${props.limit}个!`)
|
||||
return false
|
||||
}
|
||||
let fileExtension = ''
|
||||
if (file.name.lastIndexOf('.') > -1) {
|
||||
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
|
||||
}
|
||||
const isImg = props.fileType.some((type: string) => {
|
||||
if (file.type.indexOf(type) > -1) return true
|
||||
return !!(fileExtension && fileExtension.indexOf(type) > -1)
|
||||
})
|
||||
const isLimit = file.size < props.fileSize * 1024 * 1024
|
||||
if (!isImg) {
|
||||
message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
|
||||
return false
|
||||
}
|
||||
if (!isLimit) {
|
||||
message.error(`上传文件大小不能超过${props.fileSize}MB!`)
|
||||
return false
|
||||
}
|
||||
message.success('正在上传文件,请稍候...')
|
||||
uploadNumber.value++
|
||||
}
|
||||
// 处理上传的文件发生变化
|
||||
// const handleFileChange = (uploadFile: UploadFile): void => {
|
||||
// uploadRef.value.data.path = uploadFile.name
|
||||
// }
|
||||
// 文件上传成功
|
||||
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
|
||||
message.success('上传成功')
|
||||
uploadList.value.push({ name: res.data, url: res.data })
|
||||
if (uploadList.value.length == uploadNumber.value) {
|
||||
fileList.value = fileList.value.concat(uploadList.value)
|
||||
uploadList.value = []
|
||||
uploadNumber.value = 0
|
||||
emit('update:modelValue', listToString(fileList.value))
|
||||
}
|
||||
}
|
||||
// 文件数超出提示
|
||||
const handleExceed: UploadProps['onExceed'] = (): void => {
|
||||
message.error(`上传文件数量不能超过${props.limit}个!`)
|
||||
}
|
||||
// 上传错误提示
|
||||
const excelUploadError: UploadProps['onError'] = (): void => {
|
||||
message.error('导入数据失败,请您重新上传!')
|
||||
}
|
||||
// 删除上传文件
|
||||
const handleRemove = (file) => {
|
||||
const findex = fileList.value.map((f) => f.name).indexOf(file.name)
|
||||
if (findex > -1) {
|
||||
fileList.value.splice(findex, 1)
|
||||
emit('update:modelValue', listToString(fileList.value))
|
||||
}
|
||||
}
|
||||
const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
|
||||
console.log(uploadFile)
|
||||
}
|
||||
// 对象转成指定字符串分隔
|
||||
const listToString = (list: UploadUserFile[], separator?: string) => {
|
||||
let strs = ''
|
||||
separator = separator || ','
|
||||
for (let i in list) {
|
||||
strs += list[i].url + separator
|
||||
}
|
||||
return strs != '' ? strs.substr(0, strs.length - 1) : ''
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upload-file-uploader {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
:deep(.upload-file-list .el-upload-list__item) {
|
||||
border: 1px solid #e4e7ed;
|
||||
line-height: 2;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
:deep(.el-upload-list__item-file-name) {
|
||||
max-width: 250px;
|
||||
}
|
||||
:deep(.upload-file-list .ele-upload-list__item-content) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
}
|
||||
:deep(.ele-upload-list__item-content-action .el-link) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
@ -1,24 +1,27 @@
|
||||
<template>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:multiple="limit > 1"
|
||||
name="file"
|
||||
list-type="picture-card"
|
||||
v-model:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
:action="updateUrl"
|
||||
:headers="uploadHeaders"
|
||||
:limit="limit"
|
||||
:before-upload="beforeUpload"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="excelUploadError"
|
||||
:on-remove="handleRemove"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:class="{ hide: fileList.length >= limit }"
|
||||
>
|
||||
<Icon icon="ep:upload-filled" />
|
||||
</el-upload>
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:multiple="props.limit > 1"
|
||||
name="file"
|
||||
v-model="valueRef"
|
||||
list-type="picture-card"
|
||||
v-model:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
:action="updateUrl"
|
||||
:headers="uploadHeaders"
|
||||
:limit="props.limit"
|
||||
:before-upload="beforeUpload"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="excelUploadError"
|
||||
:on-remove="handleRemove"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:class="{ hide: fileList.length >= props.limit }"
|
||||
>
|
||||
<Icon icon="ep:upload-filled" />
|
||||
</el-upload>
|
||||
</div>
|
||||
<!-- 文件列表 -->
|
||||
<Dialog v-model="dialogVisible" title="预览" width="800" append-to-body>
|
||||
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
|
||||
@ -26,16 +29,17 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const emit = defineEmits(['input'])
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const props = defineProps({
|
||||
imgs: propTypes.oneOfType([String, Object, Array]),
|
||||
modelValue: propTypes.oneOfType([String, Object, Array]),
|
||||
title: propTypes.string.def('图片上传'),
|
||||
updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
|
||||
fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
@ -44,6 +48,7 @@ const props = defineProps({
|
||||
isShowTip: propTypes.bool.def(false) // 是否显示提示
|
||||
})
|
||||
// ========== 上传相关 ==========
|
||||
const valueRef = ref(props.modelValue)
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const uploadList = ref<UploadUserFile[]>([])
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
@ -55,15 +60,15 @@ const uploadHeaders = ref({
|
||||
'tenant-id': getTenantId()
|
||||
})
|
||||
watch(
|
||||
() => props.imgs,
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
// 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
|
||||
const list = Array.isArray(props.imgs)
|
||||
? props.imgs
|
||||
: Array.isArray(props.imgs?.split(','))
|
||||
? props.imgs?.split(',')
|
||||
: Array.of(props.imgs)
|
||||
const list = Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
: Array.isArray(props.modelValue?.split(','))
|
||||
? props.modelValue?.split(',')
|
||||
: Array.of(props.modelValue)
|
||||
// 然后将数组转为对象数组
|
||||
fileList.value = list.map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
@ -84,6 +89,10 @@ watch(
|
||||
)
|
||||
// 文件上传之前判断
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
|
||||
if (fileList.value.length >= props.limit) {
|
||||
message.error(`上传文件数量不能超过${props.limit}个!`)
|
||||
return false
|
||||
}
|
||||
let fileExtension = ''
|
||||
if (file.name.lastIndexOf('.') > -1) {
|
||||
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
|
||||
@ -111,14 +120,12 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
|
||||
// 文件上传成功
|
||||
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
|
||||
message.success('上传成功')
|
||||
console.info(uploadList.value)
|
||||
console.info(fileList.value)
|
||||
uploadList.value.push({ name: res.data, url: res.data })
|
||||
if (uploadList.value.length == uploadNumber.value) {
|
||||
fileList.value = fileList.value.concat(uploadList.value)
|
||||
uploadList.value = []
|
||||
uploadNumber.value = 0
|
||||
emit('input', listToString(fileList.value))
|
||||
emit('update:modelValue', listToString(fileList.value))
|
||||
}
|
||||
}
|
||||
// 文件数超出提示
|
||||
@ -134,7 +141,7 @@ const handleRemove = (file) => {
|
||||
const findex = fileList.value.map((f) => f.name).indexOf(file.name)
|
||||
if (findex > -1) {
|
||||
fileList.value.splice(findex, 1)
|
||||
emit('input', listToString(fileList.value))
|
||||
emit('update:modelValue', listToString(fileList.value))
|
||||
}
|
||||
}
|
||||
// 对象转成指定字符串分隔
|
||||
|
@ -191,6 +191,7 @@ export default {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
.verify-tips {
|
||||
text-indent: 10px;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
|
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
<script type="text/babel" setup>
|
||||
/**
|
||||
* VerifyPoints
|
||||
* @description 点选
|
||||
@ -67,215 +67,185 @@ import { aesEncrypt } from './../utils/ase'
|
||||
import { getCodeApi, reqCheckApi } from '@/api/login'
|
||||
import { onMounted, reactive, ref, nextTick, toRefs, getCurrentInstance } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
export default {
|
||||
name: 'VerifyPoints',
|
||||
props: {
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
//间隔
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
//间隔
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
let secretKey = ref(''), //后端返回的ase加密秘钥
|
||||
checkNum = ref(3), //默认需要点击的字数
|
||||
fontPos = reactive([]), //选中的坐标信息
|
||||
checkPosArr = reactive([]), //用户点击的坐标
|
||||
num = ref(1), //点击的记数
|
||||
pointBackImgBase = ref(''), //后端获取到的背景图片
|
||||
poinTextList = reactive([]), //后端返回的点击字体顺序
|
||||
backToken = ref(''), //后端返回的token值
|
||||
setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
}),
|
||||
tempPoints = reactive([]),
|
||||
text = ref(''),
|
||||
barAreaColor = ref(undefined),
|
||||
barAreaBorderColor = ref(undefined),
|
||||
showRefresh = ref(true),
|
||||
bindingClick = ref(true)
|
||||
|
||||
const init = () => {
|
||||
//加载页面
|
||||
fontPos.splice(0, fontPos.length)
|
||||
checkPosArr.splice(0, checkPosArr.length)
|
||||
num.value = 1
|
||||
getPictrue()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
})
|
||||
const canvas = ref(null)
|
||||
const canvasClick = (e) => {
|
||||
checkPosArr.push(getMousePos(canvas, e))
|
||||
if (num.value == checkNum.value) {
|
||||
num.value = createPoint(getMousePos(canvas, e))
|
||||
//按比例转换坐标值
|
||||
let arr = pointTransfrom(checkPosArr, setSize)
|
||||
checkPosArr.length = 0
|
||||
checkPosArr.push(...arr)
|
||||
//等创建坐标执行完
|
||||
setTimeout(() => {
|
||||
// var flag = this.comparePos(this.fontPos, this.checkPosArr);
|
||||
//发送后端请求
|
||||
var captchaVerification = secretKey.value
|
||||
? aesEncrypt(backToken.value + '---' + JSON.stringify(checkPosArr), secretKey.value)
|
||||
: backToken.value + '---' + JSON.stringify(checkPosArr)
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value
|
||||
? aesEncrypt(JSON.stringify(checkPosArr), secretKey.value)
|
||||
: JSON.stringify(checkPosArr),
|
||||
token: backToken.value
|
||||
}
|
||||
reqCheckApi(data).then((res) => {
|
||||
if (res.repCode == '0000') {
|
||||
barAreaColor.value = '#4cae4c'
|
||||
barAreaBorderColor.value = '#5cb85c'
|
||||
text.value = t('captcha.success')
|
||||
bindingClick.value = false
|
||||
if (mode.value == 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
} else {
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
barAreaColor.value = '#d9534f'
|
||||
barAreaBorderColor.value = '#d9534f'
|
||||
text.value = t('captcha.fail')
|
||||
setTimeout(() => {
|
||||
refresh()
|
||||
}, 700)
|
||||
}
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
if (num.value < checkNum.value) {
|
||||
num.value = createPoint(getMousePos(canvas, e))
|
||||
}
|
||||
}
|
||||
//获取坐标
|
||||
const getMousePos = function (obj, e) {
|
||||
var x = e.offsetX
|
||||
var y = e.offsetY
|
||||
return { x, y }
|
||||
}
|
||||
//创建坐标点
|
||||
const createPoint = function (pos) {
|
||||
tempPoints.push(Object.assign({}, pos))
|
||||
return num.value + 1
|
||||
}
|
||||
const refresh = function () {
|
||||
tempPoints.splice(0, tempPoints.length)
|
||||
barAreaColor.value = '#000'
|
||||
barAreaBorderColor.value = '#ddd'
|
||||
bindingClick.value = true
|
||||
fontPos.splice(0, fontPos.length)
|
||||
checkPosArr.splice(0, checkPosArr.length)
|
||||
num.value = 1
|
||||
getPictrue()
|
||||
text.value = t('captcha.fail')
|
||||
showRefresh.value = true
|
||||
}
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
function getPictrue() {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
getCodeApi(data).then((res) => {
|
||||
if (res.repCode == '0000') {
|
||||
pointBackImgBase.value = res.repData.originalImageBase64
|
||||
backToken.value = res.repData.token
|
||||
secretKey.value = res.repData.secretKey
|
||||
poinTextList.value = res.repData.wordList
|
||||
text.value = t('captcha.point') + '【' + poinTextList.value.join(',') + '】'
|
||||
} else {
|
||||
text.value = res.repMsg
|
||||
}
|
||||
})
|
||||
}
|
||||
//坐标转换函数
|
||||
const pointTransfrom = function (pointArr, imgSize) {
|
||||
var newPointArr = pointArr.map((p) => {
|
||||
let x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))
|
||||
let y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))
|
||||
return { x, y }
|
||||
})
|
||||
return newPointArr
|
||||
}
|
||||
return {
|
||||
secretKey,
|
||||
checkNum,
|
||||
fontPos,
|
||||
checkPosArr,
|
||||
num,
|
||||
pointBackImgBase,
|
||||
poinTextList,
|
||||
backToken,
|
||||
setSize,
|
||||
tempPoints,
|
||||
text,
|
||||
barAreaColor,
|
||||
barAreaBorderColor,
|
||||
showRefresh,
|
||||
bindingClick,
|
||||
init,
|
||||
canvas,
|
||||
canvasClick,
|
||||
getMousePos,
|
||||
createPoint,
|
||||
refresh,
|
||||
getPictrue,
|
||||
pointTransfrom
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
let secretKey = ref(''), //后端返回的ase加密秘钥
|
||||
checkNum = ref(3), //默认需要点击的字数
|
||||
fontPos = reactive([]), //选中的坐标信息
|
||||
checkPosArr = reactive([]), //用户点击的坐标
|
||||
num = ref(1), //点击的记数
|
||||
pointBackImgBase = ref(''), //后端获取到的背景图片
|
||||
poinTextList = reactive([]), //后端返回的点击字体顺序
|
||||
backToken = ref(''), //后端返回的token值
|
||||
setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
}),
|
||||
tempPoints = reactive([]),
|
||||
text = ref(''),
|
||||
barAreaColor = ref(undefined),
|
||||
barAreaBorderColor = ref(undefined),
|
||||
showRefresh = ref(true),
|
||||
bindingClick = ref(true)
|
||||
|
||||
const init = () => {
|
||||
//加载页面
|
||||
fontPos.splice(0, fontPos.length)
|
||||
checkPosArr.splice(0, checkPosArr.length)
|
||||
num.value = 1
|
||||
getPictrue()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
const canvas = ref(null)
|
||||
const canvasClick = (e) => {
|
||||
checkPosArr.push(getMousePos(canvas, e))
|
||||
if (num.value == checkNum.value) {
|
||||
num.value = createPoint(getMousePos(canvas, e))
|
||||
//按比例转换坐标值
|
||||
let arr = pointTransfrom(checkPosArr, setSize)
|
||||
checkPosArr.length = 0
|
||||
checkPosArr.push(...arr)
|
||||
//等创建坐标执行完
|
||||
setTimeout(() => {
|
||||
// var flag = this.comparePos(this.fontPos, this.checkPosArr);
|
||||
//发送后端请求
|
||||
var captchaVerification = secretKey.value
|
||||
? aesEncrypt(backToken.value + '---' + JSON.stringify(checkPosArr), secretKey.value)
|
||||
: backToken.value + '---' + JSON.stringify(checkPosArr)
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value
|
||||
? aesEncrypt(JSON.stringify(checkPosArr), secretKey.value)
|
||||
: JSON.stringify(checkPosArr),
|
||||
token: backToken.value
|
||||
}
|
||||
reqCheckApi(data).then((res) => {
|
||||
if (res.repCode == '0000') {
|
||||
barAreaColor.value = '#4cae4c'
|
||||
barAreaBorderColor.value = '#5cb85c'
|
||||
text.value = t('captcha.success')
|
||||
bindingClick.value = false
|
||||
if (mode.value == 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
} else {
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
barAreaColor.value = '#d9534f'
|
||||
barAreaBorderColor.value = '#d9534f'
|
||||
text.value = t('captcha.fail')
|
||||
setTimeout(() => {
|
||||
refresh()
|
||||
}, 700)
|
||||
}
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
if (num.value < checkNum.value) {
|
||||
num.value = createPoint(getMousePos(canvas, e))
|
||||
}
|
||||
}
|
||||
//获取坐标
|
||||
const getMousePos = function (obj, e) {
|
||||
var x = e.offsetX
|
||||
var y = e.offsetY
|
||||
return { x, y }
|
||||
}
|
||||
//创建坐标点
|
||||
const createPoint = function (pos) {
|
||||
tempPoints.push(Object.assign({}, pos))
|
||||
return num.value + 1
|
||||
}
|
||||
const refresh = async function () {
|
||||
tempPoints.splice(0, tempPoints.length)
|
||||
barAreaColor.value = '#000'
|
||||
barAreaBorderColor.value = '#ddd'
|
||||
bindingClick.value = true
|
||||
fontPos.splice(0, fontPos.length)
|
||||
checkPosArr.splice(0, checkPosArr.length)
|
||||
num.value = 1
|
||||
await getPictrue()
|
||||
showRefresh.value = true
|
||||
}
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
const getPictrue = async () => {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
const res = await getCodeApi(data)
|
||||
if (res.repCode == '0000') {
|
||||
pointBackImgBase.value = res.repData.originalImageBase64
|
||||
backToken.value = res.repData.token
|
||||
secretKey.value = res.repData.secretKey
|
||||
poinTextList.value = res.repData.wordList
|
||||
text.value = t('captcha.point') + '【' + poinTextList.value.join(',') + '】'
|
||||
} else {
|
||||
text.value = res.repMsg
|
||||
}
|
||||
}
|
||||
//坐标转换函数
|
||||
const pointTransfrom = function (pointArr, imgSize) {
|
||||
var newPointArr = pointArr.map((p) => {
|
||||
let x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))
|
||||
let y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))
|
||||
return { x, y }
|
||||
})
|
||||
return newPointArr
|
||||
}
|
||||
</script>
|
||||
|
@ -71,7 +71,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
<script type="text/babel" setup>
|
||||
/**
|
||||
* VerifySlide
|
||||
* @description 滑块
|
||||
@ -90,337 +90,298 @@ import {
|
||||
toRefs,
|
||||
getCurrentInstance
|
||||
} from 'vue'
|
||||
// "captchaType":"blockPuzzle",
|
||||
export default {
|
||||
name: 'VerifySlide',
|
||||
props: {
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
explain: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
blockSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '30px'
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
explain: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType, type, blockSize, explain } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
let secretKey = ref(''), //后端返回的ase加密秘钥
|
||||
passFlag = ref(''), //是否通过的标识
|
||||
backImgBase = ref(''), //验证码背景图片
|
||||
blockBackImgBase = ref(''), //验证滑块的背景图片
|
||||
backToken = ref(''), //后端返回的唯一token值
|
||||
startMoveTime = ref(''), //移动开始的时间
|
||||
endMovetime = ref(''), //移动结束的时间
|
||||
tipsBackColor = ref(''), //提示词的背景颜色
|
||||
tipWords = ref(''),
|
||||
text = ref(''),
|
||||
finishText = ref(''),
|
||||
setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
}),
|
||||
top = ref(0),
|
||||
left = ref(0),
|
||||
moveBlockLeft = ref(undefined),
|
||||
leftBarWidth = ref(undefined),
|
||||
// 移动中样式
|
||||
moveBlockBackgroundColor = ref(undefined),
|
||||
leftBarBorderColor = ref('#ddd'),
|
||||
iconColor = ref(undefined),
|
||||
iconClass = ref('icon-right'),
|
||||
status = ref(false), //鼠标状态
|
||||
isEnd = ref(false), //是够验证完成
|
||||
showRefresh = ref(true),
|
||||
transitionLeft = ref(''),
|
||||
transitionWidth = ref(''),
|
||||
startLeft = ref(0)
|
||||
|
||||
const barArea = computed(() => {
|
||||
return proxy.$el.querySelector('.verify-bar-area')
|
||||
})
|
||||
function init() {
|
||||
if (explain.value === '') {
|
||||
text.value = t('captcha.slide')
|
||||
} else {
|
||||
text.value = explain.value
|
||||
blockSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
getPictrue()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
|
||||
window.removeEventListener('touchmove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
window.removeEventListener('mousemove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
|
||||
//鼠标松开
|
||||
window.removeEventListener('touchend', function () {
|
||||
end()
|
||||
})
|
||||
window.removeEventListener('mouseup', function () {
|
||||
end()
|
||||
})
|
||||
|
||||
window.addEventListener('touchmove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
|
||||
//鼠标松开
|
||||
window.addEventListener('touchend', function () {
|
||||
end()
|
||||
})
|
||||
window.addEventListener('mouseup', function () {
|
||||
end()
|
||||
})
|
||||
}
|
||||
watch(type, () => {
|
||||
init()
|
||||
})
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '30px'
|
||||
}
|
||||
})
|
||||
//鼠标按下
|
||||
function start(e) {
|
||||
e = e || window.event
|
||||
if (!e.touches) {
|
||||
//兼容PC端
|
||||
var x = e.clientX
|
||||
} else {
|
||||
//兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)
|
||||
startMoveTime.value = +new Date() //开始滑动的时间
|
||||
if (isEnd.value == false) {
|
||||
text.value = ''
|
||||
moveBlockBackgroundColor.value = '#337ab7'
|
||||
leftBarBorderColor.value = '#337AB7'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType, type, blockSize, explain } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
let secretKey = ref(''), //后端返回的ase加密秘钥
|
||||
passFlag = ref(''), //是否通过的标识
|
||||
backImgBase = ref(''), //验证码背景图片
|
||||
blockBackImgBase = ref(''), //验证滑块的背景图片
|
||||
backToken = ref(''), //后端返回的唯一token值
|
||||
startMoveTime = ref(''), //移动开始的时间
|
||||
endMovetime = ref(''), //移动结束的时间
|
||||
tipWords = ref(''),
|
||||
text = ref(''),
|
||||
finishText = ref(''),
|
||||
setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
}),
|
||||
moveBlockLeft = ref(undefined),
|
||||
leftBarWidth = ref(undefined),
|
||||
// 移动中样式
|
||||
moveBlockBackgroundColor = ref(undefined),
|
||||
leftBarBorderColor = ref('#ddd'),
|
||||
iconColor = ref(undefined),
|
||||
iconClass = ref('icon-right'),
|
||||
status = ref(false), //鼠标状态
|
||||
isEnd = ref(false), //是够验证完成
|
||||
showRefresh = ref(true),
|
||||
transitionLeft = ref(''),
|
||||
transitionWidth = ref(''),
|
||||
startLeft = ref(0)
|
||||
|
||||
const barArea = computed(() => {
|
||||
return proxy.$el.querySelector('.verify-bar-area')
|
||||
})
|
||||
const init = () => {
|
||||
if (explain.value === '') {
|
||||
text.value = t('captcha.slide')
|
||||
} else {
|
||||
text.value = explain.value
|
||||
}
|
||||
getPictrue()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
|
||||
window.removeEventListener('touchmove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
window.removeEventListener('mousemove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
|
||||
//鼠标松开
|
||||
window.removeEventListener('touchend', function () {
|
||||
end()
|
||||
})
|
||||
window.removeEventListener('mouseup', function () {
|
||||
end()
|
||||
})
|
||||
|
||||
window.addEventListener('touchmove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
move(e)
|
||||
})
|
||||
|
||||
//鼠标松开
|
||||
window.addEventListener('touchend', function () {
|
||||
end()
|
||||
})
|
||||
window.addEventListener('mouseup', function () {
|
||||
end()
|
||||
})
|
||||
}
|
||||
watch(type, () => {
|
||||
init()
|
||||
})
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
//鼠标按下
|
||||
const start = (e) => {
|
||||
e = e || window.event
|
||||
if (!e.touches) {
|
||||
//兼容PC端
|
||||
var x = e.clientX
|
||||
} else {
|
||||
//兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)
|
||||
startMoveTime.value = +new Date() //开始滑动的时间
|
||||
if (isEnd.value == false) {
|
||||
text.value = ''
|
||||
moveBlockBackgroundColor.value = '#337ab7'
|
||||
leftBarBorderColor.value = '#337AB7'
|
||||
iconColor.value = '#fff'
|
||||
e.stopPropagation()
|
||||
status.value = true
|
||||
}
|
||||
}
|
||||
//鼠标移动
|
||||
const move = (e) => {
|
||||
e = e || window.event
|
||||
if (status.value && isEnd.value == false) {
|
||||
if (!e.touches) {
|
||||
//兼容PC端
|
||||
var x = e.clientX
|
||||
} else {
|
||||
//兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
var bar_area_left = barArea.value.getBoundingClientRect().left
|
||||
var move_block_left = x - bar_area_left //小方块相对于父元素的left值
|
||||
if (
|
||||
move_block_left >=
|
||||
barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
|
||||
) {
|
||||
move_block_left =
|
||||
barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
|
||||
}
|
||||
if (move_block_left <= 0) {
|
||||
move_block_left = parseInt(parseInt(blockSize.value.width) / 2)
|
||||
}
|
||||
//拖动后小方块的left值
|
||||
moveBlockLeft.value = move_block_left - startLeft.value + 'px'
|
||||
leftBarWidth.value = move_block_left - startLeft.value + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
//鼠标松开
|
||||
const end = () => {
|
||||
endMovetime.value = +new Date()
|
||||
//判断是否重合
|
||||
if (status.value && isEnd.value == false) {
|
||||
var moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', ''))
|
||||
moveLeftDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth)
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value
|
||||
? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
|
||||
: JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
token: backToken.value
|
||||
}
|
||||
reqCheckApi(data).then((res) => {
|
||||
if (res.repCode == '0000') {
|
||||
moveBlockBackgroundColor.value = '#5cb85c'
|
||||
leftBarBorderColor.value = '#5cb85c'
|
||||
iconColor.value = '#fff'
|
||||
e.stopPropagation()
|
||||
status.value = true
|
||||
}
|
||||
}
|
||||
//鼠标移动
|
||||
function move(e) {
|
||||
e = e || window.event
|
||||
if (status.value && isEnd.value == false) {
|
||||
if (!e.touches) {
|
||||
//兼容PC端
|
||||
var x = e.clientX
|
||||
} else {
|
||||
//兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
iconClass.value = 'icon-check'
|
||||
showRefresh.value = false
|
||||
isEnd.value = true
|
||||
if (mode.value == 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
var bar_area_left = barArea.value.getBoundingClientRect().left
|
||||
var move_block_left = x - bar_area_left //小方块相对于父元素的left值
|
||||
if (
|
||||
move_block_left >=
|
||||
barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
|
||||
) {
|
||||
move_block_left =
|
||||
barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
|
||||
}
|
||||
if (move_block_left <= 0) {
|
||||
move_block_left = parseInt(parseInt(blockSize.value.width) / 2)
|
||||
}
|
||||
//拖动后小方块的left值
|
||||
moveBlockLeft.value = move_block_left - startLeft.value + 'px'
|
||||
leftBarWidth.value = move_block_left - startLeft.value + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
//鼠标松开
|
||||
function end() {
|
||||
endMovetime.value = +new Date()
|
||||
//判断是否重合
|
||||
if (status.value && isEnd.value == false) {
|
||||
var moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', ''))
|
||||
moveLeftDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth)
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value
|
||||
? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
|
||||
: JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
token: backToken.value
|
||||
}
|
||||
reqCheckApi(data).then((res) => {
|
||||
if (res.repCode == '0000') {
|
||||
moveBlockBackgroundColor.value = '#5cb85c'
|
||||
leftBarBorderColor.value = '#5cb85c'
|
||||
iconColor.value = '#fff'
|
||||
iconClass.value = 'icon-check'
|
||||
showRefresh.value = false
|
||||
isEnd.value = true
|
||||
if (mode.value == 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
passFlag.value = true
|
||||
tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s
|
||||
passFlag.value = true
|
||||
tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s
|
||||
${t('captcha.success')}`
|
||||
var captchaVerification = secretKey.value
|
||||
? aesEncrypt(
|
||||
backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
secretKey.value
|
||||
)
|
||||
: backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
proxy.$parent.closeBox()
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
}, 1000)
|
||||
} else {
|
||||
moveBlockBackgroundColor.value = '#d9534f'
|
||||
leftBarBorderColor.value = '#d9534f'
|
||||
iconColor.value = '#fff'
|
||||
iconClass.value = 'icon-close'
|
||||
passFlag.value = false
|
||||
setTimeout(function () {
|
||||
refresh()
|
||||
}, 1000)
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
tipWords.value = t('captcha.fail')
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
status.value = false
|
||||
var captchaVerification = secretKey.value
|
||||
? aesEncrypt(
|
||||
backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
secretKey.value
|
||||
)
|
||||
: backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
proxy.$parent.closeBox()
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
}, 1000)
|
||||
} else {
|
||||
moveBlockBackgroundColor.value = '#d9534f'
|
||||
leftBarBorderColor.value = '#d9534f'
|
||||
iconColor.value = '#fff'
|
||||
iconClass.value = 'icon-close'
|
||||
passFlag.value = false
|
||||
setTimeout(function () {
|
||||
refresh()
|
||||
}, 1000)
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
tipWords.value = t('captcha.fail')
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
status.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
showRefresh.value = true
|
||||
finishText.value = ''
|
||||
const refresh = async () => {
|
||||
showRefresh.value = true
|
||||
finishText.value = ''
|
||||
|
||||
transitionLeft.value = 'left .3s'
|
||||
moveBlockLeft.value = 0
|
||||
transitionLeft.value = 'left .3s'
|
||||
moveBlockLeft.value = 0
|
||||
|
||||
leftBarWidth.value = undefined
|
||||
transitionWidth.value = 'width .3s'
|
||||
leftBarWidth.value = undefined
|
||||
transitionWidth.value = 'width .3s'
|
||||
|
||||
leftBarBorderColor.value = '#ddd'
|
||||
moveBlockBackgroundColor.value = '#fff'
|
||||
iconColor.value = '#000'
|
||||
iconClass.value = 'icon-right'
|
||||
isEnd.value = false
|
||||
leftBarBorderColor.value = '#ddd'
|
||||
moveBlockBackgroundColor.value = '#fff'
|
||||
iconColor.value = '#000'
|
||||
iconClass.value = 'icon-right'
|
||||
isEnd.value = false
|
||||
|
||||
getPictrue()
|
||||
setTimeout(() => {
|
||||
transitionWidth.value = ''
|
||||
transitionLeft.value = ''
|
||||
text.value = explain.value
|
||||
}, 300)
|
||||
}
|
||||
await getPictrue()
|
||||
setTimeout(() => {
|
||||
transitionWidth.value = ''
|
||||
transitionLeft.value = ''
|
||||
text.value = explain.value
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
function getPictrue() {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
getCodeApi(data).then((res) => {
|
||||
if (res.repCode == '0000') {
|
||||
backImgBase.value = res.repData.originalImageBase64
|
||||
blockBackImgBase.value = res.repData.jigsawImageBase64
|
||||
backToken.value = res.repData.token
|
||||
secretKey.value = res.repData.secretKey
|
||||
} else {
|
||||
tipWords.value = res.repMsg
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
secretKey, //后端返回的ase加密秘钥
|
||||
passFlag, //是否通过的标识
|
||||
backImgBase, //验证码背景图片
|
||||
blockBackImgBase, //验证滑块的背景图片
|
||||
backToken, //后端返回的唯一token值
|
||||
startMoveTime, //移动开始的时间
|
||||
endMovetime, //移动结束的时间
|
||||
tipsBackColor, //提示词的背景颜色
|
||||
tipWords,
|
||||
text,
|
||||
finishText,
|
||||
setSize,
|
||||
top,
|
||||
left,
|
||||
moveBlockLeft,
|
||||
leftBarWidth,
|
||||
// 移动中样式
|
||||
moveBlockBackgroundColor,
|
||||
leftBarBorderColor,
|
||||
iconColor,
|
||||
iconClass,
|
||||
status, //鼠标状态
|
||||
isEnd, //是够验证完成
|
||||
showRefresh,
|
||||
transitionLeft,
|
||||
transitionWidth,
|
||||
barArea,
|
||||
refresh,
|
||||
start
|
||||
}
|
||||
// 请求背景图片和验证图片
|
||||
const getPictrue = async () => {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
const res = await getCodeApi(data)
|
||||
if (res.repCode == '0000') {
|
||||
backImgBase.value = res.repData.originalImageBase64
|
||||
blockBackImgBase.value = res.repData.jigsawImageBase64
|
||||
backToken.value = res.repData.token
|
||||
secretKey.value = res.repData.secretKey
|
||||
} else {
|
||||
tipWords.value = res.repMsg
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -10,8 +10,8 @@ const props = defineProps({
|
||||
fullscreen: propTypes.bool.def(false),
|
||||
loading: propTypes.bool.def(false),
|
||||
title: propTypes.string.def('弹窗'),
|
||||
width: propTypes.string.def('800'),
|
||||
height: propTypes.string.def('480'),
|
||||
width: propTypes.string.def('40%'),
|
||||
height: propTypes.string.def('60%'),
|
||||
minWidth: propTypes.string.def('460'),
|
||||
minHeight: propTypes.string.def('320'),
|
||||
showFooter: propTypes.bool.def(true)
|
||||
|
@ -3,7 +3,6 @@ import { Icon } from './Icon'
|
||||
import { Form } from '@/components/Form'
|
||||
import { Table } from '@/components/Table'
|
||||
import { Search } from '@/components/Search'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { XModal } from '@/components/XModal'
|
||||
import { XButton, XTextButton } from '@/components/XButton'
|
||||
import { DictTag } from '@/components/DictTag'
|
||||
@ -15,7 +14,6 @@ export const setupGlobCom = (app: App<Element>): void => {
|
||||
app.component('Form', Form)
|
||||
app.component('Table', Table)
|
||||
app.component('Search', Search)
|
||||
app.component('Dialog', Dialog)
|
||||
app.component('XModal', XModal)
|
||||
app.component('XButton', XButton)
|
||||
app.component('XTextButton', XTextButton)
|
||||
|
@ -168,15 +168,19 @@ service.interceptors.response.use(
|
||||
ElMessage.error(t('sys.api.errMsg500'))
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code === 901) {
|
||||
ElMessage.error(
|
||||
'<div>' +
|
||||
ElMessage.error({
|
||||
duration: 5,
|
||||
offset: 300,
|
||||
dangerouslyUseHTMLString: true,
|
||||
message:
|
||||
'<div>' +
|
||||
t('sys.api.errMsg901') +
|
||||
'</div>' +
|
||||
'<div> </div>' +
|
||||
'<div>参考 https://doc.iocoder.cn/ 教程</div>' +
|
||||
'<div> </div>' +
|
||||
'<div>5 分钟搭建本地环境</div>'
|
||||
)
|
||||
})
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code !== 200) {
|
||||
if (msg === '无效的刷新令牌') {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { App } from 'vue'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
@ -8,7 +8,7 @@ export function hasPermi(app: App<Element>) {
|
||||
const { wsCache } = useCache()
|
||||
const { value } = binding
|
||||
const all_permission = '*:*:*'
|
||||
const permissions = wsCache.get('user').permissions
|
||||
const permissions = wsCache.get(CACHE_KEY.USER).permissions
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const permissionFlag = value
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { App } from 'vue'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
@ -8,7 +8,7 @@ export function hasRole(app: App<Element>) {
|
||||
const { wsCache } = useCache()
|
||||
const { value } = binding
|
||||
const super_admin = 'admin'
|
||||
const roles = wsCache.get('user').roles
|
||||
const roles = wsCache.get(CACHE_KEY.USER).roles
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const roleFlag = value
|
||||
|
@ -6,6 +6,16 @@ import WebStorageCache from 'web-storage-cache'
|
||||
|
||||
type CacheType = 'localStorage' | 'sessionStorage'
|
||||
|
||||
export const CACHE_KEY = {
|
||||
IS_DARK: 'isDark',
|
||||
USER: 'user',
|
||||
LANG: 'lang',
|
||||
THEME: 'theme',
|
||||
LAYOUT: 'layout',
|
||||
ROLE_ROUTERS: 'roleRouters',
|
||||
DICT_CACHE: 'dictCache'
|
||||
}
|
||||
|
||||
export const useCache = (type: CacheType = 'localStorage') => {
|
||||
const wsCache: WebStorageCache = new WebStorageCache({
|
||||
storage: type
|
||||
|
@ -208,7 +208,8 @@ const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns
|
||||
const tableSchemaItem = {
|
||||
...schemaItem.table,
|
||||
field: schemaItem.field,
|
||||
title: schemaItem.table?.title || schemaItem.title
|
||||
title: schemaItem.table?.title || schemaItem.title,
|
||||
minWidth: '80px'
|
||||
}
|
||||
tableSchemaItem.showOverflow = 'tooltip'
|
||||
if (schemaItem?.formatter) {
|
||||
@ -231,6 +232,7 @@ const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns
|
||||
const tableSchemaItem = {
|
||||
title: crudSchema.actionTitle ? crudSchema.actionTitle : t('table.action'),
|
||||
field: 'actionbtns',
|
||||
fixed: 'right' as unknown as VxeColumnPropTypes.Fixed,
|
||||
width: crudSchema.actionWidth ? crudSchema.actionWidth : '200px',
|
||||
slots: {
|
||||
default: 'actionbtns_default'
|
||||
|
@ -11,10 +11,12 @@ const message = useMessage() // 消息弹窗
|
||||
|
||||
interface UseVxeGridConfig<T = any> {
|
||||
allSchemas: VxeAllSchemas
|
||||
height?: number // 高度 默认730
|
||||
topActionSlots?: boolean // 是否开启表格内顶部操作栏插槽
|
||||
treeConfig?: VxeTablePropTypes.TreeConfig // 树形表单配置
|
||||
isList?: boolean // 是否不带分页的list
|
||||
getListApi: (option: any) => Promise<T> // 获取列表接口
|
||||
getAllListApi?: (option: any) => Promise<T> // 获取全部数据接口 用于VXE导出
|
||||
deleteApi?: (option: any) => Promise<T> // 删除接口
|
||||
exportListApi?: (option: any) => Promise<T> // 导出接口
|
||||
exportName?: string // 导出文件夹名称
|
||||
@ -47,7 +49,7 @@ export const useVxeGrid = <T = any>(config?: UseVxeGridConfig<T>) => {
|
||||
const gridOptions = reactive<VxeGridProps<any>>({
|
||||
loading: true,
|
||||
size: currentSize as any,
|
||||
height: 730, // 1080高度
|
||||
height: config?.height ? config.height : 730,
|
||||
rowConfig: {
|
||||
isCurrent: true, // 当鼠标点击行时,是否要高亮当前行
|
||||
isHover: true // 当鼠标移到行时,是否要高亮当前行
|
||||
@ -99,8 +101,8 @@ export const useVxeGrid = <T = any>(config?: UseVxeGridConfig<T>) => {
|
||||
queryAll: ({ form }) => {
|
||||
const queryParams = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
return new Promise(async (resolve) => {
|
||||
if (config?.exportListApi) {
|
||||
resolve(await config?.exportListApi(queryParams))
|
||||
if (config?.getAllListApi) {
|
||||
resolve(await config?.getAllListApi(queryParams))
|
||||
} else {
|
||||
resolve(await config?.getListApi(queryParams))
|
||||
}
|
||||
@ -113,7 +115,7 @@ export const useVxeGrid = <T = any>(config?: UseVxeGridConfig<T>) => {
|
||||
// 默认选中类型
|
||||
type: 'csv',
|
||||
// 自定义数据量列表
|
||||
modes: ['current', 'all'],
|
||||
modes: config?.getAllListApi ? ['current', 'all'] : ['current'],
|
||||
columns: config?.allSchemas.printSchema
|
||||
}
|
||||
})
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { computed, defineComponent, unref } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { Backtop } from '@/components/Backtop'
|
||||
import { Setting } from '@/components/Setting'
|
||||
import { Setting } from '@/layout/components/Setting'
|
||||
import { useRenderLayout } from './components/useRenderLayout'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Footer } from '@/layout/components/Footer'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
@ -3,13 +3,13 @@ import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||
import { ref, unref, computed, watch } from 'vue'
|
||||
import { useCssVar, useClipboard } from '@vueuse/core'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
import { trim, setCssVar } from '@/utils'
|
||||
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||
@ -188,9 +188,9 @@ const copyConfig = async () => {
|
||||
// 清空缓存
|
||||
const clear = () => {
|
||||
const { wsCache } = useCache()
|
||||
wsCache.delete('layout')
|
||||
wsCache.delete('theme')
|
||||
wsCache.delete('isDark')
|
||||
wsCache.delete(CACHE_KEY.LAYOUT)
|
||||
wsCache.delete(CACHE_KEY.THEME)
|
||||
wsCache.delete(CACHE_KEY.IS_DARK)
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
@ -5,7 +5,7 @@ import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { Menu } from '@/components/Menu'
|
||||
import { Menu } from '@/layout/components/Menu'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
import { cloneDeep } from 'lodash-es'
|
@ -1,4 +1,4 @@
|
||||
import { getAllParentPath } from '@/components/Menu/src/helper'
|
||||
import { getAllParentPath } from '@/layout/components/Menu/src/helper'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { cloneDeep } from 'lodash-es'
|
@ -7,7 +7,7 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { filterAffixTags } from './helper'
|
||||
import { ContextMenu, ContextMenuExpose } from '@/components/ContextMenu'
|
||||
import { ContextMenu, ContextMenuExpose } from '@/layout/components/ContextMenu'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useTemplateRefsList } from '@vueuse/core'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
@ -114,8 +114,8 @@ const toLastView = () => {
|
||||
addTags()
|
||||
return
|
||||
}
|
||||
// You can set another route
|
||||
push(permissionStore.getAddRouters[0].path)
|
||||
// TODO: You can set another route
|
||||
push('/')
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +128,6 @@ const moveToCurrentTag = async () => {
|
||||
if (v.fullPath !== unref(currentRoute).fullPath) {
|
||||
tagsViewStore.updateVisitedView(unref(currentRoute))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { Collapse } from '@/components/Collapse'
|
||||
import { LocaleDropdown } from '@/components/LocaleDropdown'
|
||||
import { SizeDropdown } from '@/components/SizeDropdown'
|
||||
import { UserInfo } from '@/components/UserInfo'
|
||||
import { Screenfull } from '@/components/Screenfull'
|
||||
import { Breadcrumb } from '@/components/Breadcrumb'
|
||||
import { Collapse } from '@/layout/components/Collapse'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { SizeDropdown } from '@/layout/components/SizeDropdown'
|
||||
import { UserInfo } from '@/layout/components/UserInfo'
|
||||
import { Screenfull } from '@/layout/components/Screenfull'
|
||||
import { Breadcrumb } from '@/layout/components/Breadcrumb'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import avatarImg from '@/assets/imgs/avatar.gif'
|
||||
@ -22,7 +22,7 @@ const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('user-info')
|
||||
|
||||
const user = wsCache.get('user')
|
||||
const user = wsCache.get(CACHE_KEY.USER)
|
||||
|
||||
const avatar = user.user.avatar ? user.user.avatar : avatarImg
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { computed } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { Menu } from '@/components/Menu'
|
||||
import { TabMenu } from '@/components/TabMenu'
|
||||
import { TagsView } from '@/components/TagsView'
|
||||
import { Logo } from '@/components/Logo'
|
||||
import { Menu } from '@/layout/components/Menu'
|
||||
import { TabMenu } from '@/layout/components/TabMenu'
|
||||
import { TagsView } from '@/layout/components/TagsView'
|
||||
import { Logo } from '@/layout/components/Logo'
|
||||
import AppView from './AppView.vue'
|
||||
import ToolHeader from './ToolHeader.vue'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
|
@ -1,18 +1,18 @@
|
||||
import type { App } from 'vue'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import remainingRouter from './modules/remaining'
|
||||
import { isRelogin } from '@/config/axios/service'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { useTitle } from '@/hooks/web/useTitle'
|
||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
|
||||
import { isRelogin } from '@/config/axios/service'
|
||||
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||
import { getInfoApi } from '@/api/login'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
|
||||
|
||||
const { wsCache } = useCache('sessionStorage')
|
||||
|
||||
@ -50,12 +50,12 @@ router.beforeEach(async (to, from, next) => {
|
||||
const dictStore = useDictStoreWithOut()
|
||||
const userStore = useUserStoreWithOut()
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
const dictMap = wsCache.get('dictCache')
|
||||
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
|
||||
if (!dictMap) {
|
||||
const res = await listSimpleDictDataApi()
|
||||
dictStore.setDictMap(res)
|
||||
}
|
||||
if (userStore.getRoles.length === 0) {
|
||||
if (!userStore.getIsSetUser) {
|
||||
isRelogin.show = true
|
||||
const res = await getInfoApi()
|
||||
await userStore.setUserInfoAction(res)
|
||||
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { setCssVar, humpToUnderline } from '@/utils'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { ElementPlusSize } from '@/types/elementPlus'
|
||||
import { LayoutType } from '@/types/layout'
|
||||
import { ThemeTypes } from '@/types/theme'
|
||||
@ -61,10 +61,10 @@ export const useAppStore = defineStore('app', {
|
||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||
fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单
|
||||
|
||||
layout: wsCache.get('layout') || 'classic', // layout布局
|
||||
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
|
||||
layout: wsCache.get(CACHE_KEY.LAYOUT) || 'classic', // layout布局
|
||||
isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式
|
||||
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
||||
theme: wsCache.get('theme') || {
|
||||
theme: wsCache.get(CACHE_KEY.THEME) || {
|
||||
// 主题色
|
||||
elColorPrimary: '#409eff',
|
||||
// 左侧菜单边框颜色
|
||||
@ -223,7 +223,7 @@ export const useAppStore = defineStore('app', {
|
||||
return
|
||||
}
|
||||
this.layout = layout
|
||||
wsCache.set('layout', this.layout)
|
||||
wsCache.set(CACHE_KEY.LAYOUT, this.layout)
|
||||
},
|
||||
setTitle(title: string) {
|
||||
this.title = title
|
||||
@ -237,7 +237,7 @@ export const useAppStore = defineStore('app', {
|
||||
document.documentElement.classList.add('light')
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
wsCache.set('isDark', this.isDark)
|
||||
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
|
||||
},
|
||||
setCurrentSize(currentSize: ElementPlusSize) {
|
||||
this.currentSize = currentSize
|
||||
@ -248,7 +248,7 @@ export const useAppStore = defineStore('app', {
|
||||
},
|
||||
setTheme(theme: ThemeTypes) {
|
||||
this.theme = Object.assign(this.theme, theme)
|
||||
wsCache.set('theme', this.theme)
|
||||
wsCache.set(CACHE_KEY.THEME, this.theme)
|
||||
},
|
||||
setCssVarTheme() {
|
||||
for (const key in this.theme) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { DictDataVO } from '@/api/system/dict/types'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
const { wsCache } = useCache('sessionStorage')
|
||||
|
||||
export interface DictValueType {
|
||||
@ -24,7 +24,7 @@ export const useDictStore = defineStore('dict', {
|
||||
}),
|
||||
getters: {
|
||||
getDictMap(): Recordable {
|
||||
const dictMap = wsCache.get('dictCache')
|
||||
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
|
||||
return dictMap ? dictMap : this.dictMap
|
||||
},
|
||||
getHasDictData(): boolean {
|
||||
@ -54,7 +54,7 @@ export const useDictStore = defineStore('dict', {
|
||||
})
|
||||
})
|
||||
this.dictMap = dictDataMap
|
||||
wsCache.set('dictCache', dictDataMap, { exp: 60 }) // 60 秒 过期
|
||||
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import en from 'element-plus/es/locale/lang/en'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { LocaleDropdownType } from '@/types/localeDropdown'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
@ -20,8 +20,8 @@ export const useLocaleStore = defineStore('locales', {
|
||||
state: (): LocaleState => {
|
||||
return {
|
||||
currentLocale: {
|
||||
lang: wsCache.get('lang') || 'zh-CN',
|
||||
elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
|
||||
lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN',
|
||||
elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN']
|
||||
},
|
||||
// 多语言
|
||||
localeMap: [
|
||||
@ -49,7 +49,7 @@ export const useLocaleStore = defineStore('locales', {
|
||||
// this.locale = Object.assign(this.locale, localeMap)
|
||||
this.currentLocale.lang = localeMap?.lang
|
||||
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
|
||||
wsCache.set('lang', localeMap?.lang)
|
||||
wsCache.set(CACHE_KEY.LANG, localeMap?.lang)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash-es'
|
||||
import remainingRouter from '@/router/modules/remaining'
|
||||
import { generateRoute, flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||
import { getAsyncRoutesApi } from '@/api/login'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
@ -35,11 +35,11 @@ export const usePermissionStore = defineStore('permission', {
|
||||
async generateRoutes(): Promise<unknown> {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
let res: AppCustomRouteRecordRaw[]
|
||||
if (wsCache.get('roleRouters')) {
|
||||
res = wsCache.get('roleRouters') as AppCustomRouteRecordRaw[]
|
||||
if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
|
||||
res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
|
||||
} else {
|
||||
res = await getAsyncRoutesApi()
|
||||
wsCache.set('roleRouters', res)
|
||||
wsCache.set(CACHE_KEY.ROLE_ROUTERS, res)
|
||||
}
|
||||
const routerMap: AppRouteRecordRaw[] = generateRoute(res as AppCustomRouteRecordRaw[])
|
||||
// 动态路由,404一定要放到最后面
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { store } from '../index'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getAccessToken, removeToken } from '@/utils/auth'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
@ -13,6 +13,7 @@ interface UserVO {
|
||||
interface UserInfoVO {
|
||||
permissions: string[]
|
||||
roles: string[]
|
||||
isSetUser: boolean
|
||||
user: UserVO
|
||||
}
|
||||
|
||||
@ -20,6 +21,7 @@ export const useUserStore = defineStore('admin-user', {
|
||||
state: (): UserInfoVO => ({
|
||||
permissions: [],
|
||||
roles: [],
|
||||
isSetUser: false,
|
||||
user: {
|
||||
id: 0,
|
||||
avatar: '',
|
||||
@ -33,6 +35,9 @@ export const useUserStore = defineStore('admin-user', {
|
||||
getRoles(): string[] {
|
||||
return this.roles
|
||||
},
|
||||
getIsSetUser(): boolean {
|
||||
return this.isSetUser
|
||||
},
|
||||
getUser(): UserVO {
|
||||
return this.user
|
||||
}
|
||||
@ -46,7 +51,8 @@ export const useUserStore = defineStore('admin-user', {
|
||||
this.permissions = userInfo.permissions
|
||||
this.roles = userInfo.roles
|
||||
this.user = userInfo.user
|
||||
wsCache.set('user', userInfo)
|
||||
this.isSetUser = true
|
||||
wsCache.set(CACHE_KEY.USER, userInfo)
|
||||
},
|
||||
loginOut() {
|
||||
removeToken()
|
||||
@ -56,6 +62,7 @@ export const useUserStore = defineStore('admin-user', {
|
||||
resetState() {
|
||||
this.permissions = []
|
||||
this.roles = []
|
||||
this.isSetUser = false
|
||||
this.user = {
|
||||
id: 0,
|
||||
avatar: '',
|
||||
|
@ -21,6 +21,8 @@ export type ComponentName =
|
||||
| 'TreeSelect'
|
||||
| 'InputPassword'
|
||||
| 'Editor'
|
||||
| 'UploadImg'
|
||||
| 'UploadFile'
|
||||
|
||||
export type ColProps = {
|
||||
span?: number
|
||||
|
@ -1,31 +1,33 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { TokenType } from '@/api/login/types'
|
||||
import { decrypt, encrypt } from '@/utils/jsencrypt'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const AccessTokenKey = 'ACCESS_TOKEN'
|
||||
const RefreshTokenKey = 'REFRESH_TOKEN'
|
||||
|
||||
// 获取token
|
||||
export const getAccessToken = () => {
|
||||
// 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
|
||||
return Cookies.get(AccessTokenKey) ? Cookies.get(AccessTokenKey) : Cookies.get('ACCESS_TOKEN')
|
||||
return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
|
||||
}
|
||||
|
||||
// 刷新token
|
||||
export const getRefreshToken = () => {
|
||||
return Cookies.get(RefreshTokenKey)
|
||||
return wsCache.get(RefreshTokenKey)
|
||||
}
|
||||
|
||||
// 设置token
|
||||
export const setToken = (token: TokenType) => {
|
||||
Cookies.set(RefreshTokenKey, token.refreshToken, token.expiresTime)
|
||||
Cookies.set(AccessTokenKey, token.accessToken)
|
||||
wsCache.set(RefreshTokenKey, token.refreshToken, { exp: token.expiresTime })
|
||||
wsCache.set(AccessTokenKey, token.accessToken)
|
||||
}
|
||||
|
||||
// 删除token
|
||||
export const removeToken = () => {
|
||||
Cookies.remove(AccessTokenKey)
|
||||
Cookies.remove(RefreshTokenKey)
|
||||
wsCache.delete(AccessTokenKey)
|
||||
wsCache.delete(RefreshTokenKey)
|
||||
}
|
||||
|
||||
/** 格式化token(jwt格式) */
|
||||
@ -39,40 +41,40 @@ const PasswordKey = 'PASSWORD'
|
||||
const RememberMeKey = 'REMEMBER_ME'
|
||||
|
||||
export const getUsername = () => {
|
||||
return Cookies.get(UsernameKey)
|
||||
return wsCache.get(UsernameKey)
|
||||
}
|
||||
|
||||
export const setUsername = (username: string) => {
|
||||
Cookies.set(UsernameKey, username)
|
||||
wsCache.set(UsernameKey, username, { exp: 30 * 24 * 60 * 60 })
|
||||
}
|
||||
|
||||
export const removeUsername = () => {
|
||||
Cookies.remove(UsernameKey)
|
||||
wsCache.delete(UsernameKey)
|
||||
}
|
||||
|
||||
export const getPassword = () => {
|
||||
const password = Cookies.get(PasswordKey)
|
||||
const password = wsCache.get(PasswordKey)
|
||||
return password ? decrypt(password) : undefined
|
||||
}
|
||||
|
||||
export const setPassword = (password: string) => {
|
||||
Cookies.set(PasswordKey, encrypt(password))
|
||||
wsCache.set(PasswordKey, encrypt(password), { exp: 30 * 24 * 60 * 60 })
|
||||
}
|
||||
|
||||
export const removePassword = () => {
|
||||
Cookies.remove(PasswordKey)
|
||||
wsCache.delete(PasswordKey)
|
||||
}
|
||||
|
||||
export const getRememberMe = () => {
|
||||
return Cookies.get(RememberMeKey) === 'true'
|
||||
return wsCache.get(RememberMeKey) === true
|
||||
}
|
||||
|
||||
export const setRememberMe = (rememberMe: string) => {
|
||||
Cookies.set(RememberMeKey, rememberMe)
|
||||
export const setRememberMe = (rememberMe: boolean) => {
|
||||
wsCache.set(RememberMeKey, rememberMe, { exp: 30 * 24 * 60 * 60 })
|
||||
}
|
||||
|
||||
export const removeRememberMe = () => {
|
||||
Cookies.remove(RememberMeKey)
|
||||
wsCache.delete(RememberMeKey)
|
||||
}
|
||||
|
||||
// ========== 租户相关 ==========
|
||||
@ -81,25 +83,25 @@ const TenantIdKey = 'TENANT_ID'
|
||||
const TenantNameKey = 'TENANT_NAME'
|
||||
|
||||
export const getTenantName = () => {
|
||||
return Cookies.get(TenantNameKey)
|
||||
return wsCache.get(TenantNameKey)
|
||||
}
|
||||
|
||||
export const setTenantName = (username: string) => {
|
||||
Cookies.set(TenantNameKey, username)
|
||||
wsCache.set(TenantNameKey, username, { exp: 30 * 24 * 60 * 60 })
|
||||
}
|
||||
|
||||
export const removeTenantName = () => {
|
||||
Cookies.remove(TenantNameKey)
|
||||
wsCache.delete(TenantNameKey)
|
||||
}
|
||||
|
||||
export const getTenantId = () => {
|
||||
return Cookies.get(TenantIdKey)
|
||||
return wsCache.get(TenantIdKey)
|
||||
}
|
||||
|
||||
export const setTenantId = (username: string) => {
|
||||
Cookies.set(TenantIdKey, username)
|
||||
wsCache.set(TenantIdKey, username)
|
||||
}
|
||||
|
||||
export const removeTenantId = () => {
|
||||
Cookies.remove(TenantIdKey)
|
||||
wsCache.delete(TenantIdKey)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const getParentLayout = () => {
|
||||
}
|
||||
|
||||
// 按照路由中meta下的rank等级升序来排序路由
|
||||
export function ascending(arr: any[]) {
|
||||
export const ascending = (arr: any[]) => {
|
||||
arr.forEach((v) => {
|
||||
if (v?.meta?.rank === null) v.meta.rank = undefined
|
||||
if (v?.meta?.rank === 0) {
|
||||
@ -109,7 +109,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
|
||||
data.children = generateRoute(route.children)
|
||||
}
|
||||
}
|
||||
res.push(data)
|
||||
res.push(data as AppRouteRecordRaw)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@ -203,7 +203,7 @@ const addToChildren = (
|
||||
}
|
||||
}
|
||||
}
|
||||
function toCamelCase(str: string, upperCaseFirst: boolean) {
|
||||
const toCamelCase = (str: string, upperCaseFirst: boolean) => {
|
||||
str = (str || '').toLowerCase().replace(/-(.)/g, function (group1: string) {
|
||||
return group1.toUpperCase()
|
||||
})
|
||||
|
@ -8,6 +8,11 @@ const DEFAULT_CONFIG: TreeHelperConfig = {
|
||||
children: 'children',
|
||||
pid: 'pid'
|
||||
}
|
||||
export const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id'
|
||||
}
|
||||
|
||||
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
|
||||
|
||||
@ -214,6 +219,10 @@ export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
|
||||
* @param {*} children 孩子节点字段 默认 'children'
|
||||
*/
|
||||
export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {
|
||||
if (!Array.isArray(data)) {
|
||||
console.warn('data must be an array')
|
||||
return []
|
||||
}
|
||||
const config = {
|
||||
id: id || 'id',
|
||||
parentId: parentId || 'parentId',
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Error type="403" @error-click="errorClick" />
|
||||
<Error type="403" @error-click="errorClick()" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Error @error-click="errorClick" />
|
||||
<Error @error-click="errorClick()" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Error type="500" @error-click="errorClick" />
|
||||
<Error type="500" @error-click="errorClick()" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
|
@ -167,20 +167,21 @@ import { EChartsOption } from 'echarts'
|
||||
import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
|
||||
import { formatTime } from '@/utils'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useWatermark } from '@/hooks/web/useWatermark'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import avatarImg from '@/assets/imgs/avatar.gif'
|
||||
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
||||
import { pieOptions, barOptions } from './echarts-data'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wsCache } = useCache()
|
||||
const userStore = useUserStore()
|
||||
const { setWatermark } = useWatermark()
|
||||
const loading = ref(true)
|
||||
const avatar = wsCache.get('user').user.avatar
|
||||
const username = wsCache.get('user').user.nickname
|
||||
const avatar = userStore.getUser.avatar ? userStore.getUser.avatar : avatarImg
|
||||
const username = userStore.getUser.nickname
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
// 获取统计数
|
||||
let totalSate = reactive<WorkplaceTotal>({
|
||||
|
@ -63,8 +63,8 @@ import { underlineToHump } from '@/utils'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/components/LocaleDropdown'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
@ -16,7 +16,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item prop="tenantName">
|
||||
<el-form-item prop="tenantName" v-if="loginData.tenantEnable === 'true'">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
@ -65,9 +65,13 @@
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" type="primary" class="w-[100%]" @click="getCode()">
|
||||
{{ t('login.login') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
:title="t('login.login')"
|
||||
@click="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
@ -81,19 +85,25 @@
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%" :gutter="5">
|
||||
<el-col :span="8">
|
||||
<el-button class="w-[100%]" @click="setLoginState(LoginStateEnum.MOBILE)">
|
||||
{{ t('login.btnMobile') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
class="w-[100%]"
|
||||
:title="t('login.btnMobile')"
|
||||
@click="setLoginState(LoginStateEnum.MOBILE)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button class="w-[100%]" @click="setLoginState(LoginStateEnum.QR_CODE)">
|
||||
{{ t('login.btnQRCode') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
class="w-[100%]"
|
||||
:title="t('login.btnQRCode')"
|
||||
@click="setLoginState(LoginStateEnum.QR_CODE)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button class="w-[100%]" @click="setLoginState(LoginStateEnum.REGISTER)">
|
||||
{{ t('login.btnRegister') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
class="w-[100%]"
|
||||
:title="t('login.btnRegister')"
|
||||
@click="setLoginState(LoginStateEnum.REGISTER)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
@ -103,32 +113,13 @@
|
||||
<el-form-item>
|
||||
<div class="flex justify-between w-[100%]">
|
||||
<Icon
|
||||
icon="ant-design:github-filled"
|
||||
v-for="(item, key) in socialList"
|
||||
:key="key"
|
||||
:icon="item.icon"
|
||||
:size="30"
|
||||
class="cursor-pointer anticon"
|
||||
color="#999"
|
||||
@click="doSocialLogin('github')"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:wechat-filled"
|
||||
:size="30"
|
||||
class="cursor-pointer anticon"
|
||||
color="#999"
|
||||
@click="doSocialLogin('wechat')"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:alipay-circle-filled"
|
||||
:size="30"
|
||||
color="#999"
|
||||
class="cursor-pointer anticon"
|
||||
@click="doSocialLogin('alipay')"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:dingtalk-circle-filled"
|
||||
:size="30"
|
||||
color="#999"
|
||||
class="cursor-pointer anticon"
|
||||
@click="doSocialLogin('dingtalk')"
|
||||
@click="doSocialLogin(item.type)"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@ -150,21 +141,24 @@ import {
|
||||
ElDivider,
|
||||
ElLoading
|
||||
} from 'element-plus'
|
||||
import Cookies from 'js-cookie'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { setToken, setTenantId } from '@/utils/auth'
|
||||
import { decrypt, encrypt } from '@/utils/jsencrypt'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { decrypt } from '@/utils/jsencrypt'
|
||||
import { Verify } from '@/components/Verifition'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import * as LoginApi from '@/api/login'
|
||||
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const formLogin = ref()
|
||||
const { validForm } = useFormValid(formLogin)
|
||||
const { setLoginState, getLoginState } = useLoginState()
|
||||
@ -172,9 +166,6 @@ const { currentRoute, push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const redirect = ref<string>('')
|
||||
const loginLoading = ref(false)
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
|
||||
@ -194,16 +185,33 @@ const loginData = reactive({
|
||||
signIn: false
|
||||
},
|
||||
loginForm: {
|
||||
tenantName: Cookies.get('tenantName') ? Cookies.get('tenantName') : '芋道源码',
|
||||
username: Cookies.get('username') ? Cookies.get('username') : 'admin',
|
||||
password: Cookies.get('password')
|
||||
? (decrypt(Cookies.get('password')) as unknown as string)
|
||||
: 'admin123',
|
||||
tenantName: '芋道源码',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: '',
|
||||
rememberMe: false
|
||||
}
|
||||
})
|
||||
|
||||
const socialList = [
|
||||
{
|
||||
icon: 'ant-design:github-filled',
|
||||
type: 0
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:wechat-filled',
|
||||
type: 30
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:alipay-circle-filled',
|
||||
type: 0
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:dingtalk-circle-filled',
|
||||
type: 20
|
||||
}
|
||||
]
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
// 情况一,未开启:则直接登录
|
||||
@ -217,17 +225,19 @@ const getCode = async () => {
|
||||
}
|
||||
//获取租户ID
|
||||
const getTenantId = async () => {
|
||||
const res = await LoginApi.getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
setTenantId(res)
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
const res = await LoginApi.getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 记住我
|
||||
const getCookie = () => {
|
||||
const username = Cookies.get('username')
|
||||
const password = Cookies.get('password')
|
||||
? (decrypt(Cookies.get('password')) as unknown as string)
|
||||
const username = authUtil.getUsername()
|
||||
const password = authUtil.getPassword()
|
||||
? decrypt(authUtil.getPassword() as unknown as string)
|
||||
: undefined
|
||||
const rememberMe = Cookies.get('rememberMe')
|
||||
const tenantName = Cookies.get('tenantName')
|
||||
const rememberMe = authUtil.getRememberMe()
|
||||
const tenantName = authUtil.getTenantName()
|
||||
loginData.loginForm = {
|
||||
...loginData.loginForm,
|
||||
username: username ? username : loginData.loginForm.username,
|
||||
@ -256,19 +266,17 @@ const handleLogin = async (params) => {
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
if (loginData.loginForm.rememberMe) {
|
||||
Cookies.set('username', loginData.loginForm.username, { expires: 30 })
|
||||
Cookies.set('password', encrypt(loginData.loginForm.password as unknown as string), {
|
||||
expires: 30
|
||||
})
|
||||
Cookies.set('rememberMe', loginData.loginForm.rememberMe, { expires: 30 })
|
||||
Cookies.set('tenantName', loginData.loginForm.tenantName, { expires: 30 })
|
||||
authUtil.setUsername(loginData.loginForm.username)
|
||||
authUtil.setPassword(loginData.loginForm.password)
|
||||
authUtil.setRememberMe(loginData.loginForm.rememberMe)
|
||||
authUtil.setTenantName(loginData.loginForm.tenantName)
|
||||
} else {
|
||||
Cookies.remove('username')
|
||||
Cookies.remove('password')
|
||||
Cookies.remove('rememberMe')
|
||||
Cookies.remove('tenantName')
|
||||
authUtil.removeUsername()
|
||||
authUtil.removePassword()
|
||||
authUtil.removeRememberMe()
|
||||
authUtil.removeTenantName()
|
||||
}
|
||||
setToken(res)
|
||||
authUtil.setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
@ -284,14 +292,24 @@ const handleLogin = async (params) => {
|
||||
}
|
||||
|
||||
// 社交登录
|
||||
const doSocialLogin = async (type: string) => {
|
||||
loginLoading.value = true
|
||||
// 计算 redirectUri
|
||||
const redirectUri =
|
||||
location.origin + '/social-login?type=' + type + '&redirect=' + (redirect.value || '/')
|
||||
// 进行跳转
|
||||
const res = await LoginApi.socialAuthRedirectApi(type, encodeURIComponent(redirectUri))
|
||||
window.location.href = res
|
||||
const doSocialLogin = async (type: number) => {
|
||||
if (type === 0) {
|
||||
message.error('此方式未配置')
|
||||
} else {
|
||||
loginLoading.value = true
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
await message.prompt('请输入租户名称', t('common.reminder')).then(async ({ value }) => {
|
||||
const res = await LoginApi.getTenantIdByNameApi(value)
|
||||
authUtil.setTenantId(res)
|
||||
})
|
||||
}
|
||||
// 计算 redirectUri
|
||||
const redirectUri =
|
||||
location.origin + '/social-login?type=' + type + '&redirect=' + (redirect.value || '/')
|
||||
// 进行跳转
|
||||
const res = await LoginApi.socialAuthRedirectApi(type, encodeURIComponent(redirectUri))
|
||||
window.location.href = res
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
|
@ -17,7 +17,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item prop="tenantName">
|
||||
<el-form-item prop="tenantName" v-if="loginData.tenantEnable === 'true'">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
@ -69,16 +69,23 @@
|
||||
<!-- 登录按钮 / 返回按钮 -->
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" type="primary" class="w-[100%]" @click="signIn">
|
||||
{{ t('login.login') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
:title="t('login.login')"
|
||||
@click="signIn()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" class="w-[100%]" @click="handleBackLogin">
|
||||
{{ t('login.backLogin') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
class="w-[100%]"
|
||||
:title="t('login.backLogin')"
|
||||
@click="handleBackLogin()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -91,17 +98,15 @@ import { useRouter } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { setToken } from '@/utils/auth'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { setTenantId, setToken } from '@/utils/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { getTenantIdByNameApi, sendSmsCodeApi, smsLoginApi } from '@/api/login'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { useLoginState, LoginStateEnum, useFormValid } from './useLogin'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wsCache } = useCache()
|
||||
const message = useMessage()
|
||||
const permissionStore = usePermissionStore()
|
||||
const { currentRoute, push } = useRouter()
|
||||
@ -121,7 +126,7 @@ const rules = {
|
||||
}
|
||||
const loginData = reactive({
|
||||
codeImg: '',
|
||||
tenantEnable: true,
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||
token: '',
|
||||
loading: {
|
||||
signIn: false
|
||||
@ -171,8 +176,10 @@ watch(
|
||||
)
|
||||
// 获取租户 ID
|
||||
const getTenantId = async () => {
|
||||
const res = await getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
wsCache.set('tenantId', res)
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
const res = await getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
|
@ -11,9 +11,7 @@
|
||||
<el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<div class="w-[100%] mt-15px">
|
||||
<el-button class="w-[100%]" @click="handleBackLogin">
|
||||
{{ t('sys.login.backSignIn') }}
|
||||
</el-button>
|
||||
<XButton class="w-[100%]" :title="t('login.backLogin')" @click="handleBackLogin()" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -21,11 +19,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, unref } from 'vue'
|
||||
import { ElRow, ElCol, ElCard, ElDivider } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useLoginState, LoginStateEnum } from './useLogin'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { Qrcode } from '@/components/Qrcode'
|
||||
import logoImg from '@/assets/imgs/logo.png'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { useLoginState, LoginStateEnum } from './useLogin'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
|
@ -21,14 +21,16 @@
|
||||
|
||||
<template #register>
|
||||
<div class="w-[100%]">
|
||||
<el-button type="primary" class="w-[100%]" :loading="loading" @click="loginRegister">
|
||||
{{ t('login.register') }}
|
||||
</el-button>
|
||||
<XButton
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
:title="t('login.register')"
|
||||
@click="loginRegister()"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-[100%] mt-15px">
|
||||
<el-button class="w-[100%]" @click="handleBackLogin">
|
||||
{{ t('login.hasUser') }}
|
||||
</el-button>
|
||||
<XButton class="w-[100%]" :title="t('login.hasUser')" @click="handleBackLogin()" />
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
@ -40,8 +42,8 @@ import { Form } from '@/components/Form'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useLoginState, LoginStateEnum } from './useLogin'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { useLoginState, LoginStateEnum } from './useLogin'
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Form ref="formRef" :rules="rules" :schema="schema" :labelWidth="80">
|
||||
<template #sex>
|
||||
<el-radio-group v-model="sexVlue">
|
||||
<template #sex="form">
|
||||
<el-radio-group v-model="form['sex']">
|
||||
<el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
|
||||
<el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
|
||||
</el-radio-group>
|
||||
@ -67,7 +67,6 @@ const schema = reactive<FormSchema[]>([
|
||||
value: 0
|
||||
}
|
||||
])
|
||||
const sexVlue = ref<number>()
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const submit = () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
@ -75,7 +74,6 @@ const submit = () => {
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const data = unref(formRef)?.formModel as UserProfileUpdateReqVO
|
||||
data.sex = sexVlue.value as unknown as number
|
||||
await updateUserProfileApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
await init()
|
||||
@ -84,7 +82,6 @@ const submit = () => {
|
||||
}
|
||||
const init = async () => {
|
||||
const res = await getUserProfileApi()
|
||||
sexVlue.value = res.sex
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
onMounted(async () => {
|
||||
|
@ -73,12 +73,12 @@ const submitForm = async () => {
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: FormVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
detailData.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ getList()
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
:data="detailData"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user