!317 优化 vue3 配置,修复部分 bug

Merge pull request !317 from xingyu/dev
This commit is contained in:
芋道源码 2022-12-07 13:40:42 +00:00 committed by Gitee
commit 7f0c011123
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
132 changed files with 2106 additions and 2810 deletions

View File

@ -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>

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}
})

View File

@ -6,5 +6,4 @@
/vite.config.ts
/src/types/env.d.ts
/docs/**/*
/plop/**/*
CHANGELOG

View 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
})
]
}

View 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 }

View 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`
}
}
]
}

View File

@ -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"
}

View File

@ -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>

View File

@ -1,3 +0,0 @@
import {{ upperFirstName }} from './src/{{ upperFirstName }}.vue'
export { {{ upperFirstName }} }

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,5 +0,0 @@
<template>
<ContentWrap title="{{ upperFirstName }}"> {{ name }} </ContentWrap>
</template>
<script setup lang="ts" name="{{ name }}">
</script>

View File

@ -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

View File

@ -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()

View File

@ -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 })
}
// 新增文件配置

View File

@ -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
})

View File

@ -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 }

View File

@ -1,3 +1,4 @@
import UploadImg from './src/UploadImg.vue'
import UploadFile from './src/UploadFile.vue'
export { UploadImg }
export { UploadImg, UploadFile }

View 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>

View File

@ -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))
}
}
//

View File

@ -191,6 +191,7 @@ export default {
transition: all 0.5s;
}
.verify-tips {
text-indent: 10px;
position: absolute;
left: 0px;
bottom: 0px;

View File

@ -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: {
//popfixed
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({
//popfixed
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>

View File

@ -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'
},
//popfixed
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'
},
//popfixed
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>

View File

@ -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)

View File

@ -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)

View File

@ -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> &nbsp; </div>' +
'<div>参考 https://doc.iocoder.cn/ 教程</div>' +
'<div> &nbsp; </div>' +
'<div>5 分钟搭建本地环境</div>'
)
})
return Promise.reject(new Error(msg))
} else if (code !== 200) {
if (msg === '无效的刷新令牌') {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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
}
})

View File

@ -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'

View File

@ -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()

View File

@ -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>

View File

@ -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'

View File

@ -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'

View File

@ -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
}
}

View File

@ -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'

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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) {

View File

@ -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 秒 过期
}
}
})

View File

@ -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)
}
}
})

View File

@ -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一定要放到最后面

View File

@ -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: '',

View File

@ -21,6 +21,8 @@ export type ComponentName =
| 'TreeSelect'
| 'InputPassword'
| 'Editor'
| 'UploadImg'
| 'UploadFile'
export type ColProps = {
span?: number

View File

@ -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)
}
/** 格式化tokenjwt格式 */
@ -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)
}

View File

@ -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()
})

View File

@ -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',

View File

@ -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'

View File

@ -1,5 +1,5 @@
<template>
<Error @error-click="errorClick" />
<Error @error-click="errorClick()" />
</template>
<script setup lang="ts">
import { Error } from '@/components/Error'

View File

@ -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'

View File

@ -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>({

View File

@ -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()

View File

@ -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,

View File

@ -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 () => {

View File

@ -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()

View File

@ -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()

View File

@ -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 () => {

View File

@ -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