Merge remote-tracking branch 'origin/lei_dev' into leng_dev

# Conflicts:
#	package.json
This commit is contained in:
lbw 2023-03-03 17:59:26 +08:00
commit b68eaadfb4
11 changed files with 720 additions and 666 deletions

48
doc/SvgIcon.md Normal file
View File

@ -0,0 +1,48 @@
## 全局组件
- 组件支持`四种`svg图标
- `四种`图标均支持 color 和 size 属性,使用示例见下文:
```mermaid
# font-awesome的图标使用 fa 作为前缀(带个空格)
<SvgIcon name="fa fa-pencil" />
# element-plus 的图标,使用 el-icon 作为前缀,图标名称请使用:首字母大写的驼峰语法
<SvgIcon name="ele-icon-Close" color="#8595F4" size="20" />
# 本地`/src/assets/icons`文件夹内的SVG图标使用 local 作为前缀,文件名作为后缀
# 文件自动加载,新增请重新编译
<SvgIcon name="local-logo" />
# 阿里iconfont的图标使用 iconfont 作为前缀(带个空格)
<SvgIcon name="iconfont icon-user" size="20" />
```
有时Icon组件会覆盖不了图标的原有颜色或大小请检查SVG文件的源代码。
## 图标添加与配置
### 本地图标
1. 将SVG图标文件放入本地`/src/assets/icons`文件夹,然后重新编译项目。
2. 系统会自动加载该目录下的所有图标文件备用,现在你可以使用`Icon`组件来显示图标了。
3. 示例:`<SvgIcon name="local-SVG的文件名" />`
4. 不建议在此文件夹放置`非常大、非常多`的文件,可能会影响系统加载速度,当确有此方面需求时,请将文件放置到其他位置,并单独导入和使用图标。
### font-awesome 图标
1. 此图标库目前 `pigx-ui` 已经默认加载。加载代码在 `/src/utils/setIconfont.ts`中的cssUrls中。
2. 你可以直接使用font-awesome 4.7.0的所有图标,这些图标可以在这里 https://fontawesome.com.cn/faicons/ 找到,当然你也可以直接使用图标选择器寻找图标。
3. 示例:<SvgIcon name="fa fa-pencil" />注意图标名称一定是以fa加一个空格开头
### element-plus 图标
1. 此图标库目前 `pigx-ui` 已经默认加载,加载代码在 `/src/main.ts` 执行的 elementIcons 方法中。
2. 你可以直接使用 `element-plus/icons-vue ^2.0.10` 的所有图标,在使用 `element-plus` 图标时,请使用`ele` 作为前缀,图标名称请使用:首字母大写的驼峰语法。
3. 示例:<SvgIcon name="ele-RefreshRight" />
### iconfont 图标
1. 目前系统未使用任何iconfont图标你可以获取iconfont图标库项目的Font class链接后设置到 `/src/utils/setIconfont.ts` 中的 `cssUrls` 中,系统会自动加载链接中的所有图标以供使用。
2. 示例:`<SvgIcon name="iconfont icon-user" />`注意图标名称一定是以iconfont加一个空格开头以图标名称结尾。

View File

