test #1

Open
pnhq5agf9 wants to merge 10 commits from mengqi into fengkaifeng

Binary file not shown.

@ -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
<template>
<!-- 使用naive-ui的NConfigProvider组件来提供全局配置 -->
<!-- :theme="null" 表示不应用任何预设主题,允许使用默认或自定义样式 -->
<!-- :locale="zhCN" 设置应用的语言环境为简体中文 -->
<!-- :date-locale="dateZhCN" 设置日期和时间的相关格式为简体中文 -->
<n-config-provider :theme="null" :locale="zhCN" :date-locale="dateZhCN">
<!-- 使用NDialogProvider组件来管理对话框的全局状态 -->
<!-- 这有助于在应用的任何位置控制对话框的显示和隐藏 -->
<n-dialog-provider>
<!-- router-view是Vue Router的一个组件用于显示当前路由匹配的组件 -->
<!-- 它根据URL的变化来动态渲染不同的组件 -->
<router-view/>
</n-dialog-provider>
</n-config-provider>
</template>
<script>
// 从naive-ui库中导入所需的组件和语言/日期配置
import { NConfigProvider, NDialogProvider, zhCN, dateZhCN, darkTheme } from 'naive-ui';
export default {
// 注册在模板中使用的组件
components: {
NConfigProvider,
NDialogProvider
},
setup() {
// setup函数是Vue 3 Composition API的一部分用于定义组件的响应式状态、计算属性和方法等
// 这里返回的对象中的属性可以在模板中直接使用
return {
// darkTheme是从naive-ui导入的但在这个组件中没有被直接使用
// 它可能用于其他目的,比如根据条件动态切换主题
darkTheme,
// zhCN是简体中文的语言配置用于NConfigProvider的locale属性
zhCN,
// dateZhCN是简体中文的日期和时间格式配置用于NConfigProvider的date-locale属性
dateZhCN
// 注意虽然zhCN和dateZhCN在setup函数中返回了但在模板中它们是通过NConfigProvider的props直接使用的
// 因此在模板中并不需要直接使用这些变量但它们必须被导入并在setup中返回以确保NConfigProvider能够接收到正确的props
}
}
}
</script>
<style scoped>
/* scoped样式意味着这里的CSS只会应用于当前组件不会影响到其他组件 */
/* 当前组件没有定义任何样式,所以这里保持为空 */
</style>
// 从当前目录下的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;
}

@ -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');

@ -0,0 +1,258 @@
poetizepoetize-im-uisrcApp.vue
<template>
<!-- 使用NConfigProvider组件来全局配置Naive UI的主题、语言和日期格式 -->
<n-config-provider :theme="null" :locale="zhCN" :date-locale="dateZhCN">
<!-- 使用NDialogProvider组件来管理对话框的渲染上下文确保对话框能够在正确的位置渲染 -->
<n-dialog-provider>
<!-- 路由视图,用于显示当前路由匹配的组件 -->
<router-view/>
</n-dialog-provider>
</n-config-provider>
</template>
<script>
// 从naive-ui库中导入所需的组件和配置
import { NConfigProvider, NDialogProvider, zhCN, dateZhCN, darkTheme } from 'naive-ui';
export default {
// 注册组件,使其在模板中可用
components: {
NConfigProvider, // 全局配置组件
NDialogProvider // 对话框上下文管理组件
},
setup() {
// setup函数是Vue 3的组合式API的一部分用于定义响应式状态、计算属性和方法等
// 这里返回的对象中的属性可以在模板中直接使用
return {
// 虽然darkTheme被导入但在这个示例中并未使用它代表一个可能的暗色主题配置
darkTheme,
// zhCN是Naive UI的中文语言包配置
zhCN,
// dateZhCN是Naive UI的中文日期格式配置
dateZhCN
}
// 注意:在<n-config-provider>中,:theme="null"表示不使用特定的主题,
// :locale="zhCN"将语言设置为中文,:date-locale="dateZhCN"将日期格式设置为中文格式。
}
}
</script>
<style scoped>
<!-- scoped关键字表示这些样式只会应用到当前组件的DOM上避免样式污染 -->
</style>
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); // 请求失败,返回错误信息
});
});
}
};

