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

View File

@ -1,6 +1,6 @@
<template> <template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n"> <el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" /> <router-view v-show="setLockScreen" />
<LockScreen v-if="themeConfig.isLockScreen" /> <LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" /> <Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<CloseFull v-if="!themeConfig.isLockScreen" /> <CloseFull v-if="!themeConfig.isLockScreen" />
@ -32,6 +32,15 @@ const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig(); const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig); 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(() => { const getGlobalComponentSize = computed(() => {
return other.globalComponentSize(); return other.globalComponentSize();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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