From 9b2c9292a38db100eb0c0c3f000e2c27a206c042 Mon Sep 17 00:00:00 2001 From: 1234567 <1337097412@qq.com> Date: Mon, 16 Dec 2024 19:19:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=8E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/index.js | 32 +- src/store/modules/wxAccount.js | 89 ++-- src/store/modules/wxUserTags.js | 26 +- src/utils/httpRequest.js | 97 ++-- src/utils/index.js | 98 ++-- src/utils/validate.js | 57 +-- src/views/common/404.vue | 108 +++-- src/views/common/home.vue | 20 +- src/views/common/login.vue | 382 ++++++++------- src/views/common/theme.vue | 72 +-- src/views/modules/oss/oss-config.vue | 263 ++++++----- .../modules/oss/oss-uploader-tencent.vue | 184 ++++---- src/views/modules/oss/oss-uploader.vue | 114 +++-- src/views/modules/oss/oss.vue | 177 +++---- .../modules/sys/config-add-or-update.vue | 197 ++++---- src/views/modules/sys/config.vue | 288 +++++++----- src/views/modules/sys/log.vue | 181 ++++---- src/views/modules/sys/menu-add-or-update.vue | 437 ++++++++++-------- src/views/modules/sys/menu.vue | 235 ++++++---- src/views/modules/sys/role-add-or-update.vue | 245 +++++----- 20 files changed, 1837 insertions(+), 1465 deletions(-) diff --git a/src/store/index.js b/src/store/index.js index 1c9d94b3..ebf0d802 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,5 +1,7 @@ import Vue from 'vue' import Vuex from 'vuex' + +// 引入各个模块的 Vuex 状态管理文件 import common from './modules/common' import user from './modules/user' import article from './modules/article' @@ -7,18 +9,26 @@ import message from './modules/message' import wxUserTags from './modules/wxUserTags' import wxAccount from './modules/wxAccount' +// 注册 Vuex 插件,使其可用于 Vue 中 Vue.use(Vuex) export default new Vuex.Store({ - modules: { - common, - user, - article, - message, - wxUserTags, - wxAccount - }, - mutations: { - }, - strict: true +// 使用 modules 来组织不同的子模块 +modules: { +// 引入并注册各个模块 +common, // 公共模块,可能用于存储一些通用的状态 +user, // 用户模块,存储用户信息相关的状态 +article, // 文章模块,存储与文章相关的状态 +message, // 消息模块,存储消息相关的状态 +wxUserTags, // 微信用户标签模块,管理微信用户标签的状态 +wxAccount // 微信账号模块,管理微信账号相关的状态 +}, + +// mutations 用于同步修改状态,这里没有定义任何 mutations(可根据需求进行扩展) +mutations: { +// 这里可以添加全局的 mutation,但目前没有定义 +}, + +// 启用严格模式,开发环境下会对状态的修改进行检查,确保只能通过 mutation 修改状态 +strict: true }) diff --git a/src/store/modules/wxAccount.js b/src/store/modules/wxAccount.js index 19d1c8b5..78903d08 100644 --- a/src/store/modules/wxAccount.js +++ b/src/store/modules/wxAccount.js @@ -1,32 +1,59 @@ -import Vue from 'vue' +import Vue from 'vue'; + export default { - namespaced: true, - state: { - ACCOUNT_TYPES:{ - 1:'订阅号', - 2:'服务号' - }, - accountList:[], - selectedAppid:'' - }, - mutations: { - updateAccountList (state, list) { - state.accountList = list - if(!list.length)return - if(!state.selectedAppid){ - let appidCookie = Vue.cookie.get('appid') - let selectedAppid = appidCookie?appidCookie:list[0].appid - this.commit('wxAccount/selectAccount',selectedAppid) - } - }, - selectAccount (state, appid) { - Vue.cookie.set('appid',appid) - let oldAppid = state.selectedAppid - state.selectedAppid = appid - if(oldAppid){//切换账号时刷新网页 - location.reload(); - } - }, - } - } - \ No newline at end of file +// Vuex模块启用命名空间 +namespaced: true, + +// Vuex的state,用来存储应用状态 +state: { +// 账户类型,映射数字ID到账户类型名称 +ACCOUNT_TYPES: { +1: '订阅号', +2: '服务号' +}, + +// 存储账户列表 +accountList: [], + +// 当前选中的Appid,用来标识选择的账号 +selectedAppid: '' +}, + +// Vuex的mutations,用来修改state +mutations: { +// 更新账户列表 +updateAccountList (state, list) { +// 更新state中的accountList +state.accountList = list; + +// 如果列表为空,直接返回 +if (!list.length) return; + +// 如果当前没有选中的Appid,则从cookie或列表中选择一个默认Appid +if (!state.selectedAppid) { +let appidCookie = Vue.cookie.get('appid'); +// 获取cookie中的appid,如果有则使用cookie中的appid,否则使用列表中的第一个Appid +let selectedAppid = appidCookie ? appidCookie : list[0].appid; +// 通过commit调用mutation更新选中的账号 +this.commit('wxAccount/selectAccount', selectedAppid); +} +}, + +// 选择某个账号(切换Appid) +selectAccount (state, appid) { +// 更新cookie中的appid,保存选中的Appid +Vue.cookie.set('appid', appid); + +// 记录上一个选中的Appid +let oldAppid = state.selectedAppid; + +// 更新当前选中的Appid +state.selectedAppid = appid; + +// 如果选中的Appid发生变化,则刷新页面 +if (oldAppid) { +location.reload(); +} +} +} +}; diff --git a/src/store/modules/wxUserTags.js b/src/store/modules/wxUserTags.js index ce78e9a6..6cb02ef4 100644 --- a/src/store/modules/wxUserTags.js +++ b/src/store/modules/wxUserTags.js @@ -1,12 +1,20 @@ export default { - namespaced: true, - state: { - tags:[] - }, - mutations: { - updateTags (state, tags) { - state.tags = tags - } - } + // 启用 Vuex 模块的命名空间,避免命名冲突 + namespaced: true, + + // state 存储模块的状态 + state: { + // tags 用来存储标签数据的数组 + tags: [] + }, + + // mutations 用来修改 state 中的状态 + mutations: { + // 更新 tags 数组的内容 + updateTags (state, tags) { + // 将传入的 tags 更新到 state 中 + state.tags = tags; } + } + }; \ No newline at end of file diff --git a/src/utils/httpRequest.js b/src/utils/httpRequest.js index 4112705b..9a5ad605 100644 --- a/src/utils/httpRequest.js +++ b/src/utils/httpRequest.js @@ -4,74 +4,81 @@ import router from '@/router' import qs from 'qs' import merge from 'lodash/merge' import { clearLoginInfo } from '@/utils' -const baseUrl = '/wx' +const baseUrl = '/wx' // 设置请求的基础路径 + +// 创建axios实例 const http = axios.create({ - timeout: 1000 * 30, - withCredentials: true, - headers: { - 'Content-Type': 'application/json; charset=utf-8' - } +timeout: 1000 * 30, // 设置请求超时为30秒 +withCredentials: true, // 允许携带跨域请求的cookie +headers: { +'Content-Type': 'application/json; charset=utf-8' // 默认请求头为json格式 +} }) /** - * 请求拦截 - */ +* 请求拦截器 +* 在每个请求发送之前,加入token(从cookie中获取) +*/ http.interceptors.request.use(config => { - config.headers['token'] = Vue.cookie.get('token') // 请求头带上token - return config +config.headers['token'] = Vue.cookie.get('token') // 在请求头中加入token +return config // 返回请求配置 }, error => { - return Promise.reject(error) +return Promise.reject(error) // 请求出错时,返回Promise拒绝 }) /** - * 响应拦截 - */ +* 响应拦截器 +* 对响应数据进行拦截处理 +* 如果返回的状态码为401(未授权),则清除登录信息并跳转到登录页 +*/ http.interceptors.response.use(response => { - if (response.data && response.data.code === 401) { // 401, token失效 - clearLoginInfo() - router.push({ name: 'login' }) - } - return response +if (response.data && response.data.code === 401) { // 判断返回的code是否为401,代表token失效 +clearLoginInfo() // 清除登录信息 +router.push({ name: 'login' }) // 跳转到登录页面 +} +return response // 返回响应数据 }, error => { - return Promise.reject(error) +return Promise.reject(error) // 响应出错时,返回Promise拒绝 }) /** - * 请求地址处理 - * @param {*} actionName action方法名称 - */ +* 请求地址处理函数 +* @param {*} actionName 接口的名称,拼接成完整的URL +* @returns {string} 拼接后的完整URL +*/ http.adornUrl = (actionName) => { - // 非生产环境 && 开启代理, 接口前缀统一使用[/proxyApi/]前缀做代理拦截! - return baseUrl + actionName +// 在开发环境下,如果开启了代理,则请求路径会带上代理前缀 +return baseUrl + actionName // 返回完整的请求URL } /** - * get请求参数处理 - * @param {*} params 参数对象 - * @param {*} openDefultParams 是否开启默认参数? - */ +* get请求的参数处理 +* @param {*} params 请求的参数对象 +* @param {*} openDefultParams 是否开启默认参数 +* @returns {object} 处理后的参数对象 +*/ http.adornParams = (params = {}, openDefultParams = true) => { - var defaults = { - 't': new Date().getTime() - } - return openDefultParams ? merge(defaults, params) : params +const defaults = { +'t': new Date().getTime() // 添加时间戳参数,防止缓存 +} +return openDefultParams ? merge(defaults, params) : params // 合并默认参数和传入的参数 } /** - * post请求数据处理 - * @param {*} data 数据对象 - * @param {*} openDefultdata 是否开启默认数据? - * @param {*} contentType 数据格式 - * json: 'application/json; charset=utf-8' - * form: 'application/x-www-form-urlencoded; charset=utf-8' - */ +* post请求的数据处理 +* @param {*} data 请求的数据对象 +* @param {*} openDefultdata 是否开启默认数据 +* @param {*} contentType 数据格式类型('json'或'form') +* @returns {string} 处理后的数据 +*/ http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => { - var defaults = { - 't': new Date().getTime() - } - data = openDefultdata ? merge(defaults, data) : data - return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data) +const defaults = { +'t': new Date().getTime() // 添加时间戳参数,防止缓存 +} +data = openDefultdata ? merge(defaults, data) : data // 合并默认数据和传入的数据 +// 根据不同的contentType,处理数据格式 +return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data) } -export default http +export default http // 导出axios实例,供其他模块使用 diff --git a/src/utils/index.js b/src/utils/index.js index facd0660..aa52b209 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -3,56 +3,76 @@ import router from '@/router' import store from '@/store' /** - * 获取uuid - */ +* 获取UUID +* 生成一个标准的UUID(例如:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx) +* 使用随机数和指定格式的规则生成UUID +*/ export function getUUID() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { - return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16) - }) +return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { +return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16) +}) } /** - * 是否有权限 - * @param {*} key - */ +* 检查是否有某个权限 +* @param {*} key 权限的标识符 +* @returns {boolean} 如果权限列表中包含该权限返回true,否则返回false +*/ export function isAuth(key) { - return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false +// 从 sessionStorage 中获取权限列表,并转换为数组,如果没有权限列表,默认返回空数组 +return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false } /** - * 树形数据转换 - * @param {*} data - * @param {*} id - * @param {*} pid - */ +* 将平面数据转换为树形数据 +* @param {*} data 原始平面数据 +* @param {*} id 唯一标识符字段,默认为'id' +* @param {*} pid 父级标识符字段,默认为'parentId' +* @returns {Array} 转换后的树形数据 +*/ export function treeDataTranslate(data, id = 'id', pid = 'parentId') { - var res = [] - var temp = {} - for (var i = 0; i < data.length; i++) { - temp[data[i][id]] = data[i] - } - for (var k = 0; k < data.length; k++) { - if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) { - if (!temp[data[k][pid]]['children']) { - temp[data[k][pid]]['children'] = [] - } - if (!temp[data[k][pid]]['_level']) { - temp[data[k][pid]]['_level'] = 1 - } - data[k]['_level'] = temp[data[k][pid]]._level + 1 - temp[data[k][pid]]['children'].push(data[k]) - } else { - res.push(data[k]) - } - } - return res +var res = [] // 存储最终的树形结构 +var temp = {} // 临时存储每个节点,以便快速查找父节点 + +// 将数据转换为临时对象,key为节点的id,值为节点本身 +for (var i = 0; i < data.length; i++) { +temp[data[i][id]] = data[i] +} + +// 遍历数据,根据pid将节点组织成树形结构 +for (var k = 0; k < data.length; k++) { +// 如果节点的父节点存在,并且当前节点的id不等于父节点的id +if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) { +// 如果父节点没有'children'属性,则初始化为数组 +if (!temp[data[k][pid]]['children']) { +temp[data[k][pid]]['children'] = [] +} +// 如果父节点没有'_level'属性,则设置为1 +if (!temp[data[k][pid]]['_level']) { +temp[data[k][pid]]['_level'] = 1 +} +// 当前节点的级别为父节点的级别+1 +data[k]['_level'] = temp[data[k][pid]]._level + 1 +// 将当前节点推送到父节点的children数组中 +temp[data[k][pid]]['children'].push(data[k]) +} else { +// 如果当前节点是根节点,直接推送到结果数组中 +res.push(data[k]) +} +} + +return res // 返回转换后的树形数据 } /** - * 清除登录信息 - */ +* 清除登录信息 +* 用于用户退出时,清理本地存储的登录信息 +*/ export function clearLoginInfo() { - Vue.cookie.delete('token') - //store.commit('resetStore') - router.options.isAddDynamicMenuRoutes = false +// 删除cookie中的'token' +Vue.cookie.delete('token') +// 目前注释掉了重置store的操作,若需要可以解除注释 +// store.commit('resetStore') +// 重置动态菜单路由标志 +router.options.isAddDynamicMenuRoutes = false } diff --git a/src/utils/validate.js b/src/utils/validate.js index be7357aa..c25e3f44 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -1,31 +1,36 @@ /** - * 邮箱 - * @param {*} s - */ +* 验证邮箱格式 +* @param {*} s - 需要验证的邮箱地址 +* @returns {boolean} - 返回是否是有效的邮箱地址 +*/ export function isEmail(s) { - return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) -} - -/** - * 手机号码 - * @param {*} s - */ -export function isMobile(s) { + return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) + } + + /** + * 验证手机号码格式(中国手机号) + * @param {*} s - 需要验证的手机号 + * @returns {boolean} - 返回是否是有效的手机号码 + */ + export function isMobile(s) { return /^1[0-9]{10}$/.test(s) -} - -/** - * 电话号码 - * @param {*} s - */ -export function isPhone(s) { + } + + /** + * 验证固定电话号码格式 + * @param {*} s - 需要验证的电话号码 + * @returns {boolean} - 返回是否是有效的电话号码(包括区号和本地号码) + */ + export function isPhone(s) { return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s) -} - -/** - * URL地址 - * @param {*} s - */ -export function isURL(s) { + } + + /** + * 验证URL地址格式 + * @param {*} s - 需要验证的URL地址 + * @returns {boolean} - 返回是否是有效的URL地址(包括http或https协议) + */ + export function isURL(s) { return /^http[s]?:\/\/.*/.test(s) -} + } + \ No newline at end of file diff --git a/src/views/common/404.vue b/src/views/common/404.vue index 38589071..c96b106e 100644 --- a/src/views/common/404.vue +++ b/src/views/common/404.vue @@ -1,61 +1,81 @@ - - - - + + \ No newline at end of file diff --git a/src/views/common/home.vue b/src/views/common/home.vue index 948980de..fe655f7e 100644 --- a/src/views/common/home.vue +++ b/src/views/common/home.vue @@ -1,12 +1,18 @@ - - + } + + \ No newline at end of file diff --git a/src/views/common/login.vue b/src/views/common/login.vue index a43ddb05..1914a120 100644 --- a/src/views/common/login.vue +++ b/src/views/common/login.vue @@ -1,184 +1,220 @@ - + + +
+