@ -0,0 +1,442 @@
poetizepoetize-im-uisrcApp.vue
<template>
<!-- 使用NConfigProvider组件来全局配置Naive UI的主题、语言和日期格式 -->
<n-config-provider :theme="null" :locale="zhCN" :date-locale="dateZhCN">
<!-- 使用NDialogProvider组件来管理对话框的渲染上下文确保对话框能够在正确的位置渲染 -->
<n-dialog-provider>
<!-- 路由视图,用于显示当前路由匹配的组件 -->
<router-view/>
</n-dialog-provider>
</n-config-provider>
</template>
<script>
// 从naive-ui库中导入所需的组件和配置
import { NConfigProvider, NDialogProvider, zhCN, dateZhCN, darkTheme } from 'naive-ui';
export default {
// 注册组件,使其在模板中可用
components: {
NConfigProvider, // 全局配置组件
NDialogProvider // 对话框上下文管理组件
},
setup() {
// setup函数是Vue 3的组合式API的一部分用于定义响应式状态、计算属性和方法等
// 这里返回的对象中的属性可以在模板中直接使用
return {
// 虽然darkTheme被导入但在这个示例中并未使用它代表一个可能的暗色主题配置
darkTheme,
// zhCN是Naive UI的中文语言包配置
zhCN,
// dateZhCN是Naive UI的中文日期格式配置
dateZhCN
}
// 注意:在<n-config-provider>中,:theme="null"表示不使用特定的主题,
// :locale="zhCN"将语言设置为中文,:date-locale="dateZhCN"将日期格式设置为中文格式。
}
}
</script>
<style scoped>
<!-- scoped关键字表示这些样式只会应用到当前组件的DOM上避免样式污染 -->
</style>
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 '<img loading="lazy" style="vertical-align: middle;width: 32px;height: 32px" src="' + url + '" title="' + word + '"/>';
} 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 '<img loading="lazy" style="border-radius: 5px;width: 100%;max-width: 250px" src="' + arr[1] + '" title="' + arr[0] + '"/>';
} 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 - 存储类型
*/

@ -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, '<div style="height: 12px"></div>'); // 替换连续换行符
c.content = c.content.replace(/\n/g, '<br/>'); // 替换单个换行符
c.content = $common.faceReg(c.content); // 替换表情符号
c.content = $common.pictureReg(c.content); // 替换图片链接
});
}
// 返回对外暴露的方法和数据
return {
friendCircleData,
launch,
openFriendCircle,
deleteTreeHole,
submitWeiYan,
pageWeiYan,
cleanFriendCircle,
addFriend
};
}