@ -16,21 +16,21 @@
"@highlightjs/vue-plugin": "^2.1.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.2.1",
"axios": "^1.3.3",
"crypto-js": "^3.1.9-1",
"echarts": "^5.4.1",
"element-plus": "^2.2.26",
"element-plus": "^2.2.32",
"form-designer": "^0.0.2",
"highlight.js": "^11.7.0",
"js-cookie": "^3.0.1",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.28",
"pinia": "^2.0.32",
"qrcode": "1.5.1",
"qs": "^6.11.0",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"vue": "3.2.47",
"vue": "^3.2.47",
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
@ -38,22 +38,22 @@
"xe-utils": "^3.5.4"
},
"devDependencies": {
"@types/node": "^18.11.13",
"@types/node": "^18.14.0",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.46.0",
"@typescript-eslint/parser": "^5.46.0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/compiler-sfc": "^3.2.45",
"@vue/compiler-sfc": "^3.2.47",
"consola": "^2.15.3",
"eslint": "8.22.0",
"eslint-plugin-vue": "^9.8.0",
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"pinia-plugin-persist": "^1.0.0",
"prettier": "^2.8.4",
"sass": "^1.56.2",
"typescript": "^4.9.4",
"prettier": "2.8.4",
"sass": "^1.58.3",
"typescript": "^4.9.5",
"unplugin-auto-import": "^0.13.0",
"vite": "^4.0.0",
"vite": "^4.1.4",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-top-level-await": "^1.2.4",

View File

@ -1,6 +1,6 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" />
<router-view v-show="setLockScreen" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<CloseFull v-if="!themeConfig.isLockScreen" />
@ -32,6 +32,15 @@ const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
//
const setLockScreen = computed(() => {
//
// https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P
return themeConfig.value.isLockScreen ? themeConfig.value.lockScreenTime > 1 : themeConfig.value.lockScreenTime >= 0;
});
//
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();

View File

@ -10,7 +10,7 @@ const clearReturn = /(\r)|(\n)/g;
const clearFill = /(fill="[^>+].*?")/g;
function findSvgFile(dir: string): string[] {
const svgRes = [];
const svgRes = [] as any;
const dirents = readdirSync(dir, {
withFileTypes: true,
});

View File

@ -1,29 +1,25 @@
<template>
<el-tabs v-model="objData.repType" type="border-card" @tab-click="handleClick" style="width: 100%;">
<el-tabs v-model="objData.repType" type="border-card" @tab-click="handleClick" style="width: 100%">
<el-tab-pane name="text" label="text">
<template #label><i class="el-icon-document"></i> 文本</template>
<el-input
v-model="objData.repContent"
type="textarea"
:rows="5"
placeholder="请输入内容">
</el-input>
<el-input v-model="objData.repContent" type="textarea" :rows="5" placeholder="请输入内容"> </el-input>
</el-tab-pane>
<el-tab-pane name="image" label="image">
<template #label><i class="el-icon-picture"></i> 图片</template>
<el-row>
<div v-if="objData.repUrl" class="select-item">
<img class="material-img" :src="objData.repUrl">
<img class="material-img" :src="objData.repUrl" />
<p v-if="objData.repName" class="item-name">{{ objData.repName }}</p>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<div v-if="!objData.repUrl" style="width: 100%;">
<div v-if="!objData.repUrl" style="width: 100%">
<el-row style="text-align: center">
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial({type: 'image',accountId: props.objData.appId})">素材库选择<i class="fansel-icon--right"></i>
<el-button type="success" @click="openMaterial({ type: 'image', accountId: props.objData.appId })"
>素材库选择<i class="fansel-icon--right"></i>
</el-button>
</el-col>
<el-col :span="12" class="col-add">
@ -40,16 +36,17 @@
<div v-if="objData.repName" class="select-item">
<p class="item-name">{{ objData.repName }}</p>
<div class="item-infos">
<img :src="WxVoice" style="width: 100px" @click="loadVideo(item)">
<img :src="WxVoice" style="width: 100px" @click="loadVideo(item)" />
</div>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<div v-if="!objData.repName" style="width: 100%;">
<div v-if="!objData.repName" style="width: 100%">
<el-row style="text-align: center">
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial({type: 'voice',accountId: props.objData.appId})">素材库选择<i class="fansel-icon--right"></i>
<el-button type="success" @click="openMaterial({ type: 'voice', accountId: props.objData.appId })"
>素材库选择<i class="fansel-icon--right"></i>
</el-button>
</el-col>
<el-col :span="12" class="col-add">
@ -62,20 +59,19 @@
<el-tab-pane name="video" label="video">
<template #label><i class="el-icon-share"></i> 视频</template>
<el-row style="text-align: center;flex: 1">
<el-row style="text-align: center; flex: 1">
<el-input v-model="objData.repName" placeholder="请输入标题"></el-input>
<el-input v-model="objData.repDesc" placeholder="请输入描述"></el-input>
<div style="text-align: center;">
<a v-if="objData.repUrl" target="_blank" :href="objData.repUrl"><i
class="icon-bofang">&nbsp;播放视频</i></a>
</div>
<div style="margin: 20px 0;"></div>
<div style="text-align: center">
<el-button type="success" @click="openMaterial({type: 'video',accountId: props.objData.appId})">素材库选择<i class="fansel-icon--right"></i>
<a v-if="objData.repUrl" target="_blank" :href="objData.repUrl"><i class="icon-bofang">&nbsp;播放视频</i></a>
</div>
<div style="margin: 20px 0"></div>
<div style="text-align: center">
<el-button type="success" @click="openMaterial({ type: 'video', accountId: props.objData.appId })"
>素材库选择<i class="fansel-icon--right"></i>
</el-button>
</div>
</el-row>
</el-tab-pane>
<el-tab-pane name="news" label="news">
@ -87,10 +83,11 @@
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<div v-if="!objData.content" style="width: 100%;">
<div v-if="!objData.content" style="width: 100%">
<el-row style="text-align: center">
<el-col :span="24" class="col-select2">
<el-button type="success" @click="openMaterial({type: 'news',accountId: props.objData.appId})">素材库选择<i class="fansel-icon--right"></i>
<el-button type="success" @click="openMaterial({ type: 'news', accountId: props.objData.appId })"
>素材库选择<i class="fansel-icon--right"></i>
</el-button>
</el-col>
</el-row>
@ -100,20 +97,18 @@
</el-tabs>
<wx-material-select ref="dialogNewsRef" @selectMaterial="selectMaterial"></wx-material-select>
</template>
<script setup lang="ts" name="wx-reply">
import {getMaterialVideo} from "/@/api/mp/wx-material";
import {useMessage} from "/@/hooks/message";
import WxVideo from '/@/assets/icon/wx-video.svg'
import WxVoice from '/@/assets/icon/wx-voice.svg'
const WxMaterialSelect = defineAsyncComponent(() => import("/@/components/wechart/wx-material-select/main.vue"))
import { getMaterialVideo } from '/@/api/mp/wx-material';
import { useMessage } from '/@/hooks/message';
import WxVideo from '/@/assets/icon/wx-video.svg';
import WxVoice from '/@/assets/icon/wx-voice.svg';
const WxMaterialSelect = defineAsyncComponent(() => import('/@/components/wechart/wx-material-select/main.vue'));
const WxFileUpload = defineAsyncComponent(() => import("/@/components/wechart/fileUpload/index.vue"))
const WxNews = defineAsyncComponent(() => import("/@/components/wechart/wx-news/index.vue"))
const WxFileUpload = defineAsyncComponent(() => import('/@/components/wechart/fileUpload/index.vue'));
const WxNews = defineAsyncComponent(() => import('/@/components/wechart/wx-news/index.vue'));
const props = defineProps({
objData: {
@ -124,122 +119,118 @@ const props = defineProps({
repContent: '',
repName: '',
repDesc: '',
repUrl: ''
}
}
}
})
repUrl: '',
};
},
},
});
const uploadData = reactive({
mediaType: props.objData.repType,
title: '',
introduction: '',
appId: props.objData.appId
})
appId: props.objData.appId,
});
const tempObj = ref() as any
const tempObj = ref() as any;
const handleClick = (tab) => {
uploadData.mediaType = tab.paneName
uploadData.appId = props.objData.appId
uploadData.mediaType = tab.paneName;
uploadData.appId = props.objData.appId;
const tempObjItem = tempObj.value[props.objData.repType]
const tempObjItem = tempObj.value[props.objData.repType];
if (tempObjItem) {
props.objData.repName = tempObjItem.repName ? tempObjItem.repName : null
props.objData.repMediaId = tempObjItem.repMediaId ? tempObjItem.repMediaId : null
props.objData.media_id = tempObjItem.media_id ? tempObjItem.media_id : null
props.objData.repUrl = tempObjItem.repUrl ? tempObjItem.repUrl : null
props.objData.content = tempObjItem.content ? tempObjItem.content : null
props.objData.repDesc = tempObjItem.repDesc ? tempObjItem.repDesc : null
props.objData.repName = tempObjItem.repName ? tempObjItem.repName : null;
props.objData.repMediaId = tempObjItem.repMediaId ? tempObjItem.repMediaId : null;
props.objData.media_id = tempObjItem.media_id ? tempObjItem.media_id : null;
props.objData.repUrl = tempObjItem.repUrl ? tempObjItem.repUrl : null;
props.objData.content = tempObjItem.content ? tempObjItem.content : null;
props.objData.repDesc = tempObjItem.repDesc ? tempObjItem.repDesc : null;
} else {
props.objData.repName = ''
props.objData.repMediaId = ''
props.objData.media_id = ''
props.objData.repUrl = ''
props.objData.content = ''
props.objData.repDesc = ''
props.objData.repName = '';
props.objData.repMediaId = '';
props.objData.media_id = '';
props.objData.repUrl = '';
props.objData.content = '';
props.objData.repDesc = '';
}
}
};
const deleteObj = () => {
props.objData.repName = ''
props.objData.repUrl = ''
props.objData.content = ''
}
props.objData.repName = '';
props.objData.repUrl = '';
props.objData.content = '';
};
const openMaterial = (data: any) => {
dialogNewsRef.value.openDialog(data)
}
dialogNewsRef.value.openDialog(data);
};
const dialogNewsRef = ref()
const dialogNewsRef = ref();
const selectMaterial = (item, appId) => {
let tempObjItem = {
repType: '',
repMediaId: '',
media_id: '',
content: ''
} as any
tempObjItem.repType = props.objData.repType
tempObjItem.repMediaId = item.mediaId
tempObjItem.media_id = item.mediaId
tempObjItem.content = item.content
content: '',
} as any;
tempObjItem.repType = props.objData.repType;
tempObjItem.repMediaId = item.mediaId;
tempObjItem.media_id = item.mediaId;
tempObjItem.content = item.content;
props.objData.repMediaId = item.mediaId
props.objData.media_id = item.mediaId
props.objData.content = item.content
props.objData.repMediaId = item.mediaId;
props.objData.media_id = item.mediaId;
props.objData.content = item.content;
if (props.objData.repType === 'music') {
tempObjItem.repThumbMediaId = item.mediaId
tempObjItem.repThumbUrl = item.url
props.objData.repThumbMediaId = item.mediaId
props.objData.repThumbUrl = item.url
tempObjItem.repThumbMediaId = item.mediaId;
tempObjItem.repThumbUrl = item.url;
props.objData.repThumbMediaId = item.mediaId;
props.objData.repThumbUrl = item.url;
} else {
tempObjItem.repName = item.name
tempObjItem.repUrl = item.url
props.objData.repName = item.name
props.objData.repUrl = item.url
tempObjItem.repName = item.name;
tempObjItem.repUrl = item.url;
props.objData.repName = item.name;
props.objData.repUrl = item.url;
}
if (props.objData.repType === 'video') {
getMaterialVideo({
mediaId: item.mediaId,
appId: appId
}).then(response => {
const data = response.data
tempObjItem.repDesc = data.description || ''
tempObjItem.repUrl = data.downUrl
props.objData.repName = data.title
props.objData.repDesc = data.description || ''
props.objData.repUrl = data.downUrl
})
appId: appId,
}).then((response) => {
const data = response.data;
tempObjItem.repDesc = data.description || '';
tempObjItem.repUrl = data.downUrl;
props.objData.repName = data.title;
props.objData.repDesc = data.description || '';
props.objData.repUrl = data.downUrl;
});
}
tempObj.value[props.objData.repType] = tempObjItem
}
tempObj.value[props.objData.repType] = tempObjItem;
};
const handelUpload = (response) => {
if (response.code === 0) {
const item = response.data
selectMaterial(item,props.objData.appId)
const item = response.data;
selectMaterial(item, props.objData.appId);
} else {
useMessage().error("上传错误" + response.msg)
useMessage().error('上传错误' + response.msg);
}
}
};
const loadVideo = (item) => {
getMaterialVideo({
mediaId: item.repMediaId,
appId: item.appId
}).then(response => {
const data = response.data
window.open(data.downUrl,'target','');
})
}
appId: item.appId,
}).then((response) => {
const data = response.data;
window.open(data.downUrl, 'target', '');
});
};
</script>
<style scoped lang="scss">
.select-item {
width: 280px;
padding: 10px;
@ -288,7 +279,7 @@ const loadVideo = (item) => {
padding: 50px 0px;
height: 160px;
width: 49.5%;
float: right
float: right;
}
.avatar-uploader-icon {
@ -312,6 +303,6 @@ const loadVideo = (item) => {
.item-infos {
width: 30%;
margin: auto
margin: auto;
}
</style>

View File

@ -2,7 +2,7 @@ import { createI18n } from 'vue-i18n';
import pinia from '/@/stores/index';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { info } from '/@/api/admin/i18n'
import { info } from '/@/api/admin/i18n';
// 定义语言国际化内容
@ -20,7 +20,7 @@ import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
// 定义变量内容
const messages = {};
const element = { en: enLocale, 'zh-cn': zhcnLocale};
const element = { en: enLocale, 'zh-cn': zhcnLocale };
const itemize = { en: [] as any[], 'zh-cn': [] as any[] };
const modules: Record<string, any> = import.meta.glob('./**/*.ts', { eager: true });
const pages: Record<string, any> = import.meta.glob('./../../**/**/**/i18n/*.ts', { eager: true });
@ -46,12 +46,14 @@ function mergeArrObj<T>(list: T, key: string) {
return obj;
}
// 远程获取i18n
const infoI18n = await info()
itemize["zh-cn"].push(...infoI18n.data['zh-cn'])
itemize.en.push(...infoI18n.data.en)
try {
const infoI18n = await info();
itemize['zh-cn'].push(...infoI18n.data['zh-cn']);
itemize.en.push(...infoI18n.data.en);
} catch (e) {
// 考虑请求不过去没有后台的情况下导致的i18n失效
}
for (const key in itemize) {
messages[key] = {
@ -61,7 +63,6 @@ for (const key in itemize) {
};
}
// 读取 pinia 默认语言
const stores = useThemeConfig(pinia);
const { themeConfig } = storeToRefs(stores);

View File

@ -8,10 +8,10 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import { i18n } from '/@/i18n/index';
import { Local } from '/@/utils/storage';
import { verifyUrl } from '/@/utils/toolsValidate';
import request from "/@/utils/request";
import { useMessage } from "/@/hooks/message";
import request from '/@/utils/request';
import { useMessage } from '/@/hooks/message';
// @ts-ignore
import * as CryptoJS from "crypto-js";
import * as CryptoJS from 'crypto-js';
// 引入组件
const SvgIcon = defineAsyncComponent(() => import('/@/components/svgIcon/index.vue'));
@ -57,8 +57,10 @@ export function useTitle() {
export function setTagsViewNameI18n(item: any) {
let tagsViewName: string = '';
const { query, params, meta } = item;
//修复tagsViewName匹配到其他含下列单词的路由
const pattern = /^\{("(zh-cn|en|zh-tw)":"[^,]+",?){1,3}}$/;
if (query?.tagsViewName || params?.tagsViewName) {
if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/zh-cn|en|zh-tw\//.test(params?.tagsViewName)) {
if (pattern.test(query?.tagsViewName) || pattern.test(params?.tagsViewName)) {
// 国际化
const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
tagsViewName = urlTagsParams[i18n.global.locale.value];
@ -180,16 +182,10 @@ export function handleOpenLink(val: RouteItem) {
*/
export const openWindow = (url: string, title: string, w: number, h: number) => {
// Fixes dual-screen position Most browsers Firefox
const dualScreenLeft =
window.screenLeft !== undefined ? window.screenLeft : screen.left;
const dualScreenTop =
window.screenTop !== undefined ? window.screenTop : screen.top;
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
const width = window.innerWidth
? window.innerWidth
: document.documentElement.clientWidth
? document.documentElement.clientWidth
: screen.width;
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
const height = window.innerHeight
? window.innerHeight
: document.documentElement.clientHeight
@ -201,13 +197,13 @@ export const openWindow = (url: string, title: string, w: number, h: number) =>
return window.open(
url,
title,
"toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=" +
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' +
w +
", height=" +
', height=' +
h +
", top=" +
', top=' +
top +
", left=" +
', left=' +
left
);
};
@ -217,7 +213,7 @@ export const openWindow = (url: string, title: string, w: number, h: number) =>
export function encryption(params: any) {
let { data, type, param, key } = params;
const result = JSON.parse(JSON.stringify(data));
if (type === "Base64") {
if (type === 'Base64') {
param.forEach((ele: any) => {
result[ele] = btoa(result[ele]);
});
@ -230,7 +226,7 @@ export function encryption(params: any) {
var encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CFB,
padding: CryptoJS.pad.NoPadding
padding: CryptoJS.pad.NoPadding,
});
result[ele] = encrypted.toString();
});
@ -248,11 +244,11 @@ export function encryption(params: any) {
export function downBlobFile(url: any, query: any, fileName: string) {
return request({
url: url,
method: "get",
responseType: "blob",
params: query
}).then(response => {
handleBlobFile(response, fileName)
method: 'get',
responseType: 'blob',
params: query,
}).then((response) => {
handleBlobFile(response, fileName);
});
}
@ -265,10 +261,10 @@ export function handleBlobFile(response: any, fileName: string) {
// 处理返回的文件流
const blob = response;
if (blob && blob.size === 0) {
useMessage().error("内容为空,无法下载");
useMessage().error('内容为空,无法下载');
return;
}
const link = document.createElement("a");
const link = document.createElement('a');
// 兼容一下 入参不是 File Blob 类型情况
var binaryData = [] as any;
@ -325,37 +321,37 @@ const other = {
handleOpenLink(val);
},
encryption: (data: any) => {
return encryption(data)
return encryption(data);
},
downBlobFile: (url: any, query: any, fileName: string) => {
return downBlobFile(url, query, fileName)
return downBlobFile(url, query, fileName);
},
toUnderline: (str: string) => {
return toUnderline(str)
return toUnderline(str);
},
openWindow: (url: string, title: string, w: number, h: number) => {
return openWindow(url, title, w, h)
return openWindow(url, title, w, h);
},
getQueryString: (url: string, paraName: string) => {
return getQueryString(url, paraName)
}
return getQueryString(url, paraName);
},
};
export function getQueryString(url: string, paraName: string) {
const arrObj = url.split("?");
const arrObj = url.split('?');
if (arrObj.length > 1) {
const arrPara = arrObj[1].split("&");
const arrPara = arrObj[1].split('&');
let arr;
for (let i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split("=");
arr = arrPara[i].split('=');
// eslint-disable-next-line eqeqeq
if (arr != null && arr[0] == paraName) {
return arr[1];
}
}
return "";
return '';
} else {
return "";
return '';
}
}
@ -369,25 +365,31 @@ export function getQueryString(url: string, paraName: string) {
* @returns {*}
*/
export function handleTree(data, id, parentId, children, rootId) {
id = id || 'id'
parentId = parentId || 'parentId'
children = children || 'children'
rootId = rootId || Math.min.apply(Math, data.map(item => {
return item[parentId]
})) || 0
id = id || 'id';
parentId = parentId || 'parentId';
children = children || 'children';
rootId =
rootId ||
Math.min.apply(
Math,
data.map((item) => {
return item[parentId];
})
) ||
0;
//对源数据深度克隆
const cloneData = JSON.parse(JSON.stringify(data))
const cloneData = JSON.parse(JSON.stringify(data));
//循环所有项
const treeData = cloneData.filter(father => {
const branchArr = cloneData.filter(child => {
const treeData = cloneData.filter((father) => {
const branchArr = cloneData.filter((child) => {
//返回每一项的子级数组
return father[id] === child[parentId]
})
branchArr.length > 0 ? father[children] = branchArr : ''
return father[id] === child[parentId];
});
branchArr.length > 0 ? (father[children] = branchArr) : '';
//返回第一层
return father[parentId] === rootId
})
return treeData !== '' ? treeData : data
return father[parentId] === rootId;
});
return treeData !== '' ? treeData : data;
}
/**
@ -396,9 +398,8 @@ export function handleTree(data, id, parentId, children, rootId) {
* @returns 线
*/
export function toUnderline(str: string) {
return str.replace(/([A-Z])/g, "_$1").toLowerCase()
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
}
// 统一批量导出
export default other;

View File

@ -6,7 +6,7 @@ import qs from 'qs';
// 配置新建一个 axios 实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 50000,
timeout: 5000,
headers: { 'Content-Type': 'application/json' },
paramsSerializer: {
serialize(params) {
@ -16,16 +16,17 @@ const service: AxiosInstance = axios.create({
});
// 添加请求拦截器
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// get查询参数序列化
if (config.method === 'get') {
// @ts-ignore
config.paramsSerializer = (params: any) => {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
return qs.stringify(params, { arrayFormat: 'repeat' });
};
}
// 统一增加 token
const isToken = (config.headers || {}).isToken === false
const isToken = (config.headers || {}).isToken === false;
if (Session.get('token') && !isToken) {
config.headers!['Authorization'] = `Bearer ${Session.get('token')}`;
}
@ -36,7 +37,7 @@ service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
}
return config;
},
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
@ -44,29 +45,30 @@ service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
);
// 添加响应拦截器
service.interceptors.response.use((res: any) => {
service.interceptors.response.use(
(res: any) => {
if (res.data.code === 1) {
throw res.data
throw res.data;
}
return res.data;
}, error => {
const status = Number(error.response.status) || 200
},
(error) => {
console.log(error, 'rrrrr');
const status = Number(error.response.status) || 200;
if (status === 424) {
ElMessageBox.confirm('令牌状态已过期,请点击重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
type: 'warning',
}).then(() => {
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
return
})
return;
});
}
return Promise.reject(error.response.data);
})
}
);
// 导出 axios 实例
export default service;

View File

@ -8,18 +8,23 @@ import Cookies from 'js-cookie';
* @method clear
*/
export const Local = {
// 查看 v2.4.3版本更新日志
setKey(key: string) {
// @ts-ignore
return `${__NEXT_NAME__}:${key}`;
},
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
set<T>(key: string, val: T) {
window.localStorage.setItem(Local.setKey(key), JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
let json = <string>window.localStorage.getItem(key);
let json = <string>window.localStorage.getItem(Local.setKey(key));
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
window.localStorage.removeItem(Local.setKey(key));
},
// 移除全部永久缓存
clear() {

View File

@ -42,33 +42,31 @@
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="社交登录" prop="social" >
<a-row style="display: flex;justify-content: space-between;width: 300px">
<el-form-item label="社交登录" prop="social">
<el-row style="display: flex; justify-content: space-between; width: 300px">
<div @click="handleClick('wechat')" class="item">
<SvgIcon name="local-wechat" :size="20"/>
<SvgIcon name="local-wechat" :size="20" />
<p class="title">微信</p>
</div>
<div @click="handleClick('tencent')" class="item">
<SvgIcon name="local-qq" :size="20"/>
<SvgIcon name="local-qq" :size="20" />
<p class="title">QQ</p>
</div>
<div @click="handleClick('gitee')" class="item">
<SvgIcon name="local-gitee" :size="20"/>
<SvgIcon name="local-gitee" :size="20" />
<p class="title">Gitee</p>
</div>
<div @click="handleClick('osc')" class="item">
<SvgIcon name="local-oschina" :size="20"/>
<SvgIcon name="local-oschina" :size="20" />
<p class="title">开源中国</p>
</div>
</a-row>
</el-row>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item>
<el-button type="primary" @click="handleSaveUser">
更新个人信息
</el-button>
<el-button type="primary" @click="handleSaveUser"> 更新个人信息 </el-button>
</el-form-item>
</el-col>
</el-row>
@ -81,8 +79,7 @@
<el-row :gutter="20">
<el-col :span="24" class="mb20">
<el-form-item label="原密码" prop="password">
<el-input v-model="passwordFormData.password" placeholder="请输入密码" clearable
type="password"></el-input>
<el-input v-model="passwordFormData.password" placeholder="请输入密码" clearable type="password"></el-input>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
@ -97,9 +94,7 @@
</el-col>
<el-col :span="24" class="mb20">
<el-form-item>
<el-button type="primary" @click="handleChangePassword">
修改密码
</el-button>
<el-button type="primary" @click="handleChangePassword"> 修改密码 </el-button>
</el-form-item>
</el-col>
</el-row>
@ -112,19 +107,18 @@
<script setup lang="ts" name="personal">
import { useUserInfo } from '/@/stores/userInfo';
import { editInfo, password } from '/@/api/admin/user'
import { useMessage } from "/@/hooks/message";
import { rule } from "/@/utils/validate";
import { editInfo, password } from '/@/api/admin/user';
import { useMessage } from '/@/hooks/message';
import { rule } from '/@/utils/validate';
import other from '/@/utils/other';
import {Session} from "/@/utils/storage";
import { useI18n } from "vue-i18n";
import { Session } from '/@/utils/storage';
import { useI18n } from 'vue-i18n';
const { t } = useI18n()
const { t } = useI18n();
const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'));
const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'))
const visible = ref(false)
const visible = ref(false);
//
const formData = reactive({
@ -139,120 +133,122 @@ const formData = reactive({
const passwordFormData = reactive({
password: '',
newpassword1: '',
newpassword2: ''
})
newpassword2: '',
});
const formdataRef = ref()
const passwordFormdataRef = ref()
const formdataRef = ref();
const passwordFormdataRef = ref();
const ruleForm = reactive({
phone: [{ required: true, message: "手机号不能为空", trigger: "blur" }, { validator: rule.validatePhone, trigger: 'blur' }],
nickname: [{ required: true, message: "昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱不能为空", trigger: "blur" }],
name: [{ required: true, message: "姓名不能为空", trigger: "blur" }],
})
phone: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ validator: rule.validatePhone, trigger: 'blur' },
],
nickname: [{ required: true, message: '昵称不能为空', trigger: 'blur' }],
email: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
});
const validatorPassword2 = (rule: any, value: any, callback: any) => {
if(value !== passwordFormData.newpassword1){
callback(new Error(t('personal.passwordRule')))
}else{
callback()
if (value !== passwordFormData.newpassword1) {
callback(new Error(t('personal.passwordRule')));
} else {
callback();
}
}
};
const passwordRuleForm = reactive({
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
newpassword1: [{
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
newpassword1: [
{
min: 6,
max: 20,
message: "用户密码长度必须介于 6 和 20 之间",
trigger: "blur"
}],
message: '用户密码长度必须介于 6 和 20 之间',
trigger: 'blur',
},
],
newpassword2: [
{
min: 6,
max: 20,
message: "用户密码长度必须介于 6 和 20 之间",
trigger: "blur"
},{ validator: validatorPassword2, trigger: 'blur' }
]
})
message: '用户密码长度必须介于 6 和 20 之间',
trigger: 'blur',
},
{ validator: validatorPassword2, trigger: 'blur' },
],
});
//
const handleAvatarSuccess = (url: any) => {
formData.avatar = url;
}
};
const handleChangePassword = () => {
passwordFormdataRef.value.validate((valid: boolean) => {
if (!valid) {
return false
return false;
}
password(passwordFormData).then(() => {
useMessage().success("修改成功")
password(passwordFormData)
.then(() => {
useMessage().success('修改成功');
//
// /token
Session.clear();
// 使 reload resetRoute()
window.location.reload();
}).catch(err => {
useMessage().error(err.msg)
})
})
}
.catch((err) => {
useMessage().error(err.msg);
});
});
};
//
const handleSaveUser = () => {
formdataRef.value.validate((valid: boolean) => {
if (!valid) {
return false
return false;
}
editInfo(formData).then(() => {
useMessage().success("修改成功")
editInfo(formData)
.then(() => {
useMessage().success('修改成功');
// user
useUserInfo().setUserInfos()
}).catch(err => {
useMessage().error(err.msg)
useUserInfo().setUserInfos();
})
})
}
.catch((err) => {
useMessage().error(err.msg);
});
});
};
const handleClick = (thirdpart: string) => {
let appid, client_id, redirect_uri, url;
redirect_uri = encodeURIComponent(
window.location.origin + "/#/authredirect"
);
if (thirdpart === "wechat") {
appid = "wxd1678d3f83b1d83a";
redirect_uri = encodeURIComponent(window.location.origin + '/#/authredirect');
if (thirdpart === 'wechat') {
appid = 'wxd1678d3f83b1d83a';
url = `https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${redirect_uri}&state=WX-BIND&response_type=code&scope=snsapi_login#wechat_redirect`;
} else if (thirdpart === "tencent") {
client_id = "101322838";
} else if (thirdpart === 'tencent') {
client_id = '101322838';
url = `https://graph.qq.com/oauth2.0/authorize?response_type=code&state=QQ-BIND&client_id=${client_id}&redirect_uri=${redirect_uri}`;
} else if (thirdpart === "gitee") {
client_id = '0c29cfd9cb1e0037fc837521bc08c1a7483d8fd9b3e123d46beec59a5544a881'
} else if (thirdpart === 'gitee') {
client_id = '0c29cfd9cb1e0037fc837521bc08c1a7483d8fd9b3e123d46beec59a5544a881';
url = `https://gitee.com/oauth/authorize?response_type=code&state=GITEE-BIND&client_id=${client_id}&redirect_uri=${redirect_uri}`;
} else if (thirdpart === "osc") {
client_id = "neIIqlwGsjsfsA6uxNqD";
} else if (thirdpart === 'osc') {
client_id = 'neIIqlwGsjsfsA6uxNqD';
url = `https://www.oschina.net/action/oauth2/authorize?response_type=code&client_id=${client_id}&state=OSC-BIND&redirect_uri=${redirect_uri}`;
}
other.openWindow(url, thirdpart, 540, 540)
}
other.openWindow(url, thirdpart, 540, 540);
};
const open = () => {
visible.value = true
const data = useUserInfo().userInfos
Object.assign(formData, data.user)
}
visible.value = true;
const data = useUserInfo().userInfos;
Object.assign(formData, data.user);
};
//
defineExpose({
open,
});
</script>
<style scoped lang="scss">
@ -461,7 +457,7 @@ defineExpose({
height: 100%;
}
.item{
.item {
display: flex;
flex-direction: column;
justify-content: center;

View File

@ -90,6 +90,7 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
__VUE_I18N_FULL_INSTALL__: JSON.stringify(false),
__INTLIFY_PROD_DEVTOOLS__: JSON.stringify(false),
__VERSION__: JSON.stringify(process.env.npm_package_version),
__NEXT_NAME__: JSON.stringify(process.env.npm_package_name),
},
};
});