From e2ec7bc3937d3a4d9c8fb8885df8144b9e478eae Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Tue, 8 Oct 2024 20:50:25 +0800 Subject: [PATCH 01/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=881?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(1) | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 注释(1) 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 -- 2.34.1 From 39ceef9844f51e83c08956ea27c77139be4ed8ef Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 17 Nov 2024 20:29:52 +0800 Subject: [PATCH 02/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=882?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(2) | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 注释(2) 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'); -- 2.34.1 From 708f262618d875244e619457d20aaefca0331df2 Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 17 Nov 2024 20:39:44 +0800 Subject: [PATCH 03/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=883?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(3) | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 注释(3) 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); // 请求失败,返回错误信息 + }); + }); + } +}; -- 2.34.1 From 97c82746d8cb99e439ee433da2415100abac67b2 Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 17 Nov 2024 20:53:17 +0800 Subject: [PATCH 04/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=884?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(4) | 442 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 注释(4) 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 - 存储类型 + */ -- 2.34.1 From aa17f031eaab542755d9199270aa1ac1453251ca Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 17 Nov 2024 21:18:24 +0800 Subject: [PATCH 05/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=885?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(5) | 634 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 634 insertions(+) create mode 100644 注释(5) 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 + }; +} -- 2.34.1 From 71a45fa5f51f69744df58c2e594d721ba5ec0f59 Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 17 Nov 2024 21:28:18 +0800 Subject: [PATCH 06/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=886?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(6) | 469 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 注释(6) 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 + + + + + + -- 2.34.1 From ab6c4571d4b9ac98549d5aeb96ea90122989e2eb Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 15 Dec 2024 17:20:04 +0800 Subject: [PATCH 07/10] ADD file via upload --- 质量分析报告.docx | Bin 0 -> 26289 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 质量分析报告.docx diff --git a/质量分析报告.docx b/质量分析报告.docx new file mode 100644 index 0000000000000000000000000000000000000000..830e71071330bf20e659b454ea3b9ecbe8f7fbec GIT binary patch literal 26289 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 literal 0 HcmV?d00001 -- 2.34.1 From 35807223d499c75f8f187981d7e7740b3f0032db Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 15 Dec 2024 17:35:59 +0800 Subject: [PATCH 08/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=887?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(7) | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 注释(7) 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 -- 2.34.1 From 53a74b9fe01a2a72c6490df0ca9d8efedfe3f5cf Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Sun, 15 Dec 2024 21:29:56 +0800 Subject: [PATCH 09/10] =?UTF-8?q?Add=20=E6=B3=A8=E9=87=8A=EF=BC=888?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 注释(8) | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 注释(8) 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 -- 2.34.1 From 0cbe29ef55618b191792feec6d5136b0d0622b34 Mon Sep 17 00:00:00 2001 From: pfewmlupo <3097217416@qq.com> Date: Mon, 16 Dec 2024 18:04:33 +0800 Subject: [PATCH 10/10] ADD file via upload --- 泛读报告.docx | Bin 0 -> 27547 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 泛读报告.docx diff --git a/泛读报告.docx b/泛读报告.docx new file mode 100644 index 0000000000000000000000000000000000000000..0e9e7ae36a4fcd6cd61b75bbae50dfbf6b1a3706 GIT binary patch literal 27547 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 szrlaDGajP