@ -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, '<div style="height: 12px"></div>'); // 替换多个换行符
content = content.replace(/\n/g, '<br/>'); // 替换单个换行符
content = $common.faceReg(content); // 替换表情
content = $common.pictureReg(content); // 替换图片链接
return content;
}
// 返回对象,供外部使用
return {
imUtilData,
changeAside,
mobileRight,
getSystemMessages,
hiddenBodyLeft, // 建议移除或合并
imgShow,
getImageList,
parseMessage
};
}
poetizepoetize-im-uisrccomponentscommonuploadPicture.vue
<template>
<div>
<!-- 使用Element Plus的el-upload组件进行文件上传 -->
<el-upload
class="upload-demo"
<!-- 上传文件的服务器地址从Vuex的state中获取 -->
:action="$store.state.sysConfig.qiniuUrl"
<!-- 允许同时上传多个文件 -->
multiple
<!-- 拖拽上传 -->
drag
<!-- 文件移除时的钩子 -->
:on-remove="handleRemove"
<!-- 限制上传文件的数量 -->
:limit="maxNumber"
<!-- 引用名称用于在方法中访问这个el-upload组件 -->
ref="upload"
<!-- 设置为false文件不会自动上传需要手动触发 -->
:auto-upload="false"
<!-- 自定义上传方法 -->
:http-request="customUpload"
<!-- 文件状态改变时的钩子 -->
:on-change="handleChange"
<!-- 上传文件之前的钩子 -->
:before-upload="beforeUpload"
<!-- 文件上传成功时的钩子 -->
:on-success="handleSuccess"
<!-- 文件上传失败时的钩子 -->
:on-error="handleError"
<!-- 文件列表的类型 -->
list-type="picture"
<!-- 接受的文件类型 -->
accept="image/*">
<div class="el-upload__text">
<!-- 上传提示的图标 -->
<svg viewBox="0 0 1024 1024" width="40" height="40">
<!-- 图标路径 -->
<path d="..." fill="#FFE37B"></path>
<path d="..." fill="#8C7BFD"></path>
</svg>
<!-- 上传提示文字 -->
<div>拖拽上传 / 点击上传</div>
</div>
<!-- 上传提示信息插槽 -->
<template #tip>
<div class="el-upload__tip">
<!-- 提示信息,限制上传图片的数量和大小 -->
一次最多上传{{maxNumber}}张图片,且每张图片不超过{{maxSize}}M
</div>
</template>
</el-upload>
<!-- 手动上传按钮 -->
<div style="text-align: center;margin-top: 20px">
<el-button type="success" style="font-size: 12px" @click="submitUpload">
上传
</el-button>
</div>
</div>
</template>
<script>
// 引入Element Plus的消息提示组件
import {ElMessage} from "element-plus";
// 引入自定义的上传工具函数
import upload from '../../utils/ajaxUpload';
export default {
// 组件接收的props
props: {
prefix: {
type: String,
default: ""
},
maxSize: {
type: Number,
default: 5
},
maxNumber: {
type: Number,
default: 5
}
},
data() {
return {
// 从localStorage中获取存储类型
storeType: localStorage.getItem("defaultStoreType")
}
},
created() {
// 组件创建时的逻辑
},
methods: {
// 手动触发上传的方法
submitUpload() {
this.$refs.upload.submit();
},
// 文件上传成功时的处理
handleSuccess(response, file, fileList) {
let url;
// 根据存储类型处理上传成功后的URL
if (this.storeType === "local") {
url = response.data;
} else if (this.storeType === "qiniu") {
url = this.$store.state.sysConfig['qiniu.downloadUrl'] + response.key;
// 调用自定义方法保存资源信息
this.$common.saveResource(this, this.prefix, url, file.size, file.raw.type, file.name, "qiniu");
}
// 触发父组件的addPicture事件传递URL
this.$emit("addPicture", url);
},
// 自定义上传方法
customUpload(options) {
// 获取文件后缀
let suffix = "";
if (options.file.name.lastIndexOf('.') !== -1) {
suffix = options.file.name.substring(options.file.name.lastIndexOf('.'));
}
// 生成文件在服务器上的唯一键
let key = this.prefix + "/" + this.$store.state.currentUser.username.replace(/[^a-zA-Z]/g, '') + this.$store.state.currentUser.id + new Date().getTime() + Math.floor(Math.random() * 1000) + suffix;
let data = {};
data.key = key;
options.data = data;
// 根据存储类型执行不同的上传逻辑
if (this.storeType === "local") {
// 本地存储逻辑
data.relativePath = key;
data.type = this.prefix;
data.storeType = this.storeType;
data.originalName = options.file.name;
data.file = options.file;
return this.$http.upload(this.$constant.baseURL + "/resource/upload", data, options);
} else if (this.storeType === "qiniu") {
// 七牛云存储逻辑
const xhr = new XMLHttpRequest();
xhr.open('get', this.$constant.baseURL + "/qiniu/getUpToken?key=" + key, false);
xhr.setRequestHeader("Authorization", localStorage.getItem("userToken"));
try {
xhr.send();
const res = JSON.parse(xhr.responseText);
if (res && res.code === 200) {
data.token = res.data;
return upload(options);
} else if (res && res.code !== 200) {
return Promise.reject(res.message);
} else {
return Promise.reject("服务异常!");
}
} catch (e) {
return Promise.reject(e.message);
}
}
},
// 文件上传失败时的处理
handleError(err, file, fileList) {
ElMessage({
message: err,
type: 'error'
});
},
// 文件列表移除文件时的处理
handleRemove(file, fileList) {
// 可在此处添加移除文件的逻辑
},
// 上传文件之前的处理
beforeUpload(file) {
// 可在此处添加文件上传前的校验逻辑
},
// 文件状态改变时的处理
handleChange(file, fileList) {
let flag = false;
// 检查文件大小是否超过限制
if (file.size > this.maxSize * 1024 * 1024) {
ElMessage({
message: "图片最大为" + this.maxSize + "M",
type: 'warning'
});
flag = true;
}
// 检查文件数量是否超过限制
if (fileList.length > this.maxNumber) {
flag = true;
}
// 如果文件不符合要求,则从文件列表中移除
if (flag) {
fileList.splice(fileList.length - 1, 1);
}
}
}
}
</script>
<style scoped>
<!-- 组件的样式,这里为空,表示没有自定义样式 -->
</style>