管理员登录

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + - + \ No newline at end of file diff --git a/src/views/common/theme.vue b/src/views/common/theme.vue index 1aeccbcb..388b2ff6 100644 --- a/src/views/common/theme.vue +++ b/src/views/common/theme.vue @@ -1,33 +1,53 @@ - + - + \ No newline at end of file diff --git a/src/views/modules/oss/oss-config.vue b/src/views/modules/oss/oss-config.vue index 484f90d5..ae429ca5 100644 --- a/src/views/modules/oss/oss-config.vue +++ b/src/views/modules/oss/oss-config.vue @@ -1,127 +1,150 @@ - + - + \ No newline at end of file diff --git a/src/views/modules/oss/oss-uploader-tencent.vue b/src/views/modules/oss/oss-uploader-tencent.vue index d3dac232..81e3ddb2 100644 --- a/src/views/modules/oss/oss-uploader-tencent.vue +++ b/src/views/modules/oss/oss-uploader-tencent.vue @@ -1,87 +1,105 @@ - - + +
+ + + +
{{uploading ? infoText : '上传文件'}}
+
+ + + - - + }, + mounted() { + // 组件加载完毕后,发起请求获取 COS 配置数据 + this.$http({ + url: this.$http.adornUrl('/sys/oss/config'), + method: 'get', + params: this.$http.adornParams() // 传递请求参数 + }).then(({ data }) => { + if (data && data.code === 200) { + // 如果获取成功,保存配置数据 + this.cosConfig = data.config; + // 初始化腾讯云 COS 实例 + cos = new COS({ + SecretId: data.config.qcloudSecretId, + SecretKey: data.config.qcloudSecretKey, + }); + } else { + // 配置获取失败,提示错误信息 + this.$message.error('请先配置云存储相关信息!'); + } + }) + }, + methods: { + // 选择文件的方法 + selectFile() { + // 如果当前没有文件正在上传,点击触发文件选择框 + if (!this.uploading) { + this.$refs.fileInput.click(); + } + }, + // 文件选择后触发的回调方法 + onFileChange() { + let file = this.$refs.fileInput.files[0]; // 获取选中的文件 + this.uploading = true; // 设置上传状态为 true + let now = new Date(); + // 生成上传文件的存储路径(包括日期和时间戳确保路径唯一) + let path = now.toISOString().slice(0, 10) + '/' + now.getTime() + file.name.substr(file.name.lastIndexOf('.')); + + // 调用腾讯云 COS 上传文件方法 + cos.putObject({ + Bucket: this.cosConfig.qcloudBucketName, // 必须指定的桶名称 + Region: this.cosConfig.qcloudRegion, // 必须指定的区域 + Key: path, // 文件存储路径(文件名) + Body: file, // 上传的文件对象 + onProgress: (progressData) => { // 上传进度回调 + // 更新上传进度提示文本 + this.infoText = '上传中:' + (progressData.percent * 100).toFixed(2) + '%'; + } + }, (err, data) => { + // 上传完成后回调,处理成功和失败的情况 + console.log(err || data); + this.uploading = false; // 上传状态恢复为 false + if (data) { + // 上传成功,更新提示文本 + this.infoText = '上传文件'; + // 生成文件访问的 URL + let fileUrl = 'https://' + this.cosConfig.qcloudBucketName + '.cos.' + this.cosConfig.qcloudRegion + '.myqcloud.com/' + path; + this.saveUploadResult(fileUrl); // 保存上传结果 + } else { + // 上传失败,显示错误消息 + this.$message.error('文件上传失败', err); + } + }); + }, + // 保存上传结果(上传成功后调用,传入文件的访问 URL) + saveUploadResult(url) { + this.$http({ + url: this.$http.adornUrl('/sys/oss/upload'), + method: 'post', + data: { url: url } // 将上传的文件 URL 传给服务器 + }).then(({ data }) => { + // 上传成功后,触发 `uploaded` 事件,将文件 URL 传递给父组件 + this.$emit('uploaded', url); + }) + } + } + } + + + + \ No newline at end of file diff --git a/src/views/modules/oss/oss-uploader.vue b/src/views/modules/oss/oss-uploader.vue index e013aace..64bc5914 100644 --- a/src/views/modules/oss/oss-uploader.vue +++ b/src/views/modules/oss/oss-uploader.vue @@ -1,59 +1,73 @@ - - + + \ No newline at end of file diff --git a/src/views/modules/oss/oss.vue b/src/views/modules/oss/oss.vue index 35fdcbeb..4c403ce3 100644 --- a/src/views/modules/oss/oss.vue +++ b/src/views/modules/oss/oss.vue @@ -39,108 +39,79 @@ diff --git a/src/views/modules/sys/config-add-or-update.vue b/src/views/modules/sys/config-add-or-update.vue index 26b6aa1a..898bfb61 100644 --- a/src/views/modules/sys/config-add-or-update.vue +++ b/src/views/modules/sys/config-add-or-update.vue @@ -1,96 +1,117 @@ diff --git a/src/views/modules/sys/config.vue b/src/views/modules/sys/config.vue index 6a7517b5..b20162c1 100644 --- a/src/views/modules/sys/config.vue +++ b/src/views/modules/sys/config.vue @@ -1,134 +1,172 @@ diff --git a/src/views/modules/sys/log.vue b/src/views/modules/sys/log.vue index 117b2ac8..5223704c 100644 --- a/src/views/modules/sys/log.vue +++ b/src/views/modules/sys/log.vue @@ -1,90 +1,107 @@ diff --git a/src/views/modules/sys/menu-add-or-update.vue b/src/views/modules/sys/menu-add-or-update.vue index 2d49a09f..ccee5e29 100644 --- a/src/views/modules/sys/menu-add-or-update.vue +++ b/src/views/modules/sys/menu-add-or-update.vue @@ -1,218 +1,253 @@ - - - - + + \ No newline at end of file diff --git a/src/views/modules/sys/menu.vue b/src/views/modules/sys/menu.vue index ad3fb1bc..bcdbd004 100644 --- a/src/views/modules/sys/menu.vue +++ b/src/views/modules/sys/menu.vue @@ -1,109 +1,148 @@ - - + + \ No newline at end of file diff --git a/src/views/modules/sys/role-add-or-update.vue b/src/views/modules/sys/role-add-or-update.vue index 641f01c2..c02f64e5 100644 --- a/src/views/modules/sys/role-add-or-update.vue +++ b/src/views/modules/sys/role-add-or-update.vue @@ -1,111 +1,148 @@ - - + + \ No newline at end of file