diff --git a/泛读报告.docx b/泛读报告.docx new file mode 100644 index 0000000..0e9e7ae Binary files /dev/null and b/泛读报告.docx differ diff --git a/注释(1) b/注释(1) new file mode 100644 index 0000000..1ea6a14 --- /dev/null +++ b/注释(1) @@ -0,0 +1,296 @@ +poetizepoetize-im-uisrcmain.js +// 引入Vue 3的createApp方法,用于创建Vue应用实例 +import { createApp } from 'vue'; + +// 引入应用的根组件 +import App from './App.vue'; + +// 引入Vue Router实例,用于管理前端路由 +import router from './router'; + +// 引入Vuex或Pinia实例,用于状态管理 +import store from './store'; + +// 从naive-ui库中引入多个UI组件,并使用create方法创建一个Naive UI插件实例 +import { + create, + NAvatar, + NInput, + NIcon, + NTag, + NDivider, + NButton, + NDrawer, + NCard, + NTabs, + NTabPane, + NSwitch, + NModal, + NBadge, + NPopover, + NImage, + NPopconfirm +} from 'naive-ui'; + +poetizepoetize-im-uisrcApp.vue +// 从element-plus库中引入特定组件,并引入其样式文件 +import { + ElUpload, + ElButton, + ElRadioGroup, + ElRadioButton +} from 'element-plus'; +import 'element-plus/dist/index.css'; + +// 引入自定义的HTTP请求工具、通用方法和常量 +import http from './utils/request'; +import common from './utils/common'; +import constant from './utils/constant'; + +// 引入自定义字体和样式文件 +import 'vfonts/FiraCode.css'; +import './assets/css/index.css'; +import './assets/css/color.css'; +import './assets/css/animation.css'; + +// 创建一个Naive UI插件实例,并注册所有需要的组件 +const naive = create({ + components: [ + NAvatar, NInput, NIcon, NTag, NDivider, NButton, + NDrawer, NCard, NTabs, NTabPane, NSwitch, NModal, + NBadge, NPopover, NImage, NPopconfirm + ] +}); + +// 创建Vue应用实例 +const app = createApp(App); + +// 使用Vue Router和状态管理实例 +app.use(router); +app.use(store); + +// 使用Naive UI插件 +app.use(naive); + +// 单独注册Element Plus的组件,使它们可以在应用的全局范围内使用 +app.component(ElUpload.name, ElUpload); +app.component(ElButton.name, ElButton); +app.component(ElRadioGroup.name, ElRadioGroup); +app.component(ElRadioButton.name, ElRadioButton); + +// 添加全局属性,用于HTTP请求、通用方法和常量 +app.config.globalProperties.$http = http; +app.config.globalProperties.$common = common; +app.config.globalProperties.$constant = constant; + +// 添加全局前置路由守卫 +router.beforeEach((to, from, next) => { + // 检查目标路由是否需要认证 + if (to.meta.requiresAuth) { + // 如果是根路径,则检查URL查询参数 + if (to.path === "/") { + // 如果存在defaultStoreType查询参数,则存储到localStorage + if (typeof to.query.defaultStoreType !== "undefined") { + localStorage.setItem("defaultStoreType", to.query.defaultStoreType); + } + // 如果存在userToken查询参数,则验证token + if (typeof to.query.userToken !== "undefined") { + let userToken = to.query.userToken; + const xhr = new XMLHttpRequest(); + xhr.open('post', constant.baseURL + "/user/token", false); // 同步请求,不推荐在生产环境中使用 + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.send("userToken=" + userToken); + let result = JSON.parse(xhr.responseText); + // 如果token验证通过,则加载用户信息并重定向到应用主页面 + if (!common.isEmpty(result) && result.code === 200) { + store.commit("loadCurrentUser", result.data); + localStorage.setItem("userToken", result.data.accessToken); + window.location.href = constant.imURL; + next(); + } else { + // 如果token验证失败,则重定向到登录页面 + window.location.href = constant.webBaseURL; + } + } else if (Boolean(localStorage.getItem("userToken"))) { + // 如果已存储userToken,则允许路由变化 + next(); + } else { + // 如果没有userToken,则重定向到登录页面 + window.location.href = constant.webBaseURL; + } + } else { + // 如果不是根路径,则检查是否已存储userToken + if (Boolean(localStorage.getItem("userToken"))) { + // 如果已存储userToken,则允许路由变化 + next(); + } else { + // 如果没有userToken,则重定向到登录页面 + window.location.href = constant.webBaseURL; + } + } + } else { + // 如果目标路由不需要认证,则直接允许路由变化 + next(); + } +}); + +// 将Vue应用挂载到HTML中的#app元素上 +app.mount('#app'); + +poetizepoetize-im-uisrcutilsajaxUpload.js + + + + + + +// 从当前目录下的common.js文件中导入common模块(可能包含一些工具函数或配置) +import common from './common'; + +// 定义一个函数,用于根据XMLHttpRequest对象生成一个错误对象 +function getError(action, option, xhr) { + let msg; // 定义一个变量用于存储错误信息 + + // 如果xhr对象有response属性(通常用于处理响应体为JSON的情况) + if (xhr.response) { + // 使用xhr.response.error(如果存在)或xhr.response作为错误信息 + msg = `${xhr.response.error || xhr.response}`; + } + // 如果xhr对象有responseText属性(用于处理文本响应) + else if (xhr.responseText) { + // 直接使用xhr.responseText作为错误信息 + msg = `${xhr.responseText}`; + } + // 如果以上两种情况都不满足 + else { + // 使用一个通用的错误信息,包括失败的动作和HTTP状态码 + msg = `fail to ${action} ${xhr.status}`; + } + + // 返回一个包含错误信息的Error对象 + return new Error(msg); +} + +// 定义一个函数,用于获取XMLHttpRequest对象的响应体 +function getBody(xhr) { + const text = xhr.responseText || xhr.response; // 尝试获取responseText或response属性 + + // 如果没有响应文本,则直接返回 + if (!text) { + return text; + } + + // 尝试将文本解析为JSON对象 + try { + return JSON.parse(text); + } catch { + // 如果解析失败,则返回原始的文本 + return text; + } +} + +// 导出默认函数,该函数用于发送一个XMLHttpRequest请求 +export default function (option) { + const xhr = new XMLHttpRequest(); // 创建一个新的XMLHttpRequest对象 + const action = option.action; // 从option对象中获取请求的URL + + const formData = new FormData(); // 创建一个FormData对象用于存储要发送的数据 + + // 如果option对象中有data属性,则遍历其键值对并添加到formData中 + if (option.data) { + for (const [key, value] of Object.entries(option.data)) { + // 如果值是数组,则使用...操作符将其展开为多个参数传递给append方法 + if (Array.isArray(value)) formData.append(key, ...value); + else formData.append(key, value); + } + } + + // 添加文件数据到formData中,文件名和文件对象从option中获取 + formData.append(option.filename, option.file, option.file.name); + + // 监听错误事件,并在错误发生时调用option.onError函数,传入一个Error对象 + xhr.addEventListener('error', () => { + option.onError(getError(action, option, xhr)); + }); + + // 监听加载完成事件,并根据响应状态码调用相应的回调函数 + xhr.addEventListener('load', () => { + // 如果响应状态码表示错误(不是2xx) + if (xhr.status < 200 || xhr.status >= 300) { + // 调用option.onError函数,传入一个Error对象 + return option.onError(getError(action, option, xhr)); + } + // 如果响应成功,则调用option.onSuccess函数,并传入响应体 + option.onSuccess(getBody(xhr)); + }); + + // 打开请求,指定方法(如GET、POST)、URL和是否异步 + xhr.open(option.method, action, true); + + // 如果option对象中有withCredentials属性且xhr对象支持该属性,则设置withCredentials为true + if (option.withCredentials && 'withCredentials' in xhr) { + xhr.withCredentials = true; + } + + // 设置请求头,如果option对象中有headers属性 + const headers = option.headers || {}; + if (headers instanceof Headers) { + // 如果headers是一个Headers对象,则遍历其键值对并设置到xhr中 + headers.forEach((value, key) => xhr.setRequestHeader(key, value)); + } else { + // 否则,遍历一个普通的对象并设置请求头,跳过空值 + for (const [key, value] of Object.entries(headers)) { + if (common.isEmpty(value)) continue; // 使用common模块的isEmpty函数检查值是否为空 + xhr.setRequestHeader(key, value); + } + } + + // 发送请求,并传入formData作为请求体 + xhr.send(formData); + + // 返回xhr对象,允许调用者进一步操作(如取消请求) + return xhr; +} \ No newline at end of file diff --git a/注释(2) b/注释(2) new file mode 100644 index 0000000..03fd510 --- /dev/null +++ b/注释(2) @@ -0,0 +1,200 @@ +poetizepoetize-im-uibabel.config.js + +// 这是一个使用CommonJS模块规范导出的对象,常见于Node.js环境或Babel配置文件中 +module.exports = { + // presets数组定义了一系列Babel预设(preset),这些预设是Babel插件的集合 + // 预设可以帮助简化Babel的配置,因为它们已经预定义了一组插件和它们的配置 + presets: [ + // '@vue/cli-plugin-babel/preset' 是一个特定的预设,它是由Vue CLI的Babel插件提供的 + // 这个预设会自动配置Babel以支持Vue.js项目中的现代JavaScript特性 + // 它还包括了对Vue文件(.vue文件)中单文件组件(SFCs)的支持 + '@vue/cli-plugin-babel/preset' + ] +} + + + +poetizepoetize-im-ui.eslintrc.js + +// 使用CommonJS模块规范导出配置对象 +module.exports = { + // 设置为true表示这是一个ESLint配置文件的根文件,会停止在父级目录中查找配置文件 + root: true, + + // 指定代码的运行环境,这里设置为node,表示代码将在Node.js环境中运行 + // 这有助于ESLint识别Node.js全局变量和Node.js特有的语法 + env: { + node: true + }, + + // 继承(extends)一个或多个配置文件,这里继承了两个: + // 1. 'plugin:vue/vue3-essential':这是Vue.js官方ESLint插件提供的针对Vue 3的基本规则集 + // 2. '@vue/standard':这是Vue.js官方提供的标准规则集,它基于ESLint的标准规则集并做了一些Vue特有的调整 + extends: [ + 'plugin:vue/vue3-essential', + '@vue/standard' + ], + + // 解析器选项,这里指定了使用'babel-eslint'作为解析器 + // 'babel-eslint'允许你使用Babel转译JavaScript代码的ESLint,这在你使用Babel特定功能时非常有用 + // 注意:从ESLint 6.0.0开始,官方推荐使用'@babel/eslint-parser',因为'babel-eslint'已经逐渐停止维护 + // 但如果你的项目还在使用旧版本的ESLint或Babel,这里仍然可以使用'babel-eslint' + parserOptions: { + parser: 'babel-eslint' + }, + + // 自定义规则,这里定义了两个规则: + // 1. 'no-console':控制是否允许使用console语句 + // 如果当前环境变量NODE_ENV为'production',则设置为'warn',表示在控制台给出警告但不阻止代码执行 + // 否则设置为'off',表示完全允许使用console语句 + // 2. 'no-debugger':控制是否允许使用debugger语句 + // 如果当前环境变量NODE_ENV为'production',则设置为'warn',表示在控制台给出警告但不阻止代码执行 + // 否则设置为'off',表示完全允许使用debugger语句 + rules: { + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' + } +} + + + +poetizepoetize-im-uisrcmain.js + +// 从vue包中导入createApp方法,用于创建Vue应用实例 +import { createApp } from 'vue'; + +// 导入根组件App.vue +import App from './App.vue'; + +// 导入路由配置 +import router from './router'; + +// 导入Vuex状态管理库的配置 +import store from './store'; + +// 从naive-ui包中导入一系列UI组件和create方法,用于创建naive-ui的Vue插件实例 +import { + create, + NAvatar, + NInput, + NIcon, + NTag, + NDivider, + NButton, + NDrawer, + NCard, + NTabs, + NTabPane, + NSwitch, + NModal, + NBadge, + NPopover, + NImage, + NPopconfirm +} from 'naive-ui'; + +// 从element-plus包中导入部分UI组件 +import { + ElUpload, + ElButton, + ElRadioGroup, + ElRadioButton +} from 'element-plus'; + +// 导入element-plus的样式文件 +import 'element-plus/dist/index.css'; + +// 导入其他工具模块和样式文件 +import http from './utils/request'; // 用于发送HTTP请求的封装函数 +import common from './utils/common'; // 包含一些通用函数或常量 +import constant from './utils/constant'; // 包含一些配置常量 + +import 'vfonts/FiraCode.css'; // 导入自定义字体样式 +import './assets/css/index.css'; // 导入全局样式文件 +import './assets/css/color.css'; // 导入颜色样式文件 +import './assets/css/animation.css'; // 导入动画样式文件 + +// 使用naive-ui的create方法创建一个包含所有导入组件的插件实例 +const naive = create({ + components: [ + NAvatar, NInput, NIcon, NTag, NDivider, NButton, + NDrawer, NCard, NTabs, NTabPane, NSwitch, NModal, NBadge, + NPopover, NImage, NPopconfirm + ] +}); + +// 创建Vue应用实例 +const app = createApp(App); + +// 使用路由和Vuex状态管理 +app.use(router); +app.use(store); + +// 使用naive-ui插件实例 +app.use(naive); + +// 单独注册element-plus的组件,以便在Vue应用中使用 +app.component(ElUpload.name, ElUpload); +app.component(ElButton.name, ElButton); +app.component(ElRadioGroup.name, ElRadioGroup); +app.component(ElRadioButton.name, ElRadioButton); + +// 将一些全局属性挂载到Vue应用的配置对象上,以便在组件中通过this.$http等方式访问 +app.config.globalProperties.$http = http; +app.config.globalProperties.$common = common; +app.config.globalProperties.$constant = constant; + +// 路由守卫,用于在每个路由跳转前执行一些逻辑 +router.beforeEach((to, from, next) => { + // 如果目标路由需要认证 + if (to.meta.requiresAuth) { + // 如果是首页且带有查询参数 + if (to.path === "/") { + // 保存默认商店类型到localStorage + if (typeof to.query.defaultStoreType !== "undefined") { + localStorage.setItem("defaultStoreType", to.query.defaultStoreType); + } + // 如果带有userToken查询参数 + if (typeof to.query.userToken !== "undefined") { + let userToken = to.query.userToken; + // 发送请求验证token + const xhr = new XMLHttpRequest(); + xhr.open('post', constant.baseURL + "/user/token", false); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.send("userToken=" + userToken); + let result = JSON.parse(xhr.responseText); + // 如果验证成功,则加载用户信息并跳转到应用页面 + if (!common.isEmpty(result) && result.code === 200) { + store.commit("loadCurrentUser", result.data); + localStorage.setItem("userToken", result.data.accessToken); + window.location.href = constant.imURL; + next(); + } else { + // 验证失败或没有token,则跳转到网页版登录页面 + window.location.href = constant.webBaseURL; + } + } else if (Boolean(localStorage.getItem("userToken"))) { + // 如果localStorage中有token,则直接放行 + next(); + } else { + // 如果没有token,则跳转到网页版登录页面 + window.location.href = constant.webBaseURL; + } + } else { + // 如果不是首页,但路由需要认证 + if (Boolean(localStorage.getItem("userToken"))) { + // 如果localStorage中有token,则直接放行 + next(); + } else { + // 如果没有token,则跳转到网页版登录页面 + window.location.href = constant.webBaseURL; + } + } + } else { + // 如果目标路由不需要认证,则直接放行 + next(); + } +}); + +// 挂载Vue应用实例到#app元素上 +app.mount('#app'); diff --git a/注释(3) b/注释(3) new file mode 100644 index 0000000..cf289ca --- /dev/null +++ b/注释(3) @@ -0,0 +1,258 @@ +poetizepoetize-im-uisrcApp.vue + + + + + + + + +poetizepoetize-im-uisrcutilstiows.js + +// 导入reconnecting-websocket库,这是一个会自动重连的WebSocket实现 +import ReconnectingWebSocket from 'reconnecting-websocket'; + +/** + * 创建一个WebSocket连接的构造函数 + * @param {string} ws_protocol - WebSocket协议,'wss'用于加密连接,'ws'用于非加密连接 + * @param {string} ip - WebSocket服务器的IP地址 + * @param {string|number} port - WebSocket服务器的端口号,如果为空字符串则不使用端口号 + * @param {string} paramStr - 附加在WebSocket URL后面的请求参数,例如:'name=张三&id=12' + * @param {string} binaryType - WebSocket接收二进制数据的类型,'blob'或'arraybuffer' + */ +export default function (ws_protocol, ip, port, paramStr, binaryType) { + // 将传入的参数赋值给当前对象的属性 + this.ws_protocol = ws_protocol; + this.ip = ip; + this.port = port; + this.paramStr = paramStr; + this.binaryType = binaryType; + + // 根据传入的参数构建WebSocket的URL + if (port === "") { + // 如果端口号为空字符串,则URL不包括端口号 + this.url = ws_protocol + '://' + ip + '/socket'; + } else { + // 否则,URL包括端口号 + this.url = ws_protocol + '://' + ip + ":" + port + '/socket'; + } + // 如果存在请求参数,则将它们添加到URL的末尾 + if (paramStr) { + this.url += '?' + paramStr; + } + + // 定义一个方法用于建立WebSocket连接 + this.connect = () => { + // 创建一个新的ReconnectingWebSocket实例,并传入构建的URL + let ws = new ReconnectingWebSocket(this.url); + // 将创建的WebSocket实例保存到当前对象的ws属性中 + this.ws = ws; + // 设置WebSocket接收二进制数据的类型 + ws.binaryType = this.binaryType; + + // 定义WebSocket连接打开时的回调函数 + ws.onopen = function (event) { + // 在这里可以添加获取离线消息的逻辑,但当前为空实现 + } + + // 定义WebSocket连接关闭时的回调函数 + ws.onclose = function (event) { + // 当前为空实现,可以在这里添加处理连接关闭的逻辑 + } + + // 定义WebSocket发生错误时的回调函数 + ws.onerror = function (event) { + // 当前为空实现,可以在这里添加处理错误的逻辑 + } + } + + // 定义一个方法用于通过WebSocket发送数据 + this.send = (data) => { + // 使用当前对象的ws属性(即WebSocket实例)的send方法发送数据 + this.ws.send(data); + } +} + + +poetizepoetize-im-uisrcutilsrequest.js + +// 导入axios库,用于发送HTTP请求 +import axios from "axios"; +// 导入constant模块,该模块可能包含一些全局常量,如API的基础URL +import constant from "./constant"; +// 导入qs库,用于处理URL参数序列化/反序列化 +import qs from "qs"; + +// 导入Vuex的store实例,用于全局状态管理 +import store from "../store"; + +// 设置axios的默认基础URL +axios.defaults.baseURL = constant.baseURL; + +// 添加请求拦截器 +axios.interceptors.request.use( + function (config) { + // 在请求发送之前,可以在这里添加一些配置或处理逻辑 + // 比如添加token到请求头中(但在这个例子中,token是在每个请求中单独添加的) + return config; // 返回配置对象,继续请求 + }, + function (error) { + // 请求发生错误时的处理逻辑 + // 比如显示错误提示、记录日志等 + return Promise.reject(error); // 返回Promise.reject,中断请求 + } +); + +// 添加响应拦截器 +axios.interceptors.response.use( + function (response) { + // 响应成功时的处理逻辑 + // 检查响应数据中的code,如果不等于200,则视为错误处理 + if ( + response.data !== null && + response.data.hasOwnProperty("code") && + response.data.code !== 200 + ) { + if (response.data.code === 300) { + // 特定错误码处理,比如用户未登录或token过期 + store.commit("loadCurrentUser", {}); // 清除Vuex中的用户信息 + localStorage.removeItem("userToken"); // 移除本地存储中的token + window.location.href = constant.webBaseURL + "/user"; // 重定向到登录页面 + } + return Promise.reject(new Error(response.data.message)); // 返回Promise.reject,中断请求链,并携带错误信息 + } else { + return response; // 返回响应对象,继续后续处理 + } + }, + function (error) { + // 响应发生错误时的处理逻辑 + // 比如显示错误提示、记录日志等 + return Promise.reject(error); // 返回Promise.reject,中断请求链 + } +); + +// 导出HTTP请求工具集 +export default { + // 发送POST请求的方法 + post(url, params = {}, json = true) { + let config = { + headers: { "Authorization": localStorage.getItem("userToken") } // 添加token到请求头 + }; + + return new Promise((resolve, reject) => { + axios + .post( + url, + json ? params : qs.stringify(params), // 根据json参数决定发送的数据格式 + config + ) + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + }, + + // 发送GET请求的方法 + get(url, params = {}) { + let headers = { "Authorization": localStorage.getItem("userToken") }; // 添加token到请求头 + + return new Promise((resolve, reject) => { + axios.get(url, { params: params, headers: headers }) // 发送GET请求,携带参数和请求头 + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + }, + + // 发送文件上传请求的方法 + upload(url, param, option) { + let config = { + headers: { + "Authorization": localStorage.getItem("userToken"), + "Content-Type": "multipart/form-data" // 设置内容类型为文件上传 + }, + timeout: 60000 // 设置请求超时时间 + }; + if (typeof option !== "undefined") { + // 如果提供了上传进度回调,则添加onUploadProgress监听器 + config.onUploadProgress = progressEvent => { + if (progressEvent.total > 0) { + progressEvent.percent = progressEvent.loaded / progressEvent.total * 100; // 计算上传进度百分比 + } + option.onProgress(progressEvent); // 调用传入的回调,传递进度信息 + }; + } + + return new Promise((resolve, reject) => { + axios + .post(url, param, config) // 发送POST请求,上传文件 + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + }, + + // 发送七牛云文件上传请求的方法(可能不需要token,因为七牛云上传通常通过临时凭证) + uploadQiniu(url, param) { + let config = { + headers: { "Content-Type": "multipart/form-data" }, // 设置内容类型为文件上传 + timeout: 60000 // 设置请求超时时间 + }; + + return new Promise((resolve, reject) => { + axios + .post(url, param, config) // 发送POST请求,上传文件到七牛云 + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + } +}; diff --git a/注释(4) b/注释(4) new file mode 100644 index 0000000..8428340 --- /dev/null +++ b/注释(4) @@ -0,0 +1,442 @@ +poetizepoetize-im-uisrcApp.vue + + + + + + + + +poetizepoetize-im-uisrcutilstiows.js + +// 导入reconnecting-websocket库,这是一个会自动重连的WebSocket实现 +import ReconnectingWebSocket from 'reconnecting-websocket'; + +/** + * 创建一个WebSocket连接的构造函数 + * @param {string} ws_protocol - WebSocket协议,'wss'用于加密连接,'ws'用于非加密连接 + * @param {string} ip - WebSocket服务器的IP地址 + * @param {string|number} port - WebSocket服务器的端口号,如果为空字符串则不使用端口号 + * @param {string} paramStr - 附加在WebSocket URL后面的请求参数,例如:'name=张三&id=12' + * @param {string} binaryType - WebSocket接收二进制数据的类型,'blob'或'arraybuffer' + */ +export default function (ws_protocol, ip, port, paramStr, binaryType) { + // 将传入的参数赋值给当前对象的属性 + this.ws_protocol = ws_protocol; + this.ip = ip; + this.port = port; + this.paramStr = paramStr; + this.binaryType = binaryType; + + // 根据传入的参数构建WebSocket的URL + if (port === "") { + // 如果端口号为空字符串,则URL不包括端口号 + this.url = ws_protocol + '://' + ip + '/socket'; + } else { + // 否则,URL包括端口号 + this.url = ws_protocol + '://' + ip + ":" + port + '/socket'; + } + // 如果存在请求参数,则将它们添加到URL的末尾 + if (paramStr) { + this.url += '?' + paramStr; + } + + // 定义一个方法用于建立WebSocket连接 + this.connect = () => { + // 创建一个新的ReconnectingWebSocket实例,并传入构建的URL + let ws = new ReconnectingWebSocket(this.url); + // 将创建的WebSocket实例保存到当前对象的ws属性中 + this.ws = ws; + // 设置WebSocket接收二进制数据的类型 + ws.binaryType = this.binaryType; + + // 定义WebSocket连接打开时的回调函数 + ws.onopen = function (event) { + // 在这里可以添加获取离线消息的逻辑,但当前为空实现 + } + + // 定义WebSocket连接关闭时的回调函数 + ws.onclose = function (event) { + // 当前为空实现,可以在这里添加处理连接关闭的逻辑 + } + + // 定义WebSocket发生错误时的回调函数 + ws.onerror = function (event) { + // 当前为空实现,可以在这里添加处理错误的逻辑 + } + } + + // 定义一个方法用于通过WebSocket发送数据 + this.send = (data) => { + // 使用当前对象的ws属性(即WebSocket实例)的send方法发送数据 + this.ws.send(data); + } +} + + +poetizepoetize-im-uisrcutilsrequest.js + +// 导入axios库,用于发送HTTP请求 +import axios from "axios"; +// 导入constant模块,该模块可能包含一些全局常量,如API的基础URL +import constant from "./constant"; +// 导入qs库,用于处理URL参数序列化/反序列化 +import qs from "qs"; + +// 导入Vuex的store实例,用于全局状态管理 +import store from "../store"; + +// 设置axios的默认基础URL +axios.defaults.baseURL = constant.baseURL; + +// 添加请求拦截器 +axios.interceptors.request.use( + function (config) { + // 在请求发送之前,可以在这里添加一些配置或处理逻辑 + // 比如添加token到请求头中(但在这个例子中,token是在每个请求中单独添加的) + return config; // 返回配置对象,继续请求 + }, + function (error) { + // 请求发生错误时的处理逻辑 + // 比如显示错误提示、记录日志等 + return Promise.reject(error); // 返回Promise.reject,中断请求 + } +); + +// 添加响应拦截器 +axios.interceptors.response.use( + function (response) { + // 响应成功时的处理逻辑 + // 检查响应数据中的code,如果不等于200,则视为错误处理 + if ( + response.data !== null && + response.data.hasOwnProperty("code") && + response.data.code !== 200 + ) { + if (response.data.code === 300) { + // 特定错误码处理,比如用户未登录或token过期 + store.commit("loadCurrentUser", {}); // 清除Vuex中的用户信息 + localStorage.removeItem("userToken"); // 移除本地存储中的token + window.location.href = constant.webBaseURL + "/user"; // 重定向到登录页面 + } + return Promise.reject(new Error(response.data.message)); // 返回Promise.reject,中断请求链,并携带错误信息 + } else { + return response; // 返回响应对象,继续后续处理 + } + }, + function (error) { + // 响应发生错误时的处理逻辑 + // 比如显示错误提示、记录日志等 + return Promise.reject(error); // 返回Promise.reject,中断请求链 + } +); + +// 导出HTTP请求工具集 +export default { + // 发送POST请求的方法 + post(url, params = {}, json = true) { + let config = { + headers: { "Authorization": localStorage.getItem("userToken") } // 添加token到请求头 + }; + + return new Promise((resolve, reject) => { + axios + .post( + url, + json ? params : qs.stringify(params), // 根据json参数决定发送的数据格式 + config + ) + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + }, + + // 发送GET请求的方法 + get(url, params = {}) { + let headers = { "Authorization": localStorage.getItem("userToken") }; // 添加token到请求头 + + return new Promise((resolve, reject) => { + axios.get(url, { params: params, headers: headers }) // 发送GET请求,携带参数和请求头 + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + }, + + // 发送文件上传请求的方法 + upload(url, param, option) { + let config = { + headers: { + "Authorization": localStorage.getItem("userToken"), + "Content-Type": "multipart/form-data" // 设置内容类型为文件上传 + }, + timeout: 60000 // 设置请求超时时间 + }; + if (typeof option !== "undefined") { + // 如果提供了上传进度回调,则添加onUploadProgress监听器 + config.onUploadProgress = progressEvent => { + if (progressEvent.total > 0) { + progressEvent.percent = progressEvent.loaded / progressEvent.total * 100; // 计算上传进度百分比 + } + option.onProgress(progressEvent); // 调用传入的回调,传递进度信息 + }; + } + + return new Promise((resolve, reject) => { + axios + .post(url, param, config) // 发送POST请求,上传文件 + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + }, + + // 发送七牛云文件上传请求的方法(可能不需要token,因为七牛云上传通常通过临时凭证) + uploadQiniu(url, param) { + let config = { + headers: { "Content-Type": "multipart/form-data" }, // 设置内容类型为文件上传 + timeout: 60000 // 设置请求超时时间 + }; + + return new Promise((resolve, reject) => { + axios + .post(url, param, config) // 发送POST请求,上传文件到七牛云 + .then(res => { + resolve(res.data); // 请求成功,解析并返回响应数据 + }) + .catch(err => { + reject(err); // 请求失败,返回错误信息 + }); + }); + } +}; + + +poetizepoetize-im-uisrcutilsim.js + +// 引入常量配置文件 +import constant from "./constant"; +// 引入CryptoJS库,用于加密和解密操作 +import CryptoJS from 'crypto-js'; +// 引入Vuex的store实例,用于全局状态管理 +import store from '../store'; +// 引入Element Plus库的ElMessage组件,用于显示消息提示 +import {ElMessage} from "element-plus"; + +// 导出一个包含多个工具函数的对象 +export default { + /** + * 判断当前设备是否为移动设备 + * @returns {boolean} 如果是移动设备返回true,否则返回false + */ + mobile() { + // 使用正则表达式匹配userAgent字符串,判断是否为移动设备 + let flag = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i); + // 如果匹配到且匹配结果数组长度大于0,则返回true,表示是移动设备 + return flag && flag.length > 0; + }, + + /** + * 判断一个值是否为空 + * @param {any} value - 要判断的值 + * @returns {boolean} 如果值为空返回true,否则返回false + */ + isEmpty(value) { + // 判断值是否为undefined、null、空字符串、空数组或空对象 + if (typeof value === "undefined" || value === null || + (typeof value === "string" && value.trim() === "") || + (Array.isArray(value) && value.length === 0) || + (value.constructor === Object && Object.keys(value).length === 0)) { + return true; + } else { + return false; + } + }, + + /** + * 使用AES算法加密明文 + * @param {string} plaintText - 要加密的明文 + * @returns {string} 加密后的密文,经过Base64编码并替换特定字符 + */ + encrypt(plaintText) { + // 设置加密选项,使用ECB模式和Pkcs7填充 + let options = { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }; + // 从常量配置中获取加密密钥,并转换为Utf8编码 + let key = CryptoJS.enc.Utf8.parse(constant.cryptojs_key); + // 使用AES算法加密明文,并转换为字符串,同时替换特定字符以适应某些场景 + let encryptedData = CryptoJS.AES.encrypt(plaintText, key, options); + return encryptedData.toString().replace(/\//g, "_").replace(/\+/g, "-"); + }, + + /** + * 使用AES算法解密密文 + * @param {string} encryptedBase64Str - 要解密的密文,经过Base64编码并可能包含特定替换字符 + * @returns {string} 解密后的明文 + */ + decrypt(encryptedBase64Str) { + // 将密文中的特定替换字符还原为Base64编码的字符 + let val = encryptedBase64Str.replace(/\-/g, '+').replace(/_/g, '/'); + // 设置解密选项,与加密时相同 + let options = { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }; + // 从常量配置中获取加密密钥,并转换为Utf8编码 + let key = CryptoJS.enc.Utf8.parse(constant.cryptojs_key); + // 使用AES算法解密密文,并转换为Utf8编码的字符串 + let decryptedData = CryptoJS.AES.decrypt(val, key, options); + return CryptoJS.enc.Utf8.stringify(decryptedData); + }, + + /** + * 将文本中的表情符号转换为对应的图片标签 + * @param {string} content - 包含表情符号的文本 + * @returns {string} 表情符号被替换为图片标签后的文本 + */ + faceReg(content) { + // 使用正则表达式匹配文本中的表情符号,并替换为对应的图片标签 + content = content.replace(/ +$$ +[^\[^ +$$ +]+\]/g, (word) => { + // 从常量配置的表情符号列表中查找匹配的索引 + let index = constant.emojiList.indexOf(word.replace("[", "").replace("]", "")); + if (index > -1) { + // 根据索引构造图片URL,并返回图片标签 + let url = store.state.sysConfig['webStaticResourcePrefix'] + "emoji/q" + (index + 1) + ".gif"; + return ''; + } else { + // 如果没有找到匹配的表情符号,则原样返回 + return word; + } + }); + return content; + }, + + /** + * 将文本中的图片链接转换为图片标签 + * @param {string} content - 包含图片链接的文本 + * @returns {string} 图片链接被替换为图片标签后的文本 + */ + pictureReg(content) { + // 使用正则表达式匹配文本中的图片链接,并替换为对应的图片标签 + content = content.replace(/ +$$ +[^\[^ +$$ +]+\]/g, (word) => { + // 查找图片链接中的逗号分隔符,以获取图片描述和链接 + let index = word.indexOf(","); + if (index > -1) { + let arr = word.replace("[", "").replace("]", "").split(","); + // 返回图片标签,包含描述作为title属性 + return ''; + } else { + // 如果没有找到逗号分隔符,则原样返回 + return word; + } + }); + return content; + }, + + /** + * 将日期字符串转换为时间戳 + * @param {string} dateStr - 日期字符串,格式为YYYY-MM-DD或YYYY-MM-DD HH:mm:ss + * @returns {number} 转换后的时间戳(毫秒) + */ + getDateTimeStamp(dateStr) { + // 将日期字符串中的短横线替换为斜杠,以适应Date.parse方法的格式要求 + return Date.parse(dateStr.replace(/-/gi, "/")); + }, + + /** + * 计算两个日期之间的时间差,并返回友好的时间差字符串 + * @param {string} dateStr - 要比较的日期字符串,格式为YYYY-MM-DD HH:mm:ss + * @returns {string} 友好的时间差字符串,如“3天前”、“2小时前”等 + */ + getDateDiff(dateStr) { + // 将日期字符串转换为时间戳(秒),并获取当前时间的时间戳(秒) + let publishTime = Date.parse(dateStr.replace(/-/gi, "/")) / 1000, + timeNow = Math.floor(new Date().getTime() / 1000), + d = timeNow - publishTime, // 计算时间差(秒) + // ...(省略了部分代码,包括日期格式化和时间差计算的详细逻辑) + // 根据时间差返回不同的友好时间差字符串 + if (d_days > 0 && d_days < 3) { + return d_days + '天前'; + } else if (d_days <= 0 && d_hours > 0) { + return d_hours + '小时前'; + } else if (d_hours <= 0 && d_minutes > 0) { + return d_minutes + '分钟前'; + } else if (d_seconds < 60) { + if (d_seconds <= 0) { + return '刚刚发表'; + } else { + return d_seconds + '秒前'; + } + } else if (d_days >= 3 && d_days < 30) { + return M + '-' + D + ' ' + H + ':' + m; + } else if (d_days >= 30) { + return Y + '-' + M + '-' + D + ' ' + H + ':' + m; + } + }, + + /** + * 保存资源信息到服务器 + * @param {Vue组件实例} that - 调用此方法的Vue组件实例 + * @param {string} type - 资源类型 + * @param {string} path - 资源路径 + * @param {number} size - 资源大小(字节) + * @param {string} mimeType - 资源MIME类型 + * @param {string} originalName - 资源原始名称 + * @param {string} storeType - 存储类型 + */ diff --git a/注释(5) b/注释(5) new file mode 100644 index 0000000..892a781 --- /dev/null +++ b/注释(5) @@ -0,0 +1,634 @@ +poetizepoetize-im-uisrcstoreindex.js + +// 从'vuex'包中导入createStore函数,用于创建一个新的Vuex store实例 +import { createStore } from 'vuex'; + +// 导出通过createStore函数创建的Vuex store实例 +export default createStore({ + // state对象包含了应用的状态 + // 这些状态可以在应用的任何组件中通过this.$store.state访问 + state: { + // 从localStorage中获取当前用户信息,如果不存在则默认为空对象{} + // JSON.parse用于将JSON字符串转换为JavaScript对象 + currentUser: JSON.parse(localStorage.getItem("currentUser") || '{}'), + // 从localStorage中获取系统配置信息,如果不存在则默认为空对象{} + sysConfig: JSON.parse(localStorage.getItem("sysConfig") || '{}') + }, + + // getters对象可以包含一些方法,这些方法基于state中的状态计算并返回一些值 + // 在这个例子中,getters对象是空的,意味着没有定义任何getter + getters: {}, + + // mutations对象包含了直接修改状态的方法(同步函数) + // Vuex要求更改Vuex的store中的状态的唯一方法是提交mutation + mutations: { + // loadCurrentUser方法用于加载当前用户信息 + // 它接受两个参数:state(当前Vuex store的状态对象)和user(要设置的用户信息对象) + // 这个方法会将用户信息保存到state中,并更新localStorage中的currentUser项 + loadCurrentUser(state, user) { + state.currentUser = user; + localStorage.setItem("currentUser", JSON.stringify(user)); + }, + // loadSysConfig方法用于加载系统配置信息 + // 它接受两个参数:state(当前Vuex store的状态对象)和sysConfig(要设置的系统配置信息对象) + // 这个方法会将系统配置信息保存到state中,并更新localStorage中的sysConfig项 + loadSysConfig(state, sysConfig) { + state.sysConfig = sysConfig; + localStorage.setItem("sysConfig", JSON.stringify(sysConfig)); + } + }, + + // actions对象包含了可以包含任意异步操作的函数 + // 它们通过提交mutations而不是直接更改状态来更改Vuex store中的状态 + // 在这个例子中,actions对象是空的,意味着没有定义任何action + actions: {}, + + // modules对象允许将store分割成模块(module),每个模块拥有自己的state、mutation、action、getter + // 在这个例子中,modules对象是空的,意味着没有定义任何模块 + modules: {}, + + // plugins数组允许你注册Vuex插件,插件可以扩展Vuex的功能 + // 在这个例子中,plugins数组是空的,意味着没有注册任何插件 + plugins: [] +}); + + +poetizepoetize-im-uisrcrouterindex.js + +// 从'vue-router'包中导入createRouter和createWebHistory函数 +// createRouter用于创建一个新的router实例 +// createWebHistory用于创建一个基于HTML5历史记录API的history实例 +import { createRouter, createWebHistory } from 'vue-router'; + +// 从相对路径"../utils/constant"中导入constant模块,该模块可能包含一些常量定义 +import constant from "../utils/constant"; + +// 定义一个路由配置数组,每个对象代表一个路由规则 +const routes = [ + { + // 路由的路径,当用户访问"/"时,此路由将被匹配 + path: "/", + + // 路由的meta字段,用于存储一些自定义信息,如权限要求等 + // 在这个例子中,定义了一个requiresAuth: true,表示访问此路由需要用户认证 + meta: { requiresAuth: true }, + + // 路由的组件,这里使用了一个懒加载的组件 + // () => import('../components/index') 是一个异步组件加载的语法,表示当用户访问此路由时,才加载并渲染'index'组件 + component: () => import('../components/index') + } + // 注意:这里只定义了一个路由规则,实际项目中可能会有更多 +]; + +// 创建一个新的router实例,并传入配置对象 +const router = createRouter({ + // 使用createWebHistory函数创建一个history实例,并传入constant.webHistory作为配置项 + // constant.webHistory可能是一个配置项,用于定义history的行为,如基础路径等 + // 但具体值取决于constant模块的实现,这里无法直接知道 + history: createWebHistory(constant.webHistory), + + // 传入之前定义的路由规则数组 + routes, + + // 定义一个scrollBehavior函数,用于控制路由跳转后的滚动行为 + // 当用户从一个路由跳转到另一个路由时,这个函数会被调用 + // to和from是路由对象,分别表示目标路由和来源路由 + // savedPosition是一个对象,如果页面之前被滚动过,这里会包含滚动位置的信息 + // 函数返回一个对象,指定滚动到页面的哪个位置 + // 在这个例子中,总是滚动到页面顶部(left: 0, top: 0) + scrollBehavior(to, from, savedPosition) { + return { left: 0, top: 0 }; + } +}); + +// 导出router实例,使其可以在Vue应用的创建过程中被使用 +export default router; + + +poetizepoetize-im-uisrchooksbindEmail.js + +// 导入Vuex的useStore钩子,用于访问Vuex store +import { useStore } from 'vuex'; + +// 导入Naive UI的useDialog钩子,用于显示对话框 +import { useDialog } from 'naive-ui'; + +// 导入Vue的nextTick函数,用于在下次DOM更新循环结束之后执行延迟回调 +import { nextTick } from 'vue'; + +// 导入Element Plus的ElMessage组件,用于显示消息提示 +import { ElMessage } from "element-plus"; + +// 导入Vue的reactive、getCurrentInstance、onMounted、onBeforeUnmount、watchEffect、toRefs函数 +import { reactive, getCurrentInstance, onMounted, onBeforeUnmount, watchEffect, toRefs } from 'vue'; + +// 定义一个函数,该函数返回一个对象,该对象包含响应式数据和方法 +export default function () { + // 获取当前Vue实例的上下文,并从中提取全局属性 + const globalProperties = getCurrentInstance().appContext.config.globalProperties; + const $common = globalProperties.$common; // 自定义的常用方法集合 + const $http = globalProperties.$http; // 自定义的HTTP请求方法 + const $constant = globalProperties.$constant; // 自定义的常量集合 + + // 使用Vuex的useStore钩子获取store实例 + const store = useStore(); + + // 使用Naive UI的useDialog钩子获取对话框实例 + const dialog = useDialog(); + + // 定义响应式数据对象,用于绑定邮箱相关的数据 + let bindEmailData = reactive({ + emailVisible: false, // 邮箱对话框的显示状态 + email: '', // 邮箱地址 + code: '', // 验证码 + password: '', // 密码 + codeString: "验证码" // 倒计时显示的字符串,初始为"验证码" + }); + + // 定义一个变量,用于存储验证码倒计时的setInterval返回值 + let intervalCode = null; + + // 在组件挂载时执行的方法 + onMounted(() => { + showEmail(); // 调用showEmail方法检查是否需要显示邮箱绑定对话框 + }); + + // 显示邮箱绑定对话框的逻辑(但当前代码未直接设置对话框可见性) + function showEmail() { + // 检查当前用户是否存在且未绑定邮箱,则可能需要显示对话框(但当前逻辑未实现) + if (!$common.isEmpty(store.state.currentUser) && $common.isEmpty(store.state.currentUser.email)) { + // 逻辑未实现,仅注释说明需要显示对话框 + // bindEmailData.emailVisible = true; + } + } + + // 获取验证码的逻辑 + function getCode() { + // 检查验证码倒计时是否正在进行,如果正在进行则提示稍后再试 + if (bindEmailData.codeString === "验证码") { + // 校验邮箱格式并发送验证码请求 + // ...(省略了具体的校验和请求逻辑) + + // 设置验证码倒计时 + bindEmailData.codeString = "30"; + intervalCode = setInterval(() => { + // 每秒更新一次倒计时,直到倒计时结束 + // ...(省略了具体的倒计时逻辑) + }, 1000); + } else { + // 提示稍后再试 + ElMessage({ + message: "请稍后再试!", + type: 'error' + }); + } + } + + // 提交邮箱绑定对话框的逻辑 + function submitDialog() { + // 校验邮箱、验证码和密码(省略了具体的校验逻辑) + // ... + + // 发送绑定邮箱的请求 + // ...(省略了具体的请求逻辑) + } + + // 清除邮箱绑定对话框的数据和状态 + function clearEmailDialog() { + // 重置对话框数据和状态 + // ...(省略了具体的重置逻辑) + } + + // 返回响应式数据和方法,以便在组件中使用 + return { + bindEmailData, + getCode, + submitDialog + }; +} + + +poetizepoetize-im-uisrchookschangeData.js + +// 导入所需的Vue Composition API函数、Vuex store、Naive UI对话框组件和Element Plus消息组件 +import { useStore } from 'vuex'; +import { useDialog } from 'naive-ui'; +import { nextTick } from 'vue'; // 导入nextTick但未在代码中使用 +import { ElMessage } from "element-plus"; +import { + reactive, + getCurrentInstance, + onMounted, + onBeforeUnmount, + watchEffect, + toRefs +} from 'vue'; + +// 定义一个函数,接收friendData和groupData作为参数 +export default function (friendData, groupData) { + // 获取Vue实例的全局属性 + const globalProperties = getCurrentInstance().appContext.config.globalProperties; + const $common = globalProperties.$common; // 通用方法 + const $http = globalProperties.$http; // HTTP请求方法 + const $constant = globalProperties.$constant; // 常量配置 + const store = useStore(); // 获取Vuex store实例 + const dialog = useDialog(); // 获取Naive UI对话框实例 + + // 使用reactive创建一个响应式对象,用于存储修改信息 + let changeDataData = reactive({ + changeData: '', // 修改后的数据 + changeType: null, // 修改类型 + changeModal: false, // 修改信息模态框显示状态 + avatarType: null, // 头像类型(用户或群组) + avatarPrefix: '', // 头像URL前缀 + showAvatarDialog: false // 显示头像修改对话框 + }); + + // 关闭模态框的函数,重置相关状态 + function closeModal() { + changeDataData.avatarType = null; + changeDataData.avatarPrefix = ''; + changeDataData.changeData = ''; + changeDataData.changeType = null; + } + + // 显示头像修改对话框的函数,根据类型设置前缀 + function changeAvatar(type) { + // 判断是否有权限修改头像 + if (type === 1 || (type === 2 && groupData.groups[groupData.currentGroupId].masterFlag)) { + closeModal(); // 关闭其他模态框 + changeDataData.showAvatarDialog = true; // 显示头像修改对话框 + changeDataData.avatarType = type; // 设置头像类型 + if (type === 1) { + changeDataData.avatarPrefix = 'userAvatar'; // 用户头像前缀 + } else if (type === 2) { + changeDataData.avatarPrefix = 'im/groupAvatar'; // 群组头像前缀 + } + } + } + + // 改变数据类型(修改备注、群名等)的函数 + function changeDataType(type) { + closeModal(); // 关闭其他模态框 + changeDataData.changeType = type; // 设置修改类型 + changeDataData.changeModal = true; // 显示修改信息模态框 + } + + // 提交头像修改的函数 + function submitAvatar(avatar) { + if ($common.isEmpty(avatar)) { + ElMessage({ message: "请上传头像!", type: 'warning' }); // 提示上传头像 + return; + } + // 根据头像类型执行相应的修改操作 + if (changeDataData.avatarType === 1) { + // 用户头像修改逻辑 + } else if (changeDataData.avatarType === 2) { + // 群组头像修改逻辑 + } + } + + // 提交修改的函数(备注、群名、公告、简介) + function submitChange() { + // 根据修改类型执行相应的修改操作 + if (changeDataData.changeType === 1) { + // 修改备注逻辑 + } else if (changeDataData.changeType === 2) { + // 修改群名逻辑 + } else if (changeDataData.changeType === 3) { + // 修改公告逻辑 + } else if (changeDataData.changeType === 4) { + // 修改简介逻辑 + } + } + + // 返回对象,包含状态和方法供外部使用 + return { + changeDataData, + changeAvatar, + changeDataType, + submitAvatar, + submitChange + }; +} + + +poetizepoetize-im-uisrchooksfriend.js + +// 引入Vuex的useStore钩子,用于访问Vuex store +import {useStore} from 'vuex'; + +// 引入Naive UI的useDialog钩子,用于显示对话框 +import {useDialog} from 'naive-ui'; + +// 引入Vue的nextTick函数,用于在下次DOM更新循环结束之后执行延迟回调 +import {nextTick} from 'vue'; + +// 引入Element Plus的ElMessage组件,用于显示消息提示 +import {ElMessage} from "element-plus"; + +// 引入Vue的reactive、getCurrentInstance、onMounted、onBeforeUnmount、watchEffect、toRefs函数 +import {reactive, getCurrentInstance, onMounted, onBeforeUnmount, watchEffect, toRefs} from 'vue'; + +export default function () { + // 获取当前Vue实例的上下文,并从中获取全局属性 + const globalProperties = getCurrentInstance().appContext.config.globalProperties; + const $common = globalProperties.$common; // 通用方法或属性 + const $http = globalProperties.$http; // HTTP请求方法 + const $constant = globalProperties.$constant; // 常量配置 + + // 使用Vuex的useStore钩子获取store实例 + const store = useStore(); + + // 使用Naive UI的useDialog钩子获取对话框实例 + const dialog = useDialog(); + + // 使用reactive创建一个响应式对象,用于存储好友数据 + let friendData = reactive({ + // 好友请求列表 + friendRequests: [], + // 好友列表,使用friendId作为键 + friends: {}, + // 当前操作的好友ID + currentFriendId: null + }); + + // 异步函数,用于获取当前用户的好友列表 + async function getImFriend() { + try { + const res = await $http.get($constant.baseURL + "/imChatUserFriend/getFriend", {friendStatus: 1}); + if (!$common.isEmpty(res.data)) { + res.data.forEach(friend => { + friendData.friends[friend.friendId] = friend; + }); + } + } catch (error) { + ElMessage({ + message: error.message, + type: 'error' + }); + } + } + + // 删除好友的函数 + function removeFriend(currentFriendId) { + dialog.error({ + title: '警告', + content: `你确定删除${friendData.friends[currentFriendId].remark}?`, + positiveText: '确定', + onPositiveClick: async () => { + try { + const res = await $http.get($constant.baseURL + "/imChatUserFriend/changeFriend", { + friendId: currentFriendId, + friendStatus: -1 + }); + delete friendData.friends[currentFriendId]; + friendData.currentFriendId = null; + ElMessage({ + message: "删除成功!", + type: 'success' + }); + } catch (error) { + ElMessage({ + message: error.message, + type: 'error' + }); + } + } + }); + } + + // 获取好友请求的函数 + function getFriendRequests() { + $http.get($constant.baseURL + "/imChatUserFriend/getFriend", {friendStatus: 0}) + .then((res) => { + if (!$common.isEmpty(res.data)) { + friendData.friendRequests = res.data; + ElMessage({ + message: "您有好友申请待处理!", + showClose: true, + type: 'success', + duration: 0 + }); + } + }) + .catch((error) => { + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 修改好友请求状态的函数 + function changeFriendStatus(friendId, status, index) { + $http.get($constant.baseURL + "/imChatUserFriend/changeFriend", {friendId: friendId, friendStatus: status}) + .then((res) => { + friendData.friendRequests.splice(index, 1); + ElMessage({ + message: "修改成功!", + type: 'success' + }); + }) + .catch((error) => { + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 返回对象,包含响应式数据和函数,供外部使用 + return { + friendData, + getImFriend, + removeFriend, + getFriendRequests, + changeFriendStatus + }; +} + + +poetizepoetize-im-uisrchooksfriendCircle.js + +import {useStore} from 'vuex'; // 引入Vuex的useStore函数,用于访问Vuex store +import {useDialog} from 'naive-ui'; // 引入Naive UI的useDialog函数,用于弹出对话框 +import {nextTick} from 'vue'; // 引入Vue的nextTick函数,用于在下次DOM更新循环结束之后执行延迟回调 +import {ElMessage} from "element-plus"; // 引入Element Plus的ElMessage组件,用于显示消息提示 +import {reactive, getCurrentInstance, onMounted, onBeforeUnmount, watchEffect, toRefs} from 'vue'; // 引入Vue的响应式API和其他生命周期钩子 + +export default function () { + // 获取Vue实例的全局属性 + const globalProperties = getCurrentInstance().appContext.config.globalProperties; + const $common = globalProperties.$common; // 获取全局的common工具集 + const $http = globalProperties.$http; // 获取全局的http请求方法 + const $constant = globalProperties.$constant; // 获取全局的常量配置 + const store = useStore(); // 使用Vuex store + const dialog = useDialog(); // 使用Naive UI的对话框 + + // 定义响应式数据 + let friendCircleData = reactive({ + showFriendCircle: false, // 是否显示朋友圈 + treeHoleList: [], // 朋友圈列表数据 + weiYanDialogVisible: false, // 是否显示发表朋友圈对话框 + isPublic: true, // 发表的朋友圈是否公开 + weiYanAvatar: '', // 发表朋友圈的用户头像 + weiYanUsername: '', // 发表朋友圈的用户名 + pagination: { // 分页配置 + current: 1, + size: 10, + total: 0, + userId: null + } + }); + + // 显示发表朋友圈对话框 + function launch() { + friendCircleData.weiYanDialogVisible = true; + } + + // 打开指定用户的朋友圈 + function openFriendCircle(userId, avatar, username) { + friendCircleData.pagination.userId = userId; // 设置当前用户ID + friendCircleData.weiYanAvatar = avatar; // 设置用户头像 + friendCircleData.weiYanUsername = username; // 设置用户名 + getWeiYan(); // 获取朋友圈数据 + } + + // 删除朋友圈动态 + function deleteTreeHole(id) { + dialog.error({ // 弹出确认删除对话框 + title: '警告', + content: '确定删除?', + positiveText: '确定', + onPositiveClick: () => { + $http.get($constant.baseURL + "/weiYan/deleteWeiYan", {id: id}) // 发送删除请求 + .then((res) => { + ElMessage({ // 提示删除成功 + message: "删除成功!", + type: 'success' + }); + resetPaginationAndList(); // 重置分页和列表数据 + getWeiYan(); // 重新获取朋友圈数据 + }) + .catch((error) => { + ElMessage({ // 提示错误信息 + message: error.message, + type: 'error' + }); + }); + } + }); + } + + // 获取朋友圈数据 + function getWeiYan() { + $http.post($constant.baseURL + "/weiYan/listWeiYan", friendCircleData.pagination) + .then((res) => { + if (!$common.isEmpty(res.data)) { // 如果返回的数据不为空 + processRecords(res.data.records); // 处理朋友圈记录 + friendCircleData.treeHoleList = friendCircleData.treeHoleList.concat(res.data.records); // 合并到列表 + friendCircleData.pagination.total = res.data.total; // 更新总记录数 + friendCircleData.showFriendCircle = true; // 显示朋友圈 + } + }) + .catch((error) => { + ElMessage({ // 提示错误信息 + message: error.message, + type: 'error' + }); + }); + } + + // 提交发表朋友圈 + function submitWeiYan(content) { + let weiYan = { + content: content, + isPublic: friendCircleData.isPublic + }; + + $http.post($constant.baseURL + "/weiYan/saveWeiYan", weiYan) + .then((res) => { + resetPaginationAndList(); // 重置分页和列表数据 + friendCircleData.weiYanDialogVisible = false; // 关闭发表对话框 + getWeiYan(); // 重新获取朋友圈数据 + }) + .catch((error) => { + ElMessage({ // 提示错误信息 + message: error.message, + type: 'error' + }); + }); + } + + // 重置分页和列表数据 + function resetPaginationAndList() { + friendCircleData.pagination = { + current: 1, + size: 10, + total: 0, + userId: null + }; + friendCircleData.treeHoleList = []; + } + + // 清理朋友圈数据 + function cleanFriendCircle() { + resetPaginationAndList(); // 重置分页和列表数据 + friendCircleData.weiYanAvatar = ''; // 清空头像 + friendCircleData.weiYanUsername = ''; // 清空用户名 + friendCircleData.showFriendCircle = false; // 隐藏朋友圈 + } + + // 下一页朋友圈数据 + function pageWeiYan() { + friendCircleData.pagination.current = friendCircleData.pagination.current + 1; // 更新当前页码 + getWeiYan(); // 重新获取朋友圈数据 + } + + // 添加好友 + function addFriend() { + dialog.success({ // 弹出确认添加好友对话框 + title: '好友申请', + content: '确认提交好友申请,添加 ' + friendCircleData.weiYanUsername + ' 为好友?', + positiveText: '确定', + onPositiveClick: () => { + $http.get($constant.baseURL + "/imChatUserFriend/addFriend", {friendId: friendCircleData.pagination.userId}) + .then((res) => { + ElMessage({ // 提示提交成功 + message: "提交成功!", + type: 'success' + }); + }) + .catch((error) => { + ElMessage({ // 提示错误信息 + message: error.message, + type: 'error' + }); + }); + } + }); + } + + // 处理朋友圈记录,包括替换换行符和表情符号等 + function processRecords(records) { + records.forEach(c => { + c.content = c.content.replace(/\n{2,}/g, '
'); // 替换连续换行符 + c.content = c.content.replace(/\n/g, '
'); // 替换单个换行符 + c.content = $common.faceReg(c.content); // 替换表情符号 + c.content = $common.pictureReg(c.content); // 替换图片链接 + }); + } + + // 返回对外暴露的方法和数据 + return { + friendCircleData, + launch, + openFriendCircle, + deleteTreeHole, + submitWeiYan, + pageWeiYan, + cleanFriendCircle, + addFriend + }; +} diff --git a/注释(6) b/注释(6) new file mode 100644 index 0000000..ec26fb2 --- /dev/null +++ b/注释(6) @@ -0,0 +1,469 @@ +poetizepoetize-im-uisrchooksgroup.js + +// 引入必要的Vuex、Naive UI、Vue和Element Plus的函数和对象 +import { useStore } from 'vuex'; // Vuex的useStore钩子,用于访问Vuex store +import { useDialog } from 'naive-ui'; // Naive UI的useDialog钩子,用于显示对话框 +import { nextTick } from 'vue'; // Vue的nextTick函数,用于延迟执行到下次DOM更新循环之后 +import { ElMessage } from "element-plus"; // Element Plus的ElMessage,用于显示消息提示 +import { + reactive, // Vue的reactive函数,用于创建一个响应式对象 + getCurrentInstance, // Vue的getCurrentInstance函数,用于获取当前组件实例 + onMounted, // Vue的onMounted生命周期钩子,用于组件挂载后执行 + onBeforeUnmount, // Vue的onBeforeUnmount生命周期钩子,用于组件卸载前执行 + watchEffect, // Vue的watchEffect函数,用于执行副作用 + toRefs // Vue的toRefs函数,用于将响应式对象转换为响应式引用对象 +} from 'vue'; + +export default function () { + // 获取当前Vue应用的全局属性 + const globalProperties = getCurrentInstance().appContext.config.globalProperties; + const $common = globalProperties.$common; // 自定义的全局方法或属性集合 + const $http = globalProperties.$http; // 自定义的HTTP请求方法 + const $constant = globalProperties.$constant; // 自定义的常量集合 + const store = useStore(); // 访问Vuex store + const dialog = useDialog(); // 访问Naive UI的对话框 + + // 定义响应式对象,用于存储群组数据和当前群组ID + let groupData = reactive({ + groups: {}, // 群组列表,键为群组ID,值为群组信息 + currentGroupId: null // 当前选中的群组ID + }); + + // 退出群组的功能 + function exitGroup(currentGroupId) { + $http.get($constant.baseURL + "/imChatGroupUser/quitGroup", {id: currentGroupId}) + .then((res) => { + // 成功退出群组后,从群组列表中删除当前群组,并重置当前群组ID + delete groupData.groups[currentGroupId]; + groupData.currentGroupId = null; + // 显示成功消息 + ElMessage({ + message: "退群成功!", + type: 'success' + }); + }) + .catch((error) => { + // 请求失败时显示错误消息 + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 解散群组的功能 + function dissolveGroup(currentGroupId) { + $http.get($constant.baseURL + "/imChatGroup/deleteGroup", {id: currentGroupId}) + .then((res) => { + // 成功解散群组后,从群组列表中删除当前群组,并重置当前群组ID + delete groupData.groups[currentGroupId]; + groupData.currentGroupId = null; + // 显示成功消息 + ElMessage({ + message: "解散群成功!", + type: 'success' + }); + }) + .catch((error) => { + // 请求失败时显示错误消息 + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 获取群组列表的功能 + async function getImGroup() { + await $http.get($constant.baseURL + "/imChatGroup/listGroup") + .then((res) => { + // 如果返回的数据不为空,则遍历数据并更新群组列表 + if (!$common.isEmpty(res.data)) { + res.data.forEach(group => { + groupData.groups[group.id] = group; + }); + } + }) + .catch((error) => { + // 请求失败时显示错误消息 + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 添加群组话题的功能(此函数当前为空,可能需要在后续实现) + function addGroupTopic() { + $http.get($constant.baseURL + "/imChatGroup/addGroupTopic", {id: groupData.currentGroupId}) + .then((res) => { + // 成功后的处理(当前为空) + }) + .catch((error) => { + // 请求失败时显示错误消息 + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 返回对外暴露的属性和方法 + return { + groupData, // 群组数据和当前群组ID + getImGroup, // 获取群组列表的方法 + addGroupTopic, // 添加群组话题的方法(当前为空) + exitGroup, // 退出群组的方法 + dissolveGroup // 解散群组的方法 + }; +} + + +poetizepoetize-im-uisrchooksimUtil.js + +import {useStore} from 'vuex'; // 从vuex导入useStore,用于访问Vuex状态管理 +import {useDialog} from 'naive-ui'; // 从naive-ui导入useDialog,用于控制对话框 +import {nextTick} from 'vue'; // 导入nextTick,用于在下次DOM更新循环结束之后执行延迟回调 +import {ElMessage} from "element-plus"; // 从element-plus导入ElMessage,用于显示消息提示 +import {reactive, getCurrentInstance, onMounted, onBeforeUnmount, watchEffect, toRefs} from 'vue'; // 从vue导入多个API,用于响应式数据、获取当前实例、生命周期钩子、观察副作用等 + +export default function () { + // 获取Vue应用的全局属性 + const globalProperties = getCurrentInstance().appContext.config.globalProperties; + const $common = globalProperties.$common; // 获取全局的通用方法 + const $http = globalProperties.$http; // 获取全局的HTTP请求方法 + const $constant = globalProperties.$constant; // 获取全局的常量配置 + const store = useStore(); // 使用Vuex的store + const dialog = useDialog(); // 使用naive-ui的对话框 + + // 定义响应式数据 + let imUtilData = reactive({ + systemMessages: [], // 系统消息列表 + showBodyLeft: true, // 控制左侧区域显示状态的标志 + imageList: [] // 表情包图片列表 + }); + + // 组件挂载后执行的逻辑 + onMounted(() => { + if ($common.mobile()) { // 如果是移动设备 + $(".friend-aside").click(function () { // 点击左侧区域 + imUtilData.showBodyLeft = true; // 显示左侧区域 + mobileRight(); // 调整右侧区域的样式 + }); + + $(".body-right").click(function () { // 点击右侧区域 + imUtilData.showBodyLeft = false; // 隐藏左侧区域 + mobileRight(); // 调整右侧区域的样式 + }); + } + mobileRight(); // 初始化右侧区域的样式 + }); + + // 切换左侧区域的显示状态 + function changeAside() { + imUtilData.showBodyLeft = !imUtilData.showBodyLeft; + mobileRight(); + } + + // 根据showBodyLeft的值调整右侧区域的样式 + function mobileRight() { + if (imUtilData.showBodyLeft && $common.mobile()) { + $(".body-right").addClass("mobile-right"); // 添加样式以显示右侧区域 + } else if (!imUtilData.showBodyLeft && $common.mobile()) { + $(".body-right").removeClass("mobile-right"); // 移除样式以隐藏右侧区域 + } + } + + // 获取系统消息 + function getSystemMessages() { + $http.get($constant.baseURL + "/imChatUserMessage/listSystemMessage") + .then((res) => { + if (!$common.isEmpty(res.data) && !$common.isEmpty(res.data.records)) { + imUtilData.systemMessages = res.data.records; // 更新系统消息列表 + } + }) + .catch((error) => { + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 隐藏左侧区域(重复代码,建议合并或移除) + function hiddenBodyLeft() { + if ($common.mobile()) { + $(".body-right").click(function () { + imUtilData.showBodyLeft = false; + mobileRight(); + }); + } + } + + // 显示图片预览 + function imgShow() { + $(".message img").click(function () { + let src = $(this).attr("src"); + $("#bigImg").attr("src", src); + // 以下代码用于计算并设置图片预览的样式和位置 + // ... + }); + + $("#outerImg").click(function () { // 点击外层元素关闭图片预览 + $(this).fadeOut("fast"); + }); + } + + // 获取表情包图片列表 + function getImageList() { + $http.get($constant.baseURL + "/resource/getImageList") + .then((res) => { + if (!$common.isEmpty(res.data)) { + imUtilData.imageList = res.data; // 更新表情包图片列表 + } + }) + .catch((error) => { + ElMessage({ + message: error.message, + type: 'error' + }); + }); + } + + // 解析消息内容,包括换行符替换和表情/图片解析 + function parseMessage(content) { + content = content.replace(/\n{2,}/g, '
'); // 替换多个换行符 + content = content.replace(/\n/g, '
'); // 替换单个换行符 + content = $common.faceReg(content); // 替换表情 + content = $common.pictureReg(content); // 替换图片链接 + return content; + } + + // 返回对象,供外部使用 + return { + imUtilData, + changeAside, + mobileRight, + getSystemMessages, + hiddenBodyLeft, // 建议移除或合并 + imgShow, + getImageList, + parseMessage + }; +} + + +poetizepoetize-im-uisrccomponentscommonuploadPicture.vue + + + + + + diff --git a/注释(7) b/注释(7) new file mode 100644 index 0000000..fba7c21 --- /dev/null +++ b/注释(7) @@ -0,0 +1,194 @@ +poetizepoetize-im-uisrccomponentscommontreeHole.vue + + + +poetizepoetize-im-uisrccomponentscommonproButton.vue + + + + + + + + +poetizepoetize-im-uisrccomponentscommongroupInfo.vue + \ No newline at end of file diff --git a/注释(8) b/注释(8) new file mode 100644 index 0000000..b573478 --- /dev/null +++ b/注释(8) @@ -0,0 +1,139 @@ + + + + + + + \ No newline at end of file diff --git a/质量分析报告.docx b/质量分析报告.docx new file mode 100644 index 0000000..830e710 Binary files /dev/null and b/质量分析报告.docx differ