@ -0,0 +1,194 @@
poetizepoetize-im-uisrccomponentscommontreeHole.vue
<template>
<!-- 树洞容器 -->
<div class="tree-hole-container">
<!-- 树洞列表仅当treeHoleList不为空时显示 -->
<ol class="tree-hole-list" v-if="!$common.isEmpty(treeHoleList)">
<!-- 树洞列表项使用v-for循环遍历treeHoleList -->
<li class="tree-hole-li"
v-for="(treeHole, index) in treeHoleList"
style="animation: hideToShow 1.5s"
:key="index">
<!-- 树洞内容 -->
<div class="tree-hole-content"
:class="{ leftTreeHole: index % 2 === 0 && !$common.mobile(), rightTreeHole: index % 2 !== 0 || $common.mobile() }">
<!-- 用户头像 -->
<n-avatar object-fit="cover"
lazy
class="avatar-img"
:size="36"
:src="avatar"/>
<!-- 树洞盒子,背景颜色根据索引变化 -->
<div class="tree-hole-box"
:style="{background: $constant.tree_hole_color[index % $constant.tree_hole_color.length]}">
<!-- 标签,根据索引和屏幕大小显示不同的样式 -->
<div class="box-tag" v-if="index % 2 === 0 && !$common.mobile()"
:style="{'border-color': 'transparent transparent transparent '+$constant.tree_hole_color[index % $constant.tree_hole_color.length]}"></div>
<div class="box-tag" v-if="index % 2 !== 0 || $common.mobile()"
:style="{'border-color': 'transparent '+$constant.tree_hole_color[index % $constant.tree_hole_color.length]+' transparent transparent'}"></div>
<!-- 树洞内容使用v-html渲染富文本 -->
<div class="my-content" v-html="treeHole.content"></div>
<!-- 时间和删除按钮,仅对当前用户显示删除按钮 -->
<div style="display: flex;justify-content: space-between">
<div> {{treeHole.createTime}}</div>
<div @click="deleteTreeHole(treeHole.id)"
class="tree-hole-delete"
v-if="$store.state.currentUser.id === treeHole.userId">
<!-- 删除图标 -->
<svg viewBox="0 0 1024 1024" width="18" height="18" style="vertical-align: -2px;">
<path
d="M921.1392 155.392h-270.592v-48.2816c0-22.7328-18.432-41.1648-41.1648-41.1648H426.3424a41.1648 41.1648 0 0 0-41.1648 41.1648v48.2816H110.6432c-14.1312 0-25.6 11.4688-25.6 25.6s11.4688 25.6 25.6 25.6h810.496c14.1312 0 25.6-11.4688 25.6-25.6s-11.4688-25.6-25.6-25.6zM170.8032 260.0448v592.8448c0 50.8928 41.2672 92">
</path>
</svg>
</div>
</div>
</div>
</li>
</ol>
</div>
</template>
poetizepoetize-im-uisrccomponentscommonproButton.vue
<template>
<!-- 按钮容器 -->
<div class="myButton">
<!-- 第一个按钮背景颜色由before属性控制 -->
<div :style="{'background': before}">{{info}}</div>
<!-- 第二个按钮背景颜色由after属性控制 -->
<div :style="{'background': after}">{{info}}</div>
<!-- 第三个按钮背景颜色由after属性控制 -->
<div :style="{'background': after}">{{info}}</div>
</div>
</template>
<script>
export default {
props: {
// 按钮显示的信息
info: {
type: String,
default: "确定" // 默认显示“确定”
},
// 第一个按钮的背景颜色
before: {
type: String,
default: "black" // 默认为黑色
},
// 第二个和第三个按钮的背景颜色(渐变色)
after: {
type: String,
default: "linear-gradient(45deg, #f43f3b, #ec008c)" // 默认为从#f43f3b到#ec008c的45度渐变色
}
}
}
</script>
<style scoped>
/* 按钮样式 */
.myButton {
cursor: pointer; /* 鼠标悬停时显示指针 */
user-select: none; /* 防止用户选择文本 */
position: relative; /* 相对定位 */
width: 66px; /* 宽度 */
height: 33px; /* 高度 */
border-radius: 4px; /* 圆角 */
color: var(--white); /* 文字颜色 */
font-size: 14px; /* 字体大小 */
overflow: hidden; /* 隐藏溢出的内容 */
}
/* 按钮内部div的样式 */
.myButton div {
width: 66px; /* 宽度 */
height: 33px; /* 高度 */
line-height: 33px; /* 行高 */
border-radius: 4px; /* 圆角 */
text-align: center; /* 文字居中 */
position: absolute; /* 绝对定位 */
}
/* 第二个按钮的特殊样式 */
.myButton div:nth-child(2) {
width: 100px; /* 宽度 */
transition: all 0.3s ease; /* 过渡效果 */
transform: translateX(-120px) skewX(-30deg); /* 初始位移和倾斜 */
}
/* 第三个按钮的特殊样式 */
.myButton div:nth-child(3) {
transition: all 0.3s ease; /* 过渡效果 */
transform: translateX(-120px); /* 初始位移 */
}
/* 鼠标悬停时第二个按钮的变化 */
.myButton:hover div:nth-child(2) {
transform: translateX(20px) skewX(-30deg); /* 位移和倾斜 */
}
/* 鼠标悬停时第三个按钮的变化 */
.myButton:hover div:nth-child(3) {
transform: translateX(0px); /* 位移 */
}
</style>
poetizepoetize-im-uisrccomponentscommongroupInfo.vue
<template>
<div>
<!-- 群名称区域,背景色为最大白色 -->
<div style="height: 60px;background-color: var(--maxWhite)">
<!-- 群名称位于左侧字体大小18px -->
<span style="line-height: 60px;margin-left: 20px;font-size: 18px">
{{groups[currentGroupId].groupName}}
</span>
</div>
<!-- 群信息区域,背景色为中等白色,高度为剩余空间 -->
<div style="background: var(--midWhite);height: calc(100% - 60px)">
<!-- 群头像区域上下内边距为50px -->
<div class="myCenter" style="padding: 50px 0">
<!-- 群头像大小为60px懒加载 -->
<n-avatar object-fit="cover"
:size="60"
lazy
:src="groups[currentGroupId].avatar"/>
</div>
<!-- 群信息内容区域 -->
<div class="myCenter">
<!-- 群信息内容宽度占65%字体大小16px -->
<div style="width: 65%;font-size: 16px">
<!-- 群名称标签和值 -->
<div style="margin-bottom: 10px">
<!-- 群名称标签 -->
<span class="friend-label">
群名称
</span>
<!-- 群名称值 -->
<span style="margin: 0 5px 0 0">
{{groups[currentGroupId].groupName}}
</span>
<!-- 如果当前用户是群主,则显示编辑图标 -->
<span @click="changeDataType(2)"
v-if="groups[currentGroupId].masterFlag"
style="display: inline-block;vertical-align: sub;cursor: pointer">
<!-- 编辑图标使用SVG -->
<svg viewBox="0 0 1024 1024" width="20" height="20">
<!-- 编辑图标的路径填充颜色为橙色透明度为0.502 -->
<path
d="M929.909189 827.019236H93.990821c-16.598379 0-29.997071 13.398692-29.99707 29.997071s13.398692 29.997071 29.99707 29.997071h835.918368c16.598379 0 29.997071-13.398692 29.99707-29.997071 0-16.498389-13.398692-29.997071-29.99707-29.997071z"
fill="#FF6600" opacity=".502"></path>
<!-- 编辑图标的另一路径 -->
<path
d="M705.931061 198.080656c3.099697 0 8.999121 0.799922 14.098624 5.899424l28.297236 28.297237c5.099502 5.099502 5.899424 10.998926 5.899424 14.098623 0 3.099697-0.799922 8.999121-5.899424 14.098623L392.161703 616.739772l-86.991505 28.997168 27.597305-82.791915 358.964945-358.964945c5.099502-5.199492 11.098916-5.899424 14.198613-5.899424m0-59.994141c-20.497998 0-40.896006 7.799238-56.594473 23.397715L281.672493 529.148325l-0.699932-0.699931-70.693096 212.079289 212.079289-70.693097 0.69">
</path>
</svg>
</span>
</div>
</div>
</div>
</div>
</div>
</template>

@ -0,0 +1,139 @@
<template>
<div>
<!-- 群名称区域,背景色为最大白色 -->
<div style="height: 60px;background-color: var(--maxWhite)">
<!-- 群名称位于左侧字体大小18px -->
<span style="line-height: 60px;margin-left: 20px;font-size: 18px">
{{groups[currentGroupId].groupName}}
</span>
</div>
<!-- 群信息区域,背景色为中等白色,高度为剩余空间 -->
<div style="background: var(--midWhite);height: calc(100% - 60px)">
<!-- 群头像区域上下内边距为50px -->
<div class="myCenter" style="padding: 50px 0">
<!-- 群头像大小为60px懒加载 -->
<n-avatar object-fit="cover"
:size="60"
lazy
:src="groups[currentGroupId].avatar"/>
</div>
<!-- 群信息内容区域 -->
<div class="myCenter">
<!-- 群信息内容宽度占65%字体大小16px -->
<div style="width: 65%;font-size: 16px">
<!-- 群名称标签和值 -->
<div style="margin-bottom: 10px">
<!-- 群名称标签 -->
<span class="friend-label">
群名称
</span>
<!-- 群名称值 -->
<span style="margin: 0 5px 0 0">
{{groups[currentGroupId].groupName}}
</span>
<!-- 如果当前用户是群主,则显示编辑图标 -->
<span @click="changeDataType(2)"
v-if="groups[currentGroupId].masterFlag"
style="display: inline-block;vertical-align: sub;cursor: pointer">
<!-- 编辑图标使用SVG -->
<svg viewBox="0 0 1024 1024" width="20" height="20">
<!-- 编辑图标的路径填充颜色为橙色透明度为0.502 -->
<path
d="M929.909189 827.019236H93.990821c-16.598379 0-29.997071 13.398692-29.99707 29.997071s13.398692 29.997071 29.99707 29.997071h835.918368c16.598379 0 29.997071-13.398692 29.99707-29.997071 0-16.498389-13.398692-29.997071-29.99707-29.997071z"
fill="#FF6600" opacity=".502"></path>
<!-- 编辑图标的另一路径 -->
<path
d="M705.931061 198.080656c3.099697 0 8.999121 0.799922 14.098624 5.899424l28.297236 28.297237c5.099502 5.099502 5.899424 10.998926 5.899424 14.098623 0 3.099697-0.799922 8.999121-5.899424 14.098623L392.161703 616.739772l-86.991505 28.997168 27.597305-82.791915 358.964945-358.964945c5.099502-5.199492 11.098916-5.899424 14.198613-5.899424m0-59.994141c-20.497998 0-40.896006 7.799238-56.594473 23.397715L281.672493 529.148325l-0.699932-0.699931-70.693096 212.079289 212.079289-70.693097 0.69">
</path>
</svg>
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<template>
<!-- 按钮容器 -->
<div class="myButton">
<!-- 第一个按钮背景颜色由before属性控制 -->
<div :style="{'background': before}">{{info}}</div>
<!-- 第二个按钮背景颜色由after属性控制 -->
<div :style="{'background': after}">{{info}}</div>
<!-- 第三个按钮背景颜色由after属性控制 -->
<div :style="{'background': after}">{{info}}</div>
</div>
</template>
<script>
export default {
props: {
// 按钮显示的信息
info: {
type: String,
default: "确定" // 默认显示“确定”
},
// 第一个按钮的背景颜色
before: {
type: String,
default: "black" // 默认为黑色
},
// 第二个和第三个按钮的背景颜色(渐变色)
after: {
type: String,
default: "linear-gradient(45deg, #f43f3b, #ec008c)" // 默认为从#f43f3b到#ec008c的45度渐变色
}
}
}
</script>
<style scoped>
/* 按钮样式 */
.myButton {
cursor: pointer; /* 鼠标悬停时显示指针 */
user-select: none; /* 防止用户选择文本 */
position: relative; /* 相对定位 */
width: 66px; /* 宽度 */
height: 33px; /* 高度 */
border-radius: 4px; /* 圆角 */
color: var(--white); /* 文字颜色 */
font-size: 14px; /* 字体大小 */
overflow: hidden; /* 隐藏溢出的内容 */
}
/* 按钮内部div的样式 */
.myButton div {
width: 66px; /* 宽度 */
height: 33px; /* 高度 */
line-height: 33px; /* 行高 */
border-radius: 4px; /* 圆角 */
text-align: center; /* 文字居中 */
position: absolute; /* 绝对定位 */
}
/* 第二个按钮的特殊样式 */
.myButton div:nth-child(2) {
width: 100px; /* 宽度 */
transition: all 0.3s ease; /* 过渡效果 */
transform: translateX(-120px) skewX(-30deg); /* 初始位移和倾斜 */
}
/* 第三个按钮的特殊样式 */
.myButton div:nth-child(3) {
transition: all 0.3s ease; /* 过渡效果 */
transform: translateX(-120px); /* 初始位移 */
}
/* 鼠标悬停时第二个按钮的变化 */
.myButton:hover div:nth-child(2) {
transform: translateX(20px) skewX(-30deg); /* 位移和倾斜 */
}
/* 鼠标悬停时第三个按钮的变化 */
.myButton:hover div:nth-child(3) {
transform: translateX(0px); /* 位移 */
}
</style>

Binary file not shown.
Loading…
Cancel
Save