私域操盘手模块调整

This commit is contained in:
柳清爽
2025-04-09 10:35:28 +08:00
parent 9d6f62b670
commit 899bac425b
209 changed files with 88 additions and 40408 deletions

View File

@@ -1,8 +0,0 @@
NODE_ENV=production
VUE_APP_PREVIEW=false
VUE_APP_API_BASE_URL=http://yishi.com
VUE_APP_WWW_BASE_URL=http://yishi.com
VUE_APP_WEB_SOCKET_URL=ws://yishi.com:2348
VUE_APP_WEBSITE_NAME="管理后台"
VUE_APP_TITLE=医师管理系统
VUE_APP_API_URL=http://yishi.com

View File

@@ -1,14 +0,0 @@
# 环境标识
NODE_ENV=production
# 应用名称
VUE_APP_WEBSITE_NAME=医师管理系统
# API基础URL
VUE_APP_API_BASE_URL=https://api.yishi.com
# 前端网站URL
VUE_APP_WWW_BASE_URL=https://www.yishi.com
# WebSocket URL
VUE_APP_WEB_SOCKET_URL=wss://api.yishi.com/ws

View File

@@ -1,14 +0,0 @@
# 环境标识
NODE_ENV=production
# 应用名称
VUE_APP_WEBSITE_NAME=医师管理系统(测试)
# API基础URL
VUE_APP_API_BASE_URL=https://test-api.yishi.com
# 前端网站URL
VUE_APP_WWW_BASE_URL=https://test.yishi.com
# WebSocket URL
VUE_APP_WEB_SOCKET_URL=wss://test-api.yishi.com/ws

24
Backend/.gitignore vendored
View File

@@ -1,24 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.development
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 YuanDong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,166 +0,0 @@
# Lumen IM 即时聊天系统(前端)
### 项目介绍
Lumen IM 是一个网页版在线即时聊天项目,前端使用 Element-ui + Vue后端采用了基于 Swoole 开发的 Hyperf 协程框架进行接口开发,并使用 WebSocket 服务进行消息实时推送。目前后端 WebSocket 已支持分布式集群部署。
目前该项目是在 [旧版本](https://github.com/gzydong/LumenIM/tree/v1.0.0) 项目的基础上进行了后端重构,且前后端都有较大的改动。
### 功能模块
- 基于 Swoole WebSocket 服务做消息即时推送
- 支持私聊及群聊
- 支持多种聊天消息类型 例如:文本、代码块、图片及其它类型文件,并支持文件下载
- 支持聊天消息撤回、删除(批量删除)、转发消息(逐条转发、合并转发)
- 支持编写个人笔记、支持笔记分享(好友或群)
### 项目预览
- 地址: [http://im.gzydong.club](http://im.gzydong.club)
- 账号: 18798272054 或 18798272055
- 密码: admin123
### 项目安装(部署)
###### 设置 npm 镜像源
```language
npm config set registry https://registry.npm.taobao.org
```
###### 下载安装
```bash
## 克隆项目源码包
git clone git@github.com:gzydong/LumenIM.git
## 安装项目依赖扩展组件
npm install
# 启动本地开发环境
npm run serve
## 生产环境构建项目
npm run build
## 生产环境构建项目并查看构建报告
npm run build --report
```
###### 修改 .env 配置信息
```env
VUE_APP_API_BASE_URL=http://xxx.yourdomain.com
VUE_APP_WEB_SOCKET_URL=ws://xxx.yourdomain.com/socket.io
VUE_APP_WEBSITE_NAME="Lumen IM"
```
###### 关于 Nginx 的一些配置
```nginx
server {
listen 80;
server_name www.yourdomain.com;
root /project-path/dist;
index index.html;
## 解决 VueRouter History 模式下 页面刷新404问题
location / {
try_files $uri $uri/ /index.html;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico)$ {
expires 7d;
}
location ~ .*\.(js|css)?$ {
expires 7d;
}
}
```
注意:项目需要与后端一起使用,[点击获取源码](https://github.com/gzydong/hyperf-chat)。
### 项目源码
|代码仓库|前端源码|后端源码|
|-|-|-|
|Github|https://github.com/gzydong/LumenIM|https://github.com/gzydong/hyperf-chat|
|码云|https://gitee.com/gzydong/LumenIM||
#### 联系方式
QQ : 837215079
### 如果你觉得还不错,请 Star , Fork 给作者鼓励一下。
## 环境变量配置
本项目使用Vue CLI的环境变量配置功能可以在不同环境下使用不同的配置。
### 配置文件
- `.env` - 所有环境的默认配置
- `.env.development` - 开发环境配置
- `.env.test` - 测试环境配置
- `.env.production` - 生产环境配置
### 环境变量
项目中使用的主要环境变量:
- `VUE_APP_WEBSITE_NAME` - 网站名称
- `VUE_APP_API_BASE_URL` - API基础URL
- `VUE_APP_WWW_BASE_URL` - 前端网站URL
- `VUE_APP_WEB_SOCKET_URL` - WebSocket URL
### 使用方法
在代码中可以通过以下方式访问环境变量:
```js
// 直接访问
console.log(process.env.VUE_APP_API_BASE_URL)
// 通过配置文件访问
import config from '@/config/config'
console.log(config.BASE_API_URL)
```
### 运行与构建
开发环境:
```bash
# 使用开发环境配置运行
npm run serve:dev
# 使用开发环境配置构建
npm run build:dev
```
测试环境:
```bash
# 使用测试环境配置运行
npm run serve:test
# 使用测试环境配置构建
npm run build:test
```
生产环境:
```bash
# 使用生产环境配置运行
npm run serve:prod
# 使用生产环境配置构建
npm run build:prod
```
## 项目设置
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run serve
# 构建生产版本
npm run build
# 代码检查
npm run lint
```

View File

@@ -1,51 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
"import",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
],
[
"prismjs",
{
"languages": [
"html",
"css",
"less",
"javascript",
"typescript",
"json",
"xml",
"bash",
"nginx",
"sql",
"docker",
"php",
"java",
"go",
"python",
"ruby",
"rust",
"objectivec",
"c",
"csharp",
"cpp",
"lua",
"shell",
"vim",
"yaml",
"yml",
"md",
"erlang",
"ini"
],
"theme": "okaidia"
}
]
]
}

View File

@@ -1,18 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
],
"include": [
"src/**/*"
]
}

19857
Backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +0,0 @@
{
"name": "yishi-admin",
"version": "1.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"serve:dev": "vue-cli-service serve --mode development",
"serve:test": "vue-cli-service serve --mode test",
"serve:prod": "vue-cli-service serve --mode production",
"build": "vue-cli-service build",
"build:dev": "vue-cli-service build --mode development",
"build:test": "vue-cli-service build --mode test",
"build:prod": "vue-cli-service build --mode production",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"babel-plugin-prismjs": "^2.0.1",
"core-js": "^3.6.5",
"crypto-js": "^4.2.0",
"element-ui": "^2.15.6",
"js-audio-recorder": "^1.0.6",
"js-base64": "^2.5.1",
"mavon-editor": "^2.9.0",
"nprogress": "^0.2.0",
"prismjs": "^1.22.0",
"svg-sprite-loader": "^5.0.0",
"vue": "^2.6.11",
"vue-contextmenujs": "^1.3.13",
"vue-cropper": "^0.5.5",
"vue-prism-editor": "^0.5.1",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"babel-plugin-import": "^1.13.1",
"compression-webpack-plugin": "^6.1.1",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"style-resources-loader": "^1.4.1",
"vue-cli-plugin-style-resources-loader": "^0.1.4",
"vue-svg-component-runtime": "^1.0.1",
"vue-svg-icon-loader": "^2.1.1",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
<link rel="icon" href="favicon.ico">
<title>Lumen IM</title>
<style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
<!-- require cdn assets css -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
<link rel="stylesheet" href="https://at.alicdn.com/t/font_1425251_3v0kq1by4iq.css">
<link rel="stylesheet" href="https://cdn.bootcss.com/animate.css/3.7.2/animate.css">
</head>
<body>
<noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<div class="first-loading-wrp">
<div class="loading-wrp">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
</div>
</div>
<!-- require cdn assets js -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -1,130 +0,0 @@
import Vue from 'vue'
import config from '@/config/config'
var uuid = () => {
var s = [];
var hex = '0123456789abcdef'
for (var i = 0; i < 36; i++) {
s[i] = hex.substr(Math.floor(Math.random() * 0x10), 1)
}
s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hex.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = '-'
return s.join('');
}
var log = (message, contexts = {}, ex = null) => {
if (config.WS_DEBUG) {
if (typeof contexts === 'object') {
for (var k in contexts) {
message = message.replaceAll('{' + k + '}', typeof contexts[k] === 'object' ? JSON.stringify(contexts[k]) : contexts[k])
}
}
if (ex) {
console.log('[WebSocket] ' + message, ex)
} else {
console.log('[WebSocket] ' + message)
}
}
}
var callbacks = {}
var reqQueues = []
var connected = false
var websocket = null
var logininfo = {}
Vue.prototype.$wsConnect = (username, password, callback) => {
websocket = new WebSocket(config.WS_URL)
websocket.onmessage = (e) => {
log('recv: {data}', { data: e.data })
try {
var data = JSON.parse(e.data)
if (data['type'] === 'answer') {
if (callbacks[data['requestId']]) {
callbacks[data['requestId']](data['data'])
delete callbacks[data['requestId']];
}
}
} catch (ex) {
log('recv error: {data}', { data: e.data }, ex)
}
}
websocket.onopen = (e) => {
log('open: {e}', { e })
Vue.prototype.$wsReq('login', {
username: username,
password: password,
}, (ret) => {
if (typeof callback === 'function') {
callback(ret)
}
if (ret.ok) {
connected = true
logininfo = { username, password }
if (reqQueues.length > 0) {
for (let i = 0; i < reqQueues.length; i ++) {
Vue.prototype.$wsSend(reqQueues[i])
}
}
} else {
websocket.close()
log('login error: {ret}', { ret })
}
}, true)
}
websocket.onclose = (e) => {
connected = false
log('close: {e}', { e })
if (logininfo) {
log('reconnect in three seconds')
setTimeout(() => {
Vue.prototype.$wsConnect(logininfo.username, logininfo.password)
}, 3000)
}
}
websocket.onerror = (e) => {
log('error: {e}', { e })
}
}
Vue.prototype.$wsSend = (message, force = false) => {
if (connected || force) {
websocket.send(message)
} else {
reqQueues.push(message)
}
}
Vue.prototype.$wsReq = (type, data, callback, force = false) => {
var reqId = uuid();
var message = JSON.stringify(Object.assign(config.WS_REQ_PARAMS, {
type: type,
requestId: reqId,
data: data,
}))
if (typeof callback == 'function') {
callbacks[reqId] = callback
}
log('send: {message}', { message })
Vue.prototype.$wsSend(message, force)
}
Vue.prototype.$wsClose = () => {
logininfo = { username: '', password: '' }
connected = false
websocket.close()
}
/*Vue.prototype.$wsConnect('test', '123456', (ret) => {
console.log('haha', ret)
})*/

View File

@@ -1,48 +0,0 @@
<template>
<div id="app">
<router-view v-if="showView" />
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
// 用于点击当前页的router时刷新当前页
showView: true,
}
},
methods: {
// 刷新当前路由方法
refreshView() {
this.showView = false
this.$nextTick(() => (this.showView = true))
},
},
}
</script>
<style>
.image-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.image-uploader .el-upload:hover {
border-color: #409EFF;
}
.image-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 118px;
height: 118px;
line-height: 118px !important;
text-align: center;
}
.image-uploader .image {
width: 118px;
height: 118px;
display: block;
}
</style>

View File

@@ -1,130 +0,0 @@
import { post, get, upload } from '@/utils/request'
import { getToken } from '@/utils/auth'
import config from '@/config/config'
// 查询用户文集分类服务接口
export const ServeGetArticleClass = data => {
return get('/api/v1/article/article-class', data)
}
// 获取笔记表标签服务接口
export const ServeGetArticleTag = data => {
return get('/api/v1/article/article-tags', data)
}
// 查询用户文集分类服务接口
export const ServeGetArticleList = data => {
return get('/api/v1/article/article-list', data)
}
// 查询用户文集分类服务接口
export const ServeGetArticleDetail = data => {
return get('/api/v1/article/article-detail', data)
}
// 添加或编辑文集分类服务接口
export const ServeEditArticleClass = data => {
return post('/api/v1/article/edit-article-class', data)
}
// 添加或编辑笔记标签服务接口
export const ServeEditArticleTag = data => {
return post('/api/v1/article/edit-article-tag', data)
}
// 删除笔记分类服务接口
export const ServeDeleteArticleClass = data => {
return post('/api/v1/article/del-article-class', data)
}
// 删除笔记标签服务接口
export const ServeDeleteArticleTag = data => {
return post('/api/v1/article/del-article-tag', data)
}
// 笔记分类排序服务接口
export const ServeArticleClassSort = data => {
return post('/api/v1/article/article-class-sort', data)
}
// 合并笔记分类服务接口
export const ServeMergeArticleClass = data => {
return post('/api/v1/article/merge-article-class', data)
}
// 移动笔记服务接口
export const ServeMoveArticle = data => {
return post('/api/v1/article/move-article', data)
}
// 设置标记星号笔记服务接口
export const ServeSetAsteriskArticle = data => {
return post('/api/v1/article/set-asterisk-article', data)
}
// 编辑笔记服务接口
export const ServeEditArticle = data => {
return post('/api/v1/article/edit-article', data)
}
// 删除笔记服务接口
export const ServeDeleteArticle = data => {
return post('/api/v1/article/delete-article', data)
}
// 恢复笔记服务接口
export const ServeRecoverArticle = data => {
return post('/api/v1/article/recover-article', data)
}
// 笔记图片上传服务接口
export const ServeUploadArticleImg = data => {
return upload('/api/v1/article/upload-article-image', data)
}
// 笔记附件上传服务接口
export const ServeUploadArticleAnnex = data => {
return upload('/api/v1/article/upload-article-annex', data)
}
// 移除笔记附件服务接口
export const ServeDeleteArticleAnnex = data => {
return post('/api/v1/article/delete-article-annex', data)
}
// 恢复笔记附件服务接口
export const ServeRecoverArticleAnnex = data => {
return post('/api/v1/article/recover-article-annex', data)
}
// 永久删除笔记附件回收站文件
export const ServeForeverDeleteAnnex = data => {
return post('/api/v1/article/forever-delete-annex', data)
}
// 永久删除笔记回收站的笔记
export const ServeForeverDeleteArticle = data => {
return post('/api/v1/article/forever-delete-article', data)
}
// 笔记附件回收站列表服务接口
export const ServeGetRecoverAnnexList = () => {
return get('/api/v1/article/recover-annex-list')
}
// 下载笔记附件服务接口
export const ServeDownloadAnnex = annex_id => {
let api = config.BASE_API_URL
try {
let link = document.createElement('a')
link.href = `${api}/api/v1/download/article-annex?annex_id=${annex_id}&token=${getToken()}`
link.click()
} catch (e) {
console.error(e)
}
}
// 更新笔记标签服务接口
export const ServeUpdateArticleTag = data => {
return post('/api/v1/article/update-article-tag', data)
}

View File

@@ -1,17 +0,0 @@
import { post } from '@/utils/request'
export const DeviceIndex = data => {
return post('/backend/device/index', data)
}
export const DeviceAssoc = () => {
return post('/backend/device/assoc', {})
}
export const XianyuIndex = data => {
return post('/backend/xianyu/index', data)
}
export const XianyuRemark = data => {
return post('/backend/xianyu/remark', data)
}

View File

@@ -1,31 +0,0 @@
import { post, get, upload } from '@/utils/request'
// 查询用户表情包服务接口
export const ServeFindUserEmoticon = () => {
return get('/api/v1/emoticon/user-emoticon')
}
// 查询系统表情包服务接口
export const ServeFindSysEmoticon = () => {
return get('/api/v1/emoticon/system-emoticon')
}
// 设置用户表情包服务接口
export const ServeSetUserEmoticon = data => {
return post('/api/v1/emoticon/set-user-emoticon', data)
}
// 收藏表情包服务接口
export const ServeCollectEmoticon = data => {
return post('/api/v1/emoticon/collect-emoticon', data)
}
// 移除收藏表情包服务接口
export const ServeDelCollectEmoticon = data => {
return post('/api/v1/emoticon/del-collect-emoticon', data)
}
// 上传表情包服务接口
export const ServeUploadEmoticon = data => {
return upload('/api/v1/emoticon/upload-emoticon', data)
}

View File

@@ -1,61 +0,0 @@
import { post } from '@/utils/request'
export const ProductIndex = data => {
return post('/backend/product/index', data)
}
export const ProductPrice = data => {
return post('/backend/product/price', data)
}
export const ProductStock = data => {
return post('/backend/product/stock', data)
}
export const ProductTitle = data => {
return post('/backend/product/title', data)
}
export const ProductContent = data => {
return post('/backend/product/content', data)
}
export const ProductLabel = data => {
return post('/backend/product/label', data)
}
export const ProductTheme = data => {
return post('/backend/product/theme', data)
}
export const ProductSave = data => {
return post('/backend/product/save', data)
}
export const ProductDelete = id => {
return post('/backend/product/delete', { id })
}
export const ProductGroupIndex = data => {
return post('/backend/product_group/index', data)
}
export const ProductGroupSave = data => {
return post('/backend/product_group/save', data)
}
export const ProductGroupDelete = id => {
return post('/backend/product_group/delete', { id })
}
export const ProductReleaseSave = data => {
return post('/backend/product_release/save', data )
}
export const ProductContentPoolAssoc = () => {
return post('/backend/product_content_pool/assoc', { } )
}
export const ProductContentPoolSave = data => {
return post('/backend/product_content_pool/save', data )
}

View File

@@ -1,13 +0,0 @@
import { post } from '@/utils/request'
export const RoleIndex = data => {
return post('/manage/role/index', data)
}
export const RoleSave = data => {
return post('/manage/role/save', data)
}
export const RolePrivileges = data => {
return post('/manage/role/privileges', data)
}

View File

@@ -1,5 +0,0 @@
import { post } from '@/utils/request'
export const ServerIndex = data => {
return post('/manage/server/index', data)
}

View File

@@ -1,22 +0,0 @@
import { post, upload } from '@/utils/request'
export const StatisticsIndex = data => {
return post('/backend/statistics/index', data)
}
export const StatisticsSummaryUserSmall = data => {
return post('/backend/statistics_summary/userSmall', data)
}
export const StatisticsSummaryPayMoney = data => {
return post('/backend/statistics_summary/payMoney', data)
}
export const StatisticsSummarySendNum = data => {
return post('/backend/statistics_summary/sendNum', data)
}
// 导出数据
export const derive = data => {
return post('/manage/statistics/derive', data)
}

View File

@@ -1,29 +0,0 @@
import { post } from '@/utils/request'
export const TaskIndex = data => {
return post('/backend/task/index', data)
}
export const TaskLog = data => {
return post('/backend/task/log', data)
}
export const TaskSave = data => {
return post('/backend/task/save', data)
}
export const TaskDelete = id => {
return post('/backend/task/delete', { id })
}
export const TaskBatchDelete = ids => {
return post('/backend/task/batchDelete', { ids })
}
export const TaskRunTypeAssoc = () => {
return post('/backend/task/runTypeAssoc', { })
}
export const TaskMessageReplyClose = data => {
return post('/backend/message_reply/close', data)
}

View File

@@ -1,66 +0,0 @@
import { post, get } from '@/utils/request'
/**
* 用户登录
* @param {Object} data 登录数据
* @param {string} data.username 用户名
* @param {string} data.password 密码
* @param {boolean} data.is_encrypted 密码是否已加密
* @returns {Promise} 登录结果
*/
export function ServeLogin(data) {
return post('/api/auth/login', data)
}
/**
* 手机号验证码登录
* @param {Object} data 登录数据
* @param {string} data.mobile 手机号
* @param {string} data.code 验证码
* @param {boolean} data.is_encrypted 验证码是否已加密
* @returns {Promise} 登录结果
*/
export function ServeMobileLogin(data) {
return post('/api/auth/mobile-login', data)
}
// 发送验证码
export const ServeSendCode = data => {
return post('/api/auth/code', data)
}
// 获取用户信息
export const ServeGetUser = () => {
return get('/api/auth/info')
}
// 刷新token
export const ServeRefreshToken = () => {
return post('/api/auth/refresh')
}
export const ServeSetUserPassword = (data) => {
return post('/backend/user/password', data)
}
// 退出登录服务接口
export const ServeLogout = () => {
// JWT不需要服务端登出直接清除本地token即可
return Promise.resolve({ code: 200, msg: '退出成功' })
}
export const UserIndex = data => {
return post('/backend/user/index', data)
}
export const UserGetUsername = data => {
return post('/backend/user/username', data)
}
export const UserSave = data => {
return post('/backend/user/save', data)
}
export const UserPassword = data => {
return post('/backend/user/password', data)
}

View File

@@ -1,7 +0,0 @@
.tk_header{
display: flex;
align-items: center;
justify-content: space-between;
padding: 0;
}

View File

@@ -1,158 +0,0 @@
@import "./reset.css";
@import "./common.less";
.no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.no-padding {
padding: 0;
}
.no-border {
border: 0;
}
.pointer {
cursor: pointer;
}
.border-radius0 {
border-radius: 0;
}
.full-height {
height: 100%;
}
.ov-hidden {
overflow: hidden;
}
// 滚动条样式
.lum-scrollbar {
&::-webkit-scrollbar {
width: 3px;
background-color: #e4e4e5;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: #c0bebc;
}
}
.larkc-tag {
font-size: 12px;
font-weight: 400;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 6px;
height: 20px;
border-radius: 2px;
cursor: default;
user-select: none;
background-color: #dee0e3;
transform: scale(0.83);
transform-origin: left;
flex-shrink: 0;
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// 自定义 dialog 样式
// lum-dialog -- start
.lum-dialog-mask {
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100%;
height: 100%;
background-color: @maskBagColor;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
.lum-dialog-box {
min-width: 200px;
min-height: 200px;
background-color: white;
border-radius: 3px;
overflow: hidden;
box-shadow: 0 2px 8px 0 rgba(31, 35, 41, 0.2);
margin: 0 10px;
.container {
height: 100%;
}
.header {
padding: 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f5eeee;
> p:first-child {
text-indent: 20px;
}
.tools {
height: 100%;
width: 100px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 20px;
i {
font-size: 20px;
cursor: pointer;
margin-left: 8px;
}
}
}
.main {
/deep/.el-input__inner {
border-radius: 1px !important;
}
.submit-btn {
border-radius: 2px;
font-weight: 400;
}
}
}
}
// lum-dialog -- end
.talk-notify {
.el-notification__title {
font-weight: 300;
font-size: 16px;
color: #f44336;
}
p {
max-height: 65px;
overflow: hidden;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
text-indent: -7px;
word-break: break-all;
}
}

View File

@@ -1,991 +0,0 @@
.markdown-body .octicon {
display: inline-block;
fill: currentColor;
vertical-align: text-bottom;
}
.markdown-body .anchor {
float: left;
line-height: 1;
margin-left: -20px;
padding-right: 4px;
}
.markdown-body .anchor:focus {
outline: none;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #1b1f23;
vertical-align: middle;
visibility: hidden;
}
.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
text-decoration: none;
}
.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
visibility: visible;
}
.markdown-body h1:hover .anchor .octicon-link:before,
.markdown-body h2:hover .anchor .octicon-link:before,
.markdown-body h3:hover .anchor .octicon-link:before,
.markdown-body h4:hover .anchor .octicon-link:before,
.markdown-body h5:hover .anchor .octicon-link:before,
.markdown-body h6:hover .anchor .octicon-link:before {
width: 16px;
height: 16px;
content: ' ';
display: inline-block;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'%3E%3C/path%3E%3C/svg%3E");
}
.markdown-body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
font-size: 16px;
word-wrap: break-word;
font-family: Content-font, Roboto, sans-serif;
font-weight: 400;
color: #3B454E;
line-height: 1.625;
}
.markdown-body details {
display: block;
}
.markdown-body summary {
display: list-item;
}
.markdown-body a {
background-color: initial;
}
.markdown-body a:active,
.markdown-body a:hover {
outline-width: 0;
}
.markdown-body strong {
font-weight: inherit;
font-weight: bolder;
}
.markdown-body h1 {
font-size: 2em;
margin: .67em 0;
}
.markdown-body img {
border-style: none;
}
.markdown-body code,
.markdown-body kbd,
.markdown-body pre {
font-family: monospace, monospace;
font-size: 1em;
}
.markdown-body hr {
box-sizing: initial;
height: 0;
overflow: visible;
}
.markdown-body input {
font: inherit;
margin: 0;
}
.markdown-body input {
overflow: visible;
}
.markdown-body [type=checkbox] {
box-sizing: border-box;
padding: 0;
}
.markdown-body * {
box-sizing: border-box;
}
.markdown-body input {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.markdown-body a {
color: #0366d6;
text-decoration: none;
}
.markdown-body a:hover {
text-decoration: underline;
}
.markdown-body strong {
font-weight: 600;
}
.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #dfe2e5;
}
.markdown-body hr:after,
.markdown-body hr:before {
display: table;
content: "";
}
.markdown-body hr:after {
clear: both;
}
.markdown-body table {
border-spacing: 0;
border-collapse: collapse;
}
.markdown-body td,
.markdown-body th {
padding: 0;
}
.markdown-body details summary {
cursor: pointer;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: 1px solid #d1d5da;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #d1d5da;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body h1 {
font-size: 32px;
}
.markdown-body h1,
.markdown-body h2 {
font-weight: 600;
}
.markdown-body h2 {
font-size: 24px;
}
.markdown-body h3 {
font-size: 20px;
}
.markdown-body h3,
.markdown-body h4 {
font-weight: 600;
}
.markdown-body h4 {
font-size: 16px;
}
.markdown-body h5 {
font-size: 14px;
}
.markdown-body h5,
.markdown-body h6 {
font-weight: 600;
}
.markdown-body h6 {
font-size: 12px;
}
.markdown-body p {
margin-top: 0;
margin-bottom: 10px;
}
.markdown-body blockquote {
margin: 20px 0 !important;
background-color: #f5f8fc;
padding: 1rem;
color: #8796a8;
border-left: none;
}
.markdown-body ol,
.markdown-body ul {
padding-left: 0;
margin-top: 0;
margin-bottom: 0;
}
.markdown-body ol ol,
.markdown-body ul ol {
list-style-type: lower-roman;
}
.markdown-body ol ol ol,
.markdown-body ol ul ol,
.markdown-body ul ol ol,
.markdown-body ul ul ol {
list-style-type: lower-alpha;
}
.markdown-body dd {
margin-left: 0;
}
.markdown-body code,
.markdown-body pre {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
font-size: 12px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body input::-webkit-inner-spin-button,
.markdown-body input::-webkit-outer-spin-button {
margin: 0;
-webkit-appearance: none;
appearance: none;
}
.markdown-body :checked+.radio-label {
position: relative;
z-index: 1;
border-color: #0366d6;
}
.markdown-body .border {
border: 1px solid #e1e4e8 !important;
}
.markdown-body .border-0 {
border: 0 !important;
}
.markdown-body .border-bottom {
border-bottom: 1px solid #e1e4e8 !important;
}
.markdown-body .rounded-1 {
border-radius: 3px !important;
}
.markdown-body .bg-white {
background-color: #fff !important;
}
.markdown-body .bg-gray-light {
background-color: #fafbfc !important;
}
.markdown-body .text-gray-light {
color: #6a737d !important;
}
.markdown-body .mb-0 {
margin-bottom: 0 !important;
}
.markdown-body .my-2 {
margin-top: 8px !important;
margin-bottom: 8px !important;
}
.markdown-body .pl-0 {
padding-left: 0 !important;
}
.markdown-body .py-0 {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.markdown-body .pl-1 {
padding-left: 4px !important;
}
.markdown-body .pl-2 {
padding-left: 8px !important;
}
.markdown-body .py-2 {
padding-top: 8px !important;
padding-bottom: 8px !important;
}
.markdown-body .pl-3,
.markdown-body .px-3 {
padding-left: 16px !important;
}
.markdown-body .px-3 {
padding-right: 16px !important;
}
.markdown-body .pl-4 {
padding-left: 24px !important;
}
.markdown-body .pl-5 {
padding-left: 32px !important;
}
.markdown-body .pl-6 {
padding-left: 40px !important;
}
.markdown-body .f6 {
font-size: 12px !important;
}
.markdown-body .lh-condensed {
line-height: 1.25 !important;
}
.markdown-body .text-bold {
font-weight: 600 !important;
}
.markdown-body .pl-c {
color: #6a737d;
}
.markdown-body .pl-c1,
.markdown-body .pl-s .pl-v {
color: #005cc5;
}
.markdown-body .pl-e,
.markdown-body .pl-en {
color: #6f42c1;
}
.markdown-body .pl-s .pl-s1,
.markdown-body .pl-smi {
color: #24292e;
}
.markdown-body .pl-ent {
color: #22863a;
}
.markdown-body .pl-k {
color: #d73a49;
}
.markdown-body .pl-pds,
.markdown-body .pl-s,
.markdown-body .pl-s .pl-pse .pl-s1,
.markdown-body .pl-sr,
.markdown-body .pl-sr .pl-cce,
.markdown-body .pl-sr .pl-sra,
.markdown-body .pl-sr .pl-sre {
color: #032f62;
}
.markdown-body .pl-smw,
.markdown-body .pl-v {
color: #e36209;
}
.markdown-body .pl-bu {
color: #b31d28;
}
.markdown-body .pl-ii {
color: #fafbfc;
background-color: #b31d28;
}
.markdown-body .pl-c2 {
color: #fafbfc;
background-color: #d73a49;
}
.markdown-body .pl-c2:before {
content: "^M";
}
.markdown-body .pl-sr .pl-cce {
font-weight: 700;
color: #22863a;
}
.markdown-body .pl-ml {
color: #735c0f;
}
.markdown-body .pl-mh,
.markdown-body .pl-mh .pl-en,
.markdown-body .pl-ms {
font-weight: 700;
color: #005cc5;
}
.markdown-body .pl-mi {
font-style: italic;
color: #24292e;
}
.markdown-body .pl-mb {
font-weight: 700;
color: #24292e;
}
.markdown-body .pl-md {
color: #b31d28;
background-color: #ffeef0;
}
.markdown-body .pl-mi1 {
color: #22863a;
background-color: #f0fff4;
}
.markdown-body .pl-mc {
color: #e36209;
background-color: #ffebda;
}
.markdown-body .pl-mi2 {
color: #f6f8fa;
background-color: #005cc5;
}
.markdown-body .pl-mdr {
font-weight: 700;
color: #6f42c1;
}
.markdown-body .pl-ba {
color: #586069;
}
.markdown-body .pl-sg {
color: #959da5;
}
.markdown-body .pl-corl {
text-decoration: underline;
color: #032f62;
}
.markdown-body .mb-0 {
margin-bottom: 0 !important;
}
.markdown-body .my-2 {
margin-bottom: 8px !important;
}
.markdown-body .my-2 {
margin-top: 8px !important;
}
.markdown-body .pl-0 {
padding-left: 0 !important;
}
.markdown-body .py-0 {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.markdown-body .pl-1 {
padding-left: 4px !important;
}
.markdown-body .pl-2 {
padding-left: 8px !important;
}
.markdown-body .py-2 {
padding-top: 8px !important;
padding-bottom: 8px !important;
}
.markdown-body .pl-3 {
padding-left: 16px !important;
}
.markdown-body .pl-4 {
padding-left: 24px !important;
}
.markdown-body .pl-5 {
padding-left: 32px !important;
}
.markdown-body .pl-6 {
padding-left: 40px !important;
}
.markdown-body .pl-7 {
padding-left: 48px !important;
}
.markdown-body .pl-8 {
padding-left: 64px !important;
}
.markdown-body .pl-9 {
padding-left: 80px !important;
}
.markdown-body .pl-10 {
padding-left: 96px !important;
}
.markdown-body .pl-11 {
padding-left: 112px !important;
}
.markdown-body .pl-12 {
padding-left: 128px !important;
}
.markdown-body hr {
border-bottom-color: #eee;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: 1px solid #d1d5da;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #d1d5da;
}
.markdown-body:after,
.markdown-body:before {
display: table;
content: "";
}
.markdown-body:after {
clear: both;
}
.markdown-body>:first-child {
margin-top: 0 !important;
}
.markdown-body>:last-child {
margin-bottom: 0 !important;
}
.markdown-body a:not([href]) {
color: inherit;
text-decoration: none;
}
.markdown-body blockquote,
.markdown-body details,
.markdown-body dl,
.markdown-body ol,
.markdown-body p,
.markdown-body pre,
.markdown-body table,
.markdown-body ul {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body hr {
height: .25em;
padding: 0;
margin: 24px 0;
background-color: #e1e4e8;
border: 0;
}
.markdown-body blockquote {
margin: 20px 0 !important;
background-color: #f5f8fc;
padding: 1rem;
color: #8796a8;
border-left: 3px solid #03A9F4;
}
.markdown-body blockquote>:first-child {
margin-top: 0;
}
.markdown-body blockquote>:last-child {
margin-bottom: 0;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
.markdown-body h1 {
font-size: 2em;
}
.markdown-body h1,
.markdown-body h2 {
padding-bottom: .3em;
/* border-bottom: 1px solid #eaecef; */
}
.markdown-body h2 {
font-size: 1.5em;
}
.markdown-body h3 {
font-size: 1.25em;
}
.markdown-body h4 {
font-size: 1em;
}
.markdown-body h5 {
font-size: .875em;
}
.markdown-body h6 {
font-size: .85em;
color: #6a737d;
}
.markdown-body ol,
.markdown-body ul {
padding-left: 2em;
}
.markdown-body ol ol,
.markdown-body ol ul,
.markdown-body ul ol,
.markdown-body ul ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li {
word-wrap: break-all;
}
.markdown-body li>p {
margin-top: 16px;
}
.markdown-body li+li {
margin-top: .25em;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdown-body table {
display: block;
width: 100%;
overflow: auto;
}
.markdown-body table th {
font-weight: 600;
}
.markdown-body table td,
.markdown-body table th {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
.markdown-body table tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f6f8fa;
}
.markdown-body img {
max-width: 100%;
box-sizing: initial;
background-color: #fff;
}
.markdown-body img[align=right] {
padding-left: 20px;
}
.markdown-body img[align=left] {
padding-right: 20px;
}
.markdown-body code {
padding: .2em .4em;
margin: 0;
font-size: 85%;
background-color: rgba(27, 31, 35, .05);
border-radius: 3px;
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .highlight {
margin-bottom: 16px;
}
.markdown-body .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
}
.markdown-body pre code {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: initial;
border: 0;
}
.markdown-body .commit-tease-sha {
display: inline-block;
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
font-size: 90%;
color: #444d56;
}
.markdown-body .full-commit .btn-outline:not(:disabled):hover {
color: #005cc5;
border-color: #005cc5;
}
.markdown-body .blob-wrapper {
overflow-x: auto;
overflow-y: hidden;
}
.markdown-body .blob-wrapper-embedded {
max-height: 240px;
overflow-y: auto;
}
.markdown-body .blob-num {
width: 1%;
min-width: 50px;
padding-right: 10px;
padding-left: 10px;
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
font-size: 12px;
line-height: 20px;
color: rgba(27, 31, 35, .3);
text-align: right;
white-space: nowrap;
vertical-align: top;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.markdown-body .blob-num:hover {
color: rgba(27, 31, 35, .6);
}
.markdown-body .blob-num:before {
content: attr(data-line-number);
}
.markdown-body .blob-code {
position: relative;
padding-right: 10px;
padding-left: 10px;
line-height: 20px;
vertical-align: top;
}
.markdown-body .blob-code-inner {
overflow: visible;
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
font-size: 12px;
color: #24292e;
word-wrap: normal;
white-space: pre;
}
.markdown-body .pl-token.active,
.markdown-body .pl-token:hover {
cursor: pointer;
background: #ffea7f;
}
.markdown-body .tab-size[data-tab-size="1"] {
-moz-tab-size: 1;
tab-size: 1;
}
.markdown-body .tab-size[data-tab-size="2"] {
-moz-tab-size: 2;
tab-size: 2;
}
.markdown-body .tab-size[data-tab-size="3"] {
-moz-tab-size: 3;
tab-size: 3;
}
.markdown-body .tab-size[data-tab-size="4"] {
-moz-tab-size: 4;
tab-size: 4;
}
.markdown-body .tab-size[data-tab-size="5"] {
-moz-tab-size: 5;
tab-size: 5;
}
.markdown-body .tab-size[data-tab-size="6"] {
-moz-tab-size: 6;
tab-size: 6;
}
.markdown-body .tab-size[data-tab-size="7"] {
-moz-tab-size: 7;
tab-size: 7;
}
.markdown-body .tab-size[data-tab-size="8"] {
-moz-tab-size: 8;
tab-size: 8;
}
.markdown-body .tab-size[data-tab-size="9"] {
-moz-tab-size: 9;
tab-size: 9;
}
.markdown-body .tab-size[data-tab-size="10"] {
-moz-tab-size: 10;
tab-size: 10;
}
.markdown-body .tab-size[data-tab-size="11"] {
-moz-tab-size: 11;
tab-size: 11;
}
.markdown-body .tab-size[data-tab-size="12"] {
-moz-tab-size: 12;
tab-size: 12;
}
.markdown-body .task-list-item {
list-style-type: none;
}
.markdown-body .task-list-item+.task-list-item {
margin-top: 3px;
}
.markdown-body .task-list-item input {
margin: 0 .2em .25em -1.6em;
vertical-align: middle;
}

View File

@@ -1,281 +0,0 @@
.aside-box {
position: relative;
background-color: white;
border-right: 1px solid rgb(245, 245, 245);
overflow: hidden;
padding: 0;
.header {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 15px;
.from {
flex: 1 1;
flex-shrink: 0;
height: 40px;
/deep/.el-input .el-input__inner {
border-radius: 20px;
width: 170px;
}
}
.tools {
flex-basis: 32px;
flex-shrink: 0;
height: 32px;
margin-bottom: 8px;
cursor: pointer;
line-height: 32px;
text-align: center;
position: relative;
user-select: none;
.tools-menu {
position: absolute;
right: 0;
top: 38px;
width: 100px;
min-height: 80px;
box-sizing: border-box;
background-color: rgba(31, 35, 41, 0.9);
border-radius: 5px;
z-index: 1;
padding: 3px 0;
.menu1-item {
height: 40px;
line-height: 40px;
color: white;
font-size: 14px;
&:hover {
background-color: rgba(70, 72, 73, 0.9);
}
}
}
}
}
}
// 右侧面板
.panel {
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
.header {
display: flex;
align-items: center;
justify-content: space-between;
&.border{
border-bottom: 1px solid #f5f5f5;
}
}
.subheader {
display: flex;
align-items: center;
justify-content: flex-start;
border-top: 1px solid rgb(92, 156, 230);
border-bottom: 1px solid rgb(92, 156, 230);
p {
padding: 0 10px;
cursor: pointer;
font-size: 13px;
&:first-child {
padding-left: 0;
}
&.active {
color: #508afe;
}
}
}
.panel-body {
overflow: auto;
width: 100%;
height: 100%;
box-sizing: border-box;
.preloading {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
user-select: none;
p {
margin-top: 20px;
color: #afacac;
font-size: 14px;
font-weight: 300;
}
}
.data-item {
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
cursor: pointer;
padding: 5px 15px;
position: relative;
overflow: hidden;
border-bottom: 1px solid #f1ebeb;
margin-bottom: 2px;
.avatar {
height: 35px;
width: 35px;
flex-basis: 35px;
flex-shrink: 0;
background-color: #508afe;
border-radius: 50%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: white;
user-select: none;
transition: ease 1s;
position: relative;
}
.card {
height: 40px;
display: flex;
align-content: center;
flex-direction: column;
flex: 1 1;
margin-left: 10px;
overflow: hidden;
.title {
width: 100%;
height: 20px;
display: flex;
align-items: center;
.name {
margin-right: 15px;
color: #1f2329;
}
.larkc-tag {
font-size: 12px;
font-weight: 400;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 6px;
height: 20px;
border-radius: 2px;
cursor: default;
user-select: none;
background-color: #dee0e3;
transform: scale(0.8);
transform-origin: left;
flex-shrink: 0;
}
.wait {
background: #ffb445;
color: white;
}
.agree {
background: #53bd53;
color: white;
}
}
.content {
font-size: 10px;
line-height: 18px;
color: #8f959e;
overflow: hidden;
margin-top: 3px;
font-weight: 300;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.apply-from {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: relative;
right: -110px;
top: 0px;
height: 60px;
width: 100px;
transition: ease 0.5s 0.3s;
background-color: white;
opacity: 0;
button {
margin: 2px;
}
}
&:hover {
box-shadow: 0 0 8px 4px #f1f1f1;
.avatar {
border-radius: 2px;
}
.apply-from {
opacity: 1;
right: 0px;
}
}
}
}
}
.broadside-box {
position: absolute;
width: 350px;
height: 100%;
top: 0;
right: 0;
z-index: 2;
animation: showBox 0.5s ease-in-out;
-webkit-animation: showBox 0.5s ease-in-out;
-moz-animation: showBox 0.5s ease-in-out;
-webkit-box-direction: normal;
background: white;
box-shadow: 0 0 14px #cccccc70;
}
@keyframes showBox {
0% {
transform: translateX(350px);
}
to {
transform: translateX(0);
}
}
@-webkit-keyframes showBox {
0% {
-webkit-transform: translateX(350px);
}
to {
-webkit-transform: translateX(0);
}
}

View File

@@ -1,198 +0,0 @@
/deep/.el-input__inner {
border-radius: 1px !important;
}
#auth-container {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: #f6f8fb;
#logo-name {
width: 200px;
height: 38px;
font-size: 34px;
font-family: Times New Roman, Georgia, Serif;
color: #2196f3;
margin-left: 20px;
margin-top: 20px;
}
#login-box {
position: absolute;
width: 350px;
min-height: 300px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border-radius: 5px;
box-shadow: 0 0 0 #ccc;
box-shadow: 0 4px 14px 0 rgba(206, 207, 209, 0.5);
padding: 10px 20px;
.header {
width: 100%;
height: 38px;
font-size: 22px;
margin: 25px 0 20px 0;
}
.main {
width: 100%;
.links {
display: flex;
justify-content: space-between;
align-items: center;
a {
font-weight: normal !important;
}
}
.send-code-btn {
width: 140px;
height: 40px;
line-height: 40px;
display: inline-block;
background: #f3ecec;
text-align: center;
color: #777373;
cursor: pointer;
user-select: none;
margin-left: 5px;
&:active {
background: #e4dbdb;
}
}
.send-sms-disable {
cursor: not-allowed !important;
background: #f7f7f7 !important;
color: silver !important;
}
.submit-btn {
width: 100%;
border-radius: 2px;
}
}
}
}
.preview-account {
text-align: center;
p {
height: 25px;
line-height: 25px;
color: rgb(45, 44, 44);
font-weight: 100;
font-size: 12px;
}
}
.copyright {
position: absolute;
bottom: 30px;
left: 0;
right: 0;
width: 70%;
text-align: center;
margin: 0 auto;
font-size: 12px;
color: #b1a0a0;
a {
color: #777272;
font-weight: 400;
}
}
@media screen and (max-height: 500px) {
.copyright {
display: none;
}
}
.fly-box {
.fly {
pointer-events: none;
position: fixed;
z-index: 100;
}
.bg-fly-circle1 {
left: 40px;
top: 100px;
width: 100px;
height: 100px;
border-radius: 50%;
background: linear-gradient(
to right,
rgba(100, 84, 239, 0.07) 0%,
rgba(48, 33, 236, 0.04) 100%
);
animation: move 2.5s linear infinite;
}
.bg-fly-circle2 {
left: 3%;
top: 60%;
width: 150px;
height: 150px;
border-radius: 50%;
background: linear-gradient(
to right,
rgba(100, 84, 239, 0.08) 0%,
rgba(48, 33, 236, 0.04) 100%
);
animation: move 3s linear infinite;
}
.bg-fly-circle3 {
right: 2%;
top: 140px;
width: 145px;
height: 145px;
border-radius: 50%;
background: linear-gradient(
to right,
rgba(100, 84, 239, 0.1) 0%,
rgba(48, 33, 236, 0.04) 100%
);
animation: move 2.5s linear infinite;
}
.bg-fly-circle4 {
right: 5%;
top: 60%;
width: 160px;
height: 160px;
border-radius: 50%;
background: linear-gradient(
to right,
rgba(100, 84, 239, 0.02) 0%,
rgba(48, 33, 236, 0.04) 100%
);
animation: move 3.5s linear infinite;
}
}
@keyframes move {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(25px);
}
100% {
transform: translateY(0px);
}
}

View File

@@ -1,328 +0,0 @@
.note-container {
height: 100%;
/deep/.el-scrollbar__wrap {
overflow-x: hidden;
}
}
.el-aside-one {
background-color: #fff;
overflow: hidden;
.btn-header {
width: 100%;
align-items: center;
justify-content: center;
display: flex;
padding: 0;
/deep/.btn-dropdown-menu {
border-radius: 20px;
overflow: hidden;
}
/deep/button {
height: 35px;
line-height: 10px;
font-weight: 400;
&:first-child {
width: 160px !important;
}
}
}
.note-headline {
height: 40px;
line-height: 40px;
color: #353434;
padding-left: 20px;
font-size: 16px;
font-weight: 300;
}
.note-aside {
height: calc(100% - 100px) !important;
overflow-y: auto;
user-select: none;
.note-list-first {
height: 40px;
line-height: 40px;
padding-left: 20px;
cursor: pointer;
position: relative;
&:hover {
background: #f6f3f3;
}
> span {
padding-left: 5px;
font-size: 13px;
cursor: pointer;
}
.icon-menu-nav {
position: absolute;
right: 10px;
top: 1px;
}
}
.note-list-two {
height: 40px;
line-height: 40px;
padding-left: 30px;
cursor: pointer;
i {
color: #448aff;
}
span {
font-size: 12px;
cursor: pointer;
}
input {
width: 176px;
border: 1px solid #66b1ff;
height: 25px;
line-height: 25px;
margin-left: 5px;
padding: 2px 3px;
font-size: 12px;
border-radius: 3px;
}
&:hover {
color: #448aff;
}
}
.note-list-active {
background: #f6f3f3;
}
}
}
.el-aside-two {
background-color: #f4f6f9;
}
.el-aside-two .search-header {
height: 60px;
background: #f4f6f9;
text-align: center;
position: relative;
i {
position: absolute;
left: 16px;
top: 19px;
font-size: 20px;
color: #505050;
}
input {
height: 30px;
width: 80%;
position: absolute;
left: 40px;
top: 15px;
padding: 0 3px;
font-size: 14px;
background: #f4f6f9;
&::-webkit-input-placeholder {
color: rgb(158, 150, 150);
font-size: 13px;
}
}
}
.el-aside-two .title-header {
height: 40px;
line-height: 40px;
position: relative;
color: #7b7777;
border-top: 1px solid #ece8e8;
border-bottom: 1px solid #ece8e8;
user-select: none;
span {
margin-left: 10px;
font-size: 14px;
}
.load-span {
position: absolute;
right: 5px;
display: inline-block;
margin-right: 10px;
color: #6dabdc;
font-size: 12px;
.sort-icon {
cursor: pointer;
font-size: 18px;
color: #7b7777;
}
}
}
.el-aside-two .article-main {
width: 100%;
height: calc(100% - 102px);
overflow-y: auto;
overflow-x: hidden;
.note-empty {
height: 150px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 30%;
.svg-notebook {
width: 110px;
height: 122px;
font-size: 100px;
color: #ccc;
}
}
.article-row {
min-height: 50px;
border-bottom: 2px solid #ffffff;
padding: 10px 10px 5px 10px;
position: relative;
.article-title {
height: 22px;
line-height: 22px;
font-size: 14px;
width: 327px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
.article-tool {
height: 20px;
width: 56px;
position: absolute;
right: 5px;
top: 10px;
display: none;
text-align: right;
i {
cursor: pointer;
margin-left: 2px;
margin-right: 3px;
}
.el-icon-edit-outline {
color: rgb(64, 158, 255);
}
.el-icon-delete {
color: red;
}
.recover-note {
color: rgb(64, 158, 255);
}
}
.article-items {
display: flex;
align-items: center;
.article-item {
flex-grow: 1;
.article-date {
display: flex;
align-items: center;
padding-top: 4px;
font-size: 10px;
color: #a7afbc;
font-weight: 300;
line-height: 1.6;
span {
margin-right: 15px;
}
}
.article-abstract {
display: -webkit-box;
flex-grow: 1;
max-height: 42px;
font-size: 12px;
color: #989494;
line-height: 1.6;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
word-break: break-word;
overflow: hidden;
cursor: pointer;
}
&.item-image {
width: calc(100% - 56px - 16px);
}
}
.article-image {
width: 56px;
height: 56px;
flex-grow: 0;
flex-shrink: 0;
margin-left: 16px;
cursor: pointer;
/deep/.el-image {
width: 100%;
height: 100%;
}
/deep/.el-image__error {
font-size: 10px;
background-color: white;
}
img {
border-radius: 3px;
}
}
&:hover {
.article-image /deep/.el-image__error {
background: #f5f7fa;
color: #c0c4cc;
}
}
}
&:hover,
&.active-row {
background: white;
.article-title {
width: 270px;
}
.article-tool {
display: block;
}
}
}
}

View File

@@ -1,62 +0,0 @@
* {
margin: 0;
padding: 0;
}
body,
html {
height: 100%;
min-width: 500px;
font-family: "Microsoft YaHei";
font-size: 16px;
color: #333;
}
button,
input,
select,
textarea {
font-size: 100%;
margin: 0;
padding: 0;
border: none;
outline: none;
}
img {
border: 0;
}
a,
img {
-webkit-touch-callout: none
}
a {
text-decoration: none;
}
textarea {
resize: none;
outline: 0;
white-space: pre-wrap;
word-wrap: break-word;
border: none;
background: #fff;
font-family: "Microsoft YaHei";
}
:focus {
outline: none;
}
.clearfix {
clear: both;
content: "";
display: block;
overflow: hidden
}
.clear {
clear: both;
}

View File

@@ -1,50 +0,0 @@
.message-group {
min-height: 30px;
display: flex;
margin-bottom: 5px;
flex-direction: row;
padding: 3px 12px 3px 0;
&:first-child {
margin-top: 10px;
}
.left-box {
width: 50px;
flex-shrink: 0;
display: flex;
justify-content: center;
user-select: none;
padding-top: 8px;
img {
height: 30px;
width: 30px;
border-radius: 3px;
cursor: pointer;
}
}
.right-box {
flex: auto;
overflow-x: auto;
padding: 0px 5px 15px 5px;
.msg-header {
height: 30px;
line-height: 30px;
font-size: 12px;
color: #a09a9a;
position: relative;
user-select: none;
.name {
color: #333;
}
}
/deep/.text-message {
border-radius: 0;
}
}
}

View File

@@ -1,30 +0,0 @@
//主题皮肤 - 预留功能
:root {
--themeBagColor: red;
}
// 默认主题
.theme-default {
--themeBagColor: #fff;
}
// 深黑主题
.theme-dark {
--themeBagColor: black;
}
// 红色主题
.theme-red {
--themeBagColor: red;
}
// 浅蓝主题
.theme-blue {
--themeBagColor: blue;
}
// ------- 定义 Less 变量 -------
@themeBagColor: var(--themeBagColor);
// 遮罩层背景颜色
@maskBagColor: rgba(31, 35, 41, .3);

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 789 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,441 +0,0 @@
<template>
<div>
<el-container class="editor-container">
<el-header class="no-padding toolbar" height="35px">
<ul>
<li v-popover:popoverEmoticon>
<i class="iconfont icon-icon_im_face" style="font-size: 15px" />
<p class="tip-title">表情符号</p>
</li>
<li @click="$refs.restFile.click()">
<i class="el-icon-picture-outline-round" />
<p class="tip-title">图片</p>
</li>
<li @click="$refs.materialManage.show()" style="width: 56px; font-size: 13px; color: #409EFF;">
素材库
</li>
<!--<li @click="codeBlock.isShow = true">
<i class="iconfont icon-daima" />
<p class="tip-title">代码片段</p>
</li>
<li @click="recorder = true">
<i class="el-icon-headset" />
<p class="tip-title">语音消息</p>
</li>
<li @click="$refs.restFile.click()">
<i class="el-icon-picture-outline-round" />
<p class="tip-title">图片</p>
</li>
<li @click="$refs.restFile2.click()">
<i class="el-icon-folder" />
<p class="tip-title">附件</p>
</li>-->
<!--<li @click="filesManager.isShow = true">
<i class="el-icon-folder-opened" />
<p class="tip-title">上传管理</p>
</li>-->
<p class="text-tips no-select">
<span>按Enter发送 / Shift+Enter 换行</span>
<el-popover placement="top-end" width="600" trigger="click">
<div class="editor-books">
<div class="books-title">编辑说明:</div>
<p>
1.
支持上传QQ及微信截图在QQ或微信中截图后使用Ctrl+v上传图片
</p>
<p>
2.
支持浏览器及Word文档中的图片复制上传复制后使用Ctrl+v上传图片
</p>
<p>3. 支持图片拖拽上传</p>
<!--<p>4. 支持视频上传 ( 文件小于30M ) </p>-->
<p>4. 按Enter发送 / Shift+Enter 换行</p>
<p>5.注意当图片和视频正在上传时请勿关闭网页或离开当前对话框否则将导致文件停止上传或上传失败</p>
</div>
<i class="el-icon-info" slot="reference" />
</el-popover>
</p>
</ul>
<el-popover
ref="popoverEmoticon"
placement="top-start"
width="500"
trigger="click"
popper-class="no-padding"
>
<MeEditorEmoticon ref="editorEmoticon" @selected="selecteEmoticon" />
</el-popover>
<form
enctype="multipart/form-data"
style="display: none"
ref="fileFrom"
>
<input
type="file"
ref="restFile"
accept="image/*"
@change="uploadImageChange"
/>
<input type="file" ref="restFile2" @change="uploadFileChange" />
</form>
</el-header>
<el-main class="no-padding textarea">
<textarea
ref="textarea"
v-paste="pasteImage"
v-drag="dragPasteImage"
v-model.trim="editorText"
rows="6"
placeholder="你想要聊点什么呢 ..."
@keydown="keydownEvent($event)"
/>
</el-main>
</el-container>
<!-- 图片查看器 -->
<MeEditorImageView
ref="imageViewer"
v-model="imageViewer.isShow"
:file="imageViewer.file"
@confirm="confirmUploadImage"
/>
<MeEditorRecorder v-if="recorder" @close="recorder = false" />
<!-- 代码块编辑器 -->
<TalkCodeBlock
v-if="codeBlock.isShow"
:edit-mode="codeBlock.editMode"
@close="codeBlock.isShow = false"
@confirm="confirmCodeBlock"
/>
<!-- 文件上传管理器 -->
<MeEditorFileManage ref="filesManager" v-model="filesManager.isShow" />
<material-manage ref="materialManage" @confirm="confirmUploadMaterials" />
</div>
</template>
<script>
import MeEditorEmoticon from './MeEditorEmoticon'
import MeEditorFileManage from './MeEditorFileManage'
import MeEditorImageView from './MeEditorImageView'
import MeEditorRecorder from './MeEditorRecorder'
import TalkCodeBlock from '@/components/chat/TalkCodeBlock'
import { getPasteImgs, getDragPasteImg } from '@/utils/editor'
import { findTalk } from '@/utils/talk'
import MaterialManage from "@/views/message/components/MaterialManage";
export default {
name: 'MeEditor',
components: {
MaterialManage,
MeEditorEmoticon,
MeEditorFileManage,
MeEditorImageView,
TalkCodeBlock,
MeEditorRecorder,
},
computed: {
talkUser() {
return this.$store.state.dialogue.index_name
},
},
watch: {
talkUser(n_index_name) {
this.$refs.filesManager.clear()
this.editorText = this.getDraftText(n_index_name)
},
},
data() {
return {
// 当前编辑的内容
editorText: '',
// 图片查看器相关信息
imageViewer: {
isShow: false,
file: null,
},
codeBlock: {
isShow: false,
editMode: true,
},
filesManager: {
isShow: false,
},
// 录音器
recorder: false,
// 上次发送消息的时间
sendtime: 0,
// 发送间隔时间默认1秒
interval: 1000,
}
},
methods: {
setText(text) {
this.editorText = text
},
// 读取对话编辑草稿信息
getDraftText(index_name) {
return findTalk(index_name).draft_text || ''
},
//复制粘贴图片回调方法
pasteImage(e) {
let files = getPasteImgs(e)
if (files.length == 0) return
this.openImageViewer(files[0])
},
//拖拽上传图片回调方法
dragPasteImage(e) {
let files = getDragPasteImg(e)
if (files.length == 0) return
this.openImageViewer(files[0])
},
// 键盘按下监听事件
keydownEvent(e) {
if (e.keyCode == 13 && this.editorText == '') {
e.preventDefault()
}
// 回车发送消息
if (e.keyCode == 13 && e.shiftKey == false && this.editorText != '') {
this.$emit('send', this.editorText)
this.editorText = ''
e.preventDefault()
}
},
// 选择图片文件后回调方法
uploadImageChange(e) {
this.openImageViewer(e.target.files[0])
this.$refs.restFile.value = null
},
// 选择文件回调事件
uploadFileChange(e) {
let maxsize = 30 * 1024 * 1024
if (e.target.files.length == 0) {
return false
}
let file = e.target.files[0]
if (/\.(gif|jpg|jpeg|png|webp|GIF|JPG|PNG|WEBP)$/.test(file.name)) {
this.openImageViewer(file)
return
}
if (file.size > maxsize) {
this.$notify.info({
title: '消息',
message: '上传文件不能大于30M',
})
return
}
this.filesManager.isShow = true
this.$refs.restFile2.value = null
this.$refs.filesManager.upload(file)
},
// 打开图片查看器
openImageViewer(file) {
this.imageViewer.isShow = true
this.imageViewer.file = file
},
// 确认上传图片消息回调事件
confirmUploadImage() {
let fileData = new FormData()
fileData.append('image', this.imageViewer.file)
let ref1 = this.$refs.imageViewer
ref1.loading = false
ref1.closeBox()
this.$emit('send-image', fileData)
},
confirmUploadMaterials(materials) {
this.$emit('send-materials', materials)
},
// 选中表情包回调事件
selecteEmoticon(data) {
if (data.type == 1) {
let value = this.editorText
let el = this.$refs.textarea
let startPos = el.selectionStart
let endPos = el.selectionEnd
let newValue =
value.substring(0, startPos) +
data.value +
value.substring(endPos, value.length)
this.editorText = newValue
if (el.setSelectionRange) {
setTimeout(() => {
let index = startPos + data.value.length
el.setSelectionRange(index, index)
el.focus()
}, 0)
}
} else {
const { source, receive_id } = this.$store.state.dialogue
ServeSendEmoticon({
source,
receive_id,
emoticon_id: data.value,
})
}
this.$refs.popoverEmoticon.doClose()
},
},
}
</script>
<style scoped>
.editor-container {
height: 160px;
width: 100%;
background-color: white;
}
.editor-container .toolbar {
line-height: 35px;
border-bottom: 1px solid #f5f0f0;
border-top: 1px solid #f5f0f0;
}
.editor-container .toolbar li {
list-style: none;
float: left;
width: 35px;
margin-left: 3px;
cursor: pointer;
text-align: center;
line-height: 35px;
position: relative;
color: #8d8d8d;
}
.editor-container .toolbar li .tip-title {
display: none;
position: absolute;
top: 38px;
left: 0px;
height: 26px;
line-height: 26px;
background-color: rgba(31, 35, 41, 0.9);
color: white;
min-width: 30px;
font-size: 10px;
padding: 0 5px;
border-radius: 2px;
white-space: pre;
text-align: center;
user-select: none;
z-index: 1;
}
.editor-container .toolbar li:hover .tip-title {
display: block;
}
.editor-container .toolbar li:hover {
background-color: #f7f5f5;
}
.editor-container .toolbar .text-tips {
float: right;
margin-right: 15px;
font-size: 12px;
color: #ccc;
}
.editor-container .toolbar .text-tips i {
font-size: 14px;
cursor: pointer;
margin-left: 5px;
color: rgb(255, 181, 111);
}
.editor-container .textarea {
overflow: hidden;
position: relative;
}
textarea {
width: calc(100% - 10px);
width: -moz-calc(100% - 10px);
width: -webkit-calc(100% - 10px);
height: calc(100% - 10px);
height: -moz-calc(100% - 10px);
height: -webkit-calc(100% - 10px);
border: 0 none;
outline: none;
resize: none;
font-size: 15px;
overflow-y: auto;
color: #464545;
padding: 5px;
position: relative;
}
textarea::-webkit-scrollbar {
width: 4px;
height: 1px;
}
textarea::-webkit-scrollbar-thumb {
background: #d5cfcf;
}
textarea::-webkit-scrollbar-track {
background: #ededed;
}
textarea::-webkit-input-placeholder {
color: #dccdcd;
font-size: 12px;
font-weight: 400;
}
/* 编辑器文档说明 --- start */
.editor-books .books-title {
font-size: 16px;
height: 30px;
line-height: 22px;
margin-top: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #cbcbcb;
color: #726f6f;
font-weight: 400;
margin-left: 11px;
}
.editor-books p {
text-indent: 10px;
font-size: 12px;
height: 30px;
line-height: 30px;
color: #7f7c7c;
}
/* 编辑器文档说明 --- end */
</style>

View File

@@ -1,348 +0,0 @@
<template>
<div>
<el-container class="container">
<el-header height="30px" class="no-padding header">
<span v-text="showTitle"></span>
<!--<div class="addbtn" @click="systemEmojiBox = true">系统表情</div>-->
</el-header>
<el-main class="no-padding main lum-scrollbar">
<input
type="file"
ref="fileCustomEmoji"
accept="image/*"
style="display: none"
@change="customUploadEmoji"
/>
<div v-show="showEmoticonId == -1" class="emoticon">
<div class="title">QQ表情</div>
<div
v-for="(elImg, text) in emoji.emojis"
v-html="elImg"
class="emoticon-item"
@click="clickEmoticon(text)"
></div>
<div class="clear"></div>
<div class="title">符号表情</div>
<div
v-for="(item, i) in emoji.symbol"
:key="i"
class="emoticon-item symbol"
@click="clickEmoticon(item)"
>
{{ item }}
</div>
<div class="clear"></div>
</div>
<div
v-for="item in emojiItem.slice(1)"
v-show="item.emoticon_id == showEmoticonId"
:key="item.emoticon_id"
class="emoji-box"
>
<!--<div
v-if="item.emoticon_id == 0"
class="emoji-item custom-emoji"
@click="$refs.fileCustomEmoji.click()"
>
<i class="el-icon-picture" />
<span>自定义</span>
</div>-->
<div
v-for="subitem in item.list"
:key="subitem.src"
class="emoji-item"
@click="clickImageEmoticon(subitem)"
>
<el-image :src="subitem.src" fit="cover" />
</div>
<div class="clear"></div>
</div>
</el-main>
<el-footer height="40px" class="no-padding footer">
<div class="toolbar-items">
<div
v-show="emojiItem.length > 13"
class="toolbar-item prev-page"
@click="turnPage(1)"
>
<i class="el-icon-caret-left" />
</div>
<div
v-for="(item, index) in showItems"
:key="index"
class="toolbar-item"
@click="triggerItem(item)"
>
<img :src="item.url" />
<p class="title">{{ item.name }}</p>
</div>
<div
v-show="emojiItem.length > 13 && showItems.length == 13"
class="toolbar-item next-page"
@click="turnPage(2)"
>
<i class="el-icon-caret-right" />
</div>
</div>
</el-footer>
</el-container>
<MeEditorSystemEmoticon
v-if="systemEmojiBox"
@close="systemEmojiBox = false"
/>
</div>
</template>
<script>
import MeEditorSystemEmoticon from '@/components/editor/MeEditorSystemEmoticon'
import { emojiList as emoji } from '@/utils/emojis'
import { mapState } from 'vuex'
export default {
name: 'MeEditorEmoticon',
components: {
MeEditorSystemEmoticon,
},
computed: {
...mapState({
emojiItem: state => state.emoticon.items,
}),
showItems() {
let start = (this.page - 1) * this.pageSize
let end = start + this.pageSize
return this.emojiItem.slice(start, end - 1)
},
pageTotal() {
return this.emojiItem.length / this.pageSize
},
},
data() {
return {
emoji,
// 系统表情包套弹出窗
systemEmojiBox: false,
showEmoticonId: -1,
showTitle: 'QQ表情/符号表情',
page: 1,
pageSize: 13,
}
},
created() {
//this.$store.commit('LOAD_USER_EMOTICON')
},
methods: {
// 表情包导航翻页
turnPage(type) {
if (type == 1) {
if (this.page == 1) return false
this.page--
} else {
if (this.page >= this.pageTotal) return false
this.page++
}
},
// 点击表情包导航
triggerItem(item) {
this.showEmoticonId = item.emoticon_id
this.showTitle = item.name
},
// 选中表情
clickEmoticon(emoji) {
this.callback({
type: 1,
value: emoji,
})
},
// 发送图片表情包
clickImageEmoticon(item) {
this.callback({
type: 2,
value: item.media_id,
})
},
callback(data) {
this.$emit('selected', data)
},
// 自定义上传表情
customUploadEmoji(e) {
if (e.target.files.length == 0) {
return false
}
this.$store.commit('UPLOAD_USER_EMOTICON', {
file: e.target.files[0],
})
},
},
}
</script>
<style lang="less" scoped>
.container {
height: 300px;
width: 500px;
background-color: white;
.header {
line-height: 30px;
font-size: 13px;
font-weight: 400;
padding-left: 5px;
user-select: none;
position: relative;
border-bottom: 1px solid #fbf5f5;
.addbtn {
position: absolute;
right: 10px;
top: 1px;
color: #409eff;
cursor: pointer;
}
}
.footer {
background-color: #eff1f7;
.toolbar-items {
width: 100%;
height: 40px;
line-height: 40px;
display: flex;
flex-direction: row;
align-items: center;
.toolbar-item {
height: 30px;
width: 30px;
margin: 0 2px;
background-color: #fff;
display: inline-block;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
position: relative;
img {
width: 20px;
height: 20px;
}
.title {
display: none;
position: absolute;
top: -25px;
left: 0px;
height: 20px;
line-height: 20px;
background: #353434;
color: white;
min-width: 30px;
font-size: 10px;
padding-left: 5px;
padding-right: 5px;
border-radius: 2px;
white-space: pre;
text-align: center;
}
&:hover .title {
display: block;
}
}
}
}
}
.container .footer .toolbar-items .prev-page:active i,
.container .footer .toolbar-items .next-page:active i {
transform: scale(1.2);
}
.emoji-box,
.emoticon {
width: 100%;
}
.emoticon {
.title {
width: 50%;
height: 25px;
line-height: 25px;
color: #ccc;
font-weight: 400;
padding-left: 3px;
font-size: 12px;
}
.emoticon-item {
width: 30px;
height: 30px;
margin: 2px;
float: left;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
&:hover {
transform: scale(1.3);
}
}
.symbol {
font-size: 22px;
}
}
.emoji-box {
.emoji-item {
width: 67px;
height: 67px;
margin: 2px;
background-color: #eff1f7;
float: left;
cursor: pointer;
transition: ease-in 0.3s;
}
.custom-emoji {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-size: 10px;
i {
font-size: 30px;
margin-bottom: 3px;
}
&:active {
color: #409eff;
}
}
}
/deep/ .el-image {
width: 100%;
height: 100%;
transition: ease-in 0.3s;
}
.emoji-box .emoji-item:hover .el-image,
.emoji-box .emoji-item:hover {
border-radius: 10px;
}
</style>

View File

@@ -1,440 +0,0 @@
<template>
<el-container
class="container animated bounceInUp"
v-outside="closeBox"
v-if="show"
>
<el-header class="no-padding header" height="50px">
<p>
上传管理 <span v-show="total">({{ successNum }}/{{ total }})</span>
</p>
<i class="close-btn el-icon-close" @click="closeBox" />
</el-header>
<el-main class="no-padding mian lum-scrollbar">
<div class="empty-data" v-show="total == 0">
<SvgNotData />
<p>暂无上传文件</p>
</div>
<div
v-for="file in items"
v-show="!file.isDelete"
:key="file.hashName"
class="file-item"
>
<div class="file-header">
<div class="type-icon">{{ file.ext }}</div>
<el-tooltip :content="file.filename" placement="top-start">
<div class="filename">{{ file.filename }}</div>
</el-tooltip>
<div class="status">
<span v-if="file.status == 0">等待上传</span>
<span v-else-if="file.status == 1" style="color: #66b1ff">
正在上传...
</span>
<span v-else-if="file.status == 2" style="color: #67c23a">
已完成
</span>
<span v-else style="color: red">网络异常</span>
</div>
</div>
<div class="file-mian">
<div class="progress">
<el-progress
type="dashboard"
:percentage="file.progress"
:width="50"
:color="colors"
/>
<span class="name">上传进度</span>
</div>
<div class="detail">
<p>
文件类型<span>{{ file.filetype }}</span>
</p>
<p>
文件大小<span>{{ file.filesize }}</span>
</p>
<p>
上传时间<span>{{ file.datetime }}</span>
</p>
</div>
</div>
<div v-show="file.status == 2 || file.status == 3" class="file-means">
<div class="btns" @click="removeFile(file.hashName)">删除</div>
<div
v-show="file.status == 3"
class="btns"
@click="triggerUpload(file.hashName)"
>
继续上传
</div>
</div>
</div>
</el-main>
</el-container>
</template>
<script>
import Vue from 'vue'
import { SvgNotData } from '@/core/icons'
import { Progress } from 'element-ui'
Vue.use(Progress)
import { ServeFindFileSplitInfo, ServeFileSubareaUpload } from '@/api/upload'
import { formateSize, getFileExt, parseTime } from '@/utils/functions'
import { ServeSendTalkFile } from '@/api/chat'
export default {
name: 'MeEditorFileManage',
model: {
prop: 'show',
event: 'close',
},
props: {
show: Boolean,
},
components: {
SvgNotData,
},
data() {
return {
colors: [
{
color: '#f56c6c',
percentage: 20,
},
{
color: '#e6a23c',
percentage: 40,
},
{
color: '#5cb87a',
percentage: 60,
},
{
color: '#1989fa',
percentage: 80,
},
{
color: '#11ce65',
percentage: 100,
},
],
items: [],
}
},
computed: {
total() {
return this.items.filter(item => {
return item.isDelete === false
}).length
},
successNum() {
return this.items.filter(item => {
return item.isDelete === false && item.status == 2
}).length
},
},
methods: {
closeBox() {
this.$emit('close', false)
},
upload(file) {
ServeFindFileSplitInfo({
file_name: file.name,
file_size: file.size,
}).then(res => {
if (res.code == 200) {
const { hash_name, split_size } = res.data
this.items.unshift({
hashName: hash_name,
originalFile: file,
filename: file.name,
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
progress: 0,
filesize: formateSize(file.size),
filetype: file.type || '未知',
datetime: parseTime(new Date(), '{m}-{d} {h}:{i}'),
ext: getFileExt(file.name),
forms: this.fileSlice(file, hash_name, split_size),
successNum: 0,
isDelete: false,
})
this.triggerUpload(hash_name)
}
})
},
// 处理拆分上传文件
fileSlice(file, hash, eachSize) {
const ext = getFileExt(file.name)
const splitNum = Math.ceil(file.size / eachSize) // 分片总数
const forms = []
// 处理每个分片的上传操作
for (let i = 0; i < splitNum; i++) {
let start = i * eachSize
let end = Math.min(file.size, start + eachSize)
// 构建表单
const form = new FormData()
form.append('file', file.slice(start, end))
form.append('name', file.name)
form.append('hash', hash)
form.append('ext', ext)
form.append('size', file.size)
form.append('split_index', i)
form.append('split_num', splitNum)
forms.push(form)
}
return forms
},
// 触发上传文件
triggerUpload(hashName) {
let $index = this.getFileIndex(hashName)
if ($index < 0 || this.items[$index].isDelte) {
return
}
let i = this.items[$index].successNum
let form = this.items[$index].forms[i]
let length = this.items[$index].forms.length
this.items[$index].status = 1
ServeFileSubareaUpload(form)
.then(res => {
if (res.code == 200) {
$index = this.getFileIndex(hashName)
this.items[$index].successNum++
this.items[$index].progress = Math.floor(
(this.items[$index].successNum / length) * 100
)
if (this.items[$index].successNum == length) {
this.items[$index].status = 2
if (res.data.is_file_merge) {
ServeSendTalkFile({
hash_name: res.data.hash,
receive_id: this.$store.state.dialogue.receive_id,
source: this.$store.state.dialogue.source,
})
}
} else {
this.triggerUpload(hashName)
}
} else {
this.items[$index].status = 3
}
})
.catch(() => {
$index = this.getFileIndex(hashName)
this.items[$index].status = 3
})
},
// 获取分片文件数组索引
getFileIndex(hashName) {
return this.items.findIndex(item => {
return item.hashName === hashName
})
},
removeFile(hashName) {
let index = this.getFileIndex(hashName)
this.items[index].isDelete = true
},
clear() {
this.items = []
},
},
}
</script>
<style lang="less" scoped>
.container {
position: fixed;
right: 0;
bottom: 0;
width: 400px;
height: 600px;
background-color: white;
box-shadow: 0 0 5px #eae5e5;
border: 1px solid #eae5e5;
overflow: hidden;
border-radius: 3px 3px 0 0;
.header {
height: 50px;
line-height: 50px;
position: relative;
text-indent: 20px;
border-bottom: 1px solid #f5eeee;
i {
position: absolute;
right: 20px;
top: 15px;
font-size: 20px;
cursor: pointer;
}
}
.mian {
.empty-data {
width: 100%;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 50%;
svg {
font-size: 70px;
}
p {
margin-top: 30px;
color: #cccccc;
font-size: 10px;
}
}
}
}
.file-item {
width: 95%;
min-height: 100px;
background-color: white;
display: flex;
flex-direction: column;
border-radius: 5px;
margin: 15px auto;
box-shadow: 0 0 5px #eae5e5;
overflow: hidden;
.file-header {
height: 45px;
display: flex;
flex-direction: row;
align-items: center;
position: relative;
border-bottom: 1px solid #f7f4f4;
.type-icon {
height: 30px;
width: 30px;
background-color: #66b1ff;
border-radius: 50%;
margin-left: 5px;
font-size: 10px;
font-weight: 200;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
color: white;
}
.filename {
margin-left: 10px;
font-size: 14px;
width: 65%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status {
position: absolute;
right: 14px;
top: 12px;
font-size: 13px;
color: #6b6868;
font-weight: 200;
}
}
.file-mian {
padding: 8px;
display: flex;
flex-direction: row;
.progress {
width: 80px;
height: 80px;
flex-shrink: 0;
background: #f9f6f6;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
.name {
font-size: 12px;
color: #ada8a8;
font-weight: 300;
}
}
.detail {
flex: auto;
flex-shrink: 0;
display: flex;
flex-direction: column;
padding-left: 20px;
justify-content: center;
align-items: flex-start;
font-size: 13px;
p {
margin: 3px;
color: #ada8a8;
span {
color: #595a5a;
font-weight: 500;
}
}
}
}
.file-means {
width: 96.5%;
height: 35px;
border-top: 1px dashed rgb(234, 227, 227);
margin: 3px auto;
padding-top: 5px;
display: flex;
justify-content: flex-end;
align-items: center;
.btns {
width: 80px;
height: 25px;
border: 1px solid #e6e1e1;
display: flex;
justify-content: center;
align-items: center;
margin: 3px;
border-radius: 15px;
font-size: 12px;
color: #635f5f;
cursor: pointer;
&:active {
box-shadow: 0 0 5px #eae5e5;
font-size: 13px;
}
}
}
}
</style>

View File

@@ -1,133 +0,0 @@
<template>
<div v-if="show" class="lum-dialog-mask animated fadeIn">
<el-container class="lum-dialog-box" v-outside="closeBox">
<el-header class="no-padding header" height="50px">
<p>发送图片</p>
<p class="tools">
<i class="el-icon-close" @click="closeBox" />
</p>
</el-header>
<el-main class="no-padding mian">
<img v-show="src" :src="src" />
<div v-show="src">
<span class="filename">{{ fileName }}</span>
<br />
<span class="size">{{ fileSize }} KB</span>
</div>
</el-main>
<el-footer class="footer" height="50px">
<el-button
class="btn"
type="primary"
size="medium"
:loading="loading"
@click="uploadImage"
>立即发送
</el-button>
</el-footer>
</el-container>
</div>
</template>
<script>
export default {
name: 'MeEditorImageView',
model: {
prop: 'show',
event: 'close',
},
props: {
show: Boolean,
file: File,
},
watch: {
file(file) {
this.loadFile(file)
},
},
data() {
return {
src: '',
fileSize: '',
fileName: '',
loading: false,
}
},
methods: {
closeBox() {
if (this.loading) {
return false
}
this.$emit('close', false)
},
loadFile(file) {
let reader = new FileReader()
this.fileSize = Math.ceil(file.size / 1024)
this.fileName = file.name
reader.onload = () => {
this.src = reader.result
}
reader.readAsDataURL(file)
},
// 确认按钮事件
uploadImage() {
this.loading = true
this.$emit('confirm')
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 500px;
max-width: 500px;
height: 450px;
.mian {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
img {
max-width: 80%;
max-height: 80%;
border-radius: 3px;
cursor: pointer;
box-shadow: 0 0 8px #e0dbdb;
}
div {
margin-top: 10px;
text-align: center;
overflow: hidden;
max-width: 80%;
.filename {
font-weight: 400;
}
.size {
color: rgb(148, 140, 140);
font-size: 12px;
}
}
}
.footer {
height: 50px;
background: rgba(247, 245, 245, 0.66);
text-align: center;
line-height: 50px;
.btn {
width: 150px;
border-radius: 2px;
}
}
}
</style>

View File

@@ -1,518 +0,0 @@
<template>
<div class="lum-dialog-mask animated fadeIn">
<el-container class="lum-dialog-box">
<el-header class="no-padding header no-select" height="50px">
<p>语音消息</p>
<p class="tools"><i class="el-icon-close" @click="closeBox" /></p>
</el-header>
<el-main class="no-padding mian">
<div class="music">
<span class="line line1" :class="{ 'line-ani': animation }"></span>
<span class="line line2" :class="{ 'line-ani': animation }"></span>
<span class="line line3" :class="{ 'line-ani': animation }"></span>
<span class="line line4" :class="{ 'line-ani': animation }"></span>
<span class="line line5" :class="{ 'line-ani': animation }"></span>
</div>
<div style="margin-top: 35px; color: #676262; font-weight: 300">
<template v-if="recorderStatus == 0">
<p style="font-size: 13px; margin-top: 5px">
<span>语音消息让聊天更简单方便 ...</span>
</p>
</template>
<template
v-else-if="
recorderStatus == 1 || recorderStatus == 2 || recorderStatus == 3
"
>
<p>{{ datetime }}</p>
<p style="font-size: 13px; margin-top: 5px">
<span v-if="recorderStatus == 1">正在录音</span>
<span v-else-if="recorderStatus == 2">已暂停录音</span>
<span v-else-if="recorderStatus == 3">录音时长</span>
</p>
</template>
<template
v-else-if="
recorderStatus == 4 || recorderStatus == 5 || recorderStatus == 6
"
>
<p>{{ formatPlayTime }}</p>
<p style="font-size: 13px; margin-top: 5px">
<span v-if="recorderStatus == 4">正在播放</span>
<span v-else-if="recorderStatus == 5">已暂停播放</span>
<span v-else-if="recorderStatus == 6">播放已结束</span>
</p>
</template>
</div>
</el-main>
<el-footer class="footer" height="50px">
<!-- 0:未开始录音 1:正在录音 2:暂停录音 3:结束录音 4:播放录音 5:停止播放 -->
<el-button
v-show="recorderStatus == 0"
type="primary"
size="mini"
round
icon="el-icon-microphone"
@click="startRecorder"
>开始录音
</el-button>
<el-button
v-show="recorderStatus == 1"
type="primary"
size="mini"
round
icon="el-icon-video-pause"
@click="pauseRecorder"
>暂停录音
</el-button>
<el-button
v-show="recorderStatus == 2"
type="primary"
size="mini"
round
icon="el-icon-microphone"
@click="resumeRecorder"
>继续录音
</el-button>
<el-button
v-show="recorderStatus == 2"
type="primary"
size="mini"
round
icon="el-icon-microphone"
@click="stopRecorder"
>结束录音
</el-button>
<el-button
v-show="recorderStatus == 3 || recorderStatus == 6"
type="primary"
size="mini"
round
icon="el-icon-video-play"
@click="playRecorder"
>播放录音
</el-button>
<el-button
v-show="
recorderStatus == 3 || recorderStatus == 5 || recorderStatus == 6
"
type="primary"
size="mini"
round
icon="el-icon-video-play"
@click="startRecorder"
>重新录音
</el-button>
<el-button
v-show="recorderStatus == 4"
type="primary"
size="mini"
round
icon="el-icon-video-pause"
@click="pausePlayRecorder"
>暂停播放
</el-button>
<el-button
v-show="recorderStatus == 5"
type="primary"
size="mini"
round
icon="el-icon-video-play"
@click="resumePlayRecorder"
>继续播放
</el-button>
<el-button
v-show="
recorderStatus == 3 || recorderStatus == 5 || recorderStatus == 6
"
type="primary"
size="mini"
round
@click="submit"
>立即发送
</el-button>
</el-footer>
</el-container>
</div>
</template>
<script>
import Recorder from 'js-audio-recorder'
export default {
name: 'MeEditorRecorder',
data() {
return {
// 录音实例
recorder: null,
// 录音时长
duration: 0,
// 播放时长
playTime: 0,
animation: false,
// 当前状态
recorderStatus: 0, //0:未开始录音 1:正在录音 2:暂停录音 3:结束录音 4:播放录音 5:停止播放 6:播放结束
playTimeout: null,
}
},
computed: {
datetime() {
let hour = parseInt((this.duration / 60 / 60) % 24) //小时
let minute = parseInt((this.duration / 60) % 60) //分钟
let seconds = parseInt(this.duration % 60) //秒
if (hour < 10) hour = `0${hour}`
if (minute < 10) minute = `0${minute}`
if (seconds < 10) seconds = `0${seconds}`
return `${hour}:${minute}:${seconds}`
},
formatPlayTime() {
let hour = parseInt((this.playTime / 60 / 60) % 24) //小时
let minute = parseInt((this.playTime / 60) % 60) //分钟
let seconds = parseInt(this.playTime % 60) //秒
if (hour < 10) hour = `0${hour}`
if (minute < 10) minute = `0${minute}`
if (seconds < 10) seconds = `0${seconds}`
return `${hour}:${minute}:${seconds}`
},
},
destroyed() {
if (this.recorder) {
this.destroyRecorder()
}
},
methods: {
closeBox() {
if (this.recorder == null) {
this.$emit('close', false)
return
}
if (this.recorderStatus == 1) {
this.stopRecorder()
} else if (this.recorderStatus == 4) {
this.pausePlayRecorder()
}
// 销毁录音后关闭窗口
this.destroyRecorder(() => {
this.$emit('close', false)
})
},
// 开始录音
startRecorder() {
let _this = this
// http://recorder.api.zhuyuntao.cn/Recorder/event.html
// https://blog.csdn.net/qq_41619796/article/details/107865602
this.recorder = new Recorder()
this.recorder.onprocess = duration => {
duration = parseInt(duration)
_this.duration = duration
}
this.recorder.start().then(
() => {
this.recorderStatus = 1
this.animation = true
},
error => {
console.log(`${error.name} : ${error.message}`)
}
)
},
// 暂停录音
pauseRecorder() {
this.recorder.pause()
this.recorderStatus = 2
this.animation = false
},
// 继续录音
resumeRecorder() {
this.recorderStatus = 1
this.recorder.resume()
this.animation = true
},
// 结束录音
stopRecorder() {
this.recorderStatus = 3
this.recorder.stop()
this.animation = false
},
// 录音播放
playRecorder() {
this.recorderStatus = 4
this.recorder.play()
this.playTimeouts()
this.animation = true
},
// 暂停录音播放
pausePlayRecorder() {
this.recorderStatus = 5
this.recorder.pausePlay()
clearInterval(this.playTimeout)
this.animation = false
},
// 恢复录音播放
resumePlayRecorder() {
this.recorderStatus = 4
this.recorder.resumePlay()
this.playTimeouts()
this.animation = true
},
// 销毁录音
destroyRecorder(callBack) {
this.recorder.destroy().then(() => {
this.recorder = null
console.log('销毁了...')
if (callBack) {
callBack()
}
})
},
// 获取录音文件大小(单位:字节)
recorderSize() {
return this.recorder.fileSize
},
playTimeouts() {
this.playTimeout = setInterval(() => {
let time = parseInt(this.recorder.getPlayTime())
this.playTime = time
if (time == this.duration) {
clearInterval(this.playTimeout)
this.animation = false
this.recorderStatus = 6
}
}, 100)
},
submit() {
alert('功能研发中,敬请期待...')
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 500px;
max-width: 500px;
height: 450px;
.mian {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.footer {
height: 50px;
text-align: center;
line-height: 50px;
border-top: 1px solid #f7f3f3;
}
}
.music {
position: relative;
width: 180px;
height: 160px;
border: 8px solid #bebebe;
border-bottom: 0px;
border-top-left-radius: 110px;
border-top-right-radius: 110px;
}
.music:before,
.music:after {
content: '';
position: absolute;
bottom: -20px;
width: 40px;
height: 82px;
background-color: #bebebe;
border-radius: 15px;
}
.music:before {
right: -25px;
}
.music:after {
left: -25px;
}
.line {
position: absolute;
width: 6px;
min-height: 30px;
transition: 0.5s;
vertical-align: middle;
bottom: 0 !important;
box-shadow: inset 0px 0px 16px -2px rgba(0, 0, 0, 0.15);
}
.line-ani {
animation: equalize 4s 0s infinite;
animation-timing-function: linear;
}
.line1 {
left: 30%;
bottom: 0px;
animation-delay: -1.9s;
background-color: #ff5e50;
}
.line2 {
left: 40%;
height: 60px;
bottom: -15px;
animation-delay: -2.9s;
background-color: #a64de6;
}
.line3 {
left: 50%;
height: 30px;
bottom: -1.5px;
animation-delay: -3.9s;
background-color: #5968dc;
}
.line4 {
left: 60%;
height: 65px;
bottom: -16px;
animation-delay: -4.9s;
background-color: #27c8f8;
}
.line5 {
left: 70%;
height: 60px;
bottom: -12px;
animation-delay: -5.9s;
background-color: #cc60b5;
}
@keyframes equalize {
0% {
height: 48px;
}
4% {
height: 42px;
}
8% {
height: 40px;
}
12% {
height: 30px;
}
16% {
height: 20px;
}
20% {
height: 30px;
}
24% {
height: 40px;
}
28% {
height: 10px;
}
32% {
height: 40px;
}
36% {
height: 48px;
}
40% {
height: 20px;
}
44% {
height: 40px;
}
48% {
height: 48px;
}
52% {
height: 30px;
}
56% {
height: 10px;
}
60% {
height: 30px;
}
64% {
height: 48px;
}
68% {
height: 30px;
}
72% {
height: 48px;
}
76% {
height: 20px;
}
80% {
height: 48px;
}
84% {
height: 38px;
}
88% {
height: 48px;
}
92% {
height: 20px;
}
96% {
height: 48px;
}
100% {
height: 48px;
}
}
</style>

View File

@@ -1,169 +0,0 @@
<template>
<div class="lum-dialog-mask">
<el-container class="lum-dialog-box" v-outside="closeBox">
<el-header class="no-padding header" height="50px">
<p>系统表情</p>
<p class="tools">
<i class="el-icon-close" @click="closeBox" />
</p>
</el-header>
<el-main class="no-padding mian lum-scrollbar">
<ul>
<li v-for="(item, i) in items" :key="item.id" class="no-select">
<div class="pkg-avatar">
<el-image :src="item.url" fit="cover" :lazy="true" />
</div>
<div class="pkg-info" v-text="item.name"></div>
<div class="pkg-status">
<button
:class="{
'add-emoji': item.status == 0,
'remove-emoji': item.status != 0,
}"
@click="setEmoticon(i, item, item.status == 0 ? 1 : 2)"
>
{{ item.status == 0 ? '添加' : '移除' }}
</button>
</div>
</li>
</ul>
</el-main>
<el-footer class="footer" height="50px">
<el-button type="primary" size="medium" class="btn" @click="closeBox">
关闭窗口
</el-button>
</el-footer>
</el-container>
</div>
</template>
<script>
import { ServeFindSysEmoticon, ServeSetUserEmoticon } from '@/api/emoticon'
export default {
name: 'MeEditorSystemEmoticon',
data() {
return {
items: [],
}
},
created() {
this.loadSysEmoticon()
},
methods: {
closeBox() {
this.$emit('close')
},
// 获取系统表情包列表
loadSysEmoticon() {
ServeFindSysEmoticon().then(res => {
if (res.code == 200) {
this.items = res.data
}
})
},
setEmoticon(index, item, type) {
ServeSetUserEmoticon({
emoticon_id: item.id,
type: type,
}).then(res => {
if (res.code == 200) {
if (type == 1) {
this.items[index].status = 1
this.$store.commit('APPEND_SYS_EMOTICON', res.data)
} else {
this.items[index].status = 0
this.$store.commit('REMOVE_SYS_EMOTICON', {
emoticon_id: item.id,
})
}
}
})
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 350px;
max-width: 350px;
height: 500px;
.mian {
height: 480px;
overflow-y: auto;
li {
display: flex;
cursor: pointer;
height: 68px;
align-items: center;
border-bottom: 3px solid #fbf2fb;
padding-left: 5px;
.pkg-avatar {
flex-shrink: 0;
.el-image {
width: 50px;
height: 50px;
border-radius: 3px;
}
}
.pkg-info {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 14px;
margin-right: 14px;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
width: 200px;
color: #615d5d;
font-size: 13px;
}
.pkg-status {
flex-shrink: 0;
button {
font-size: 12px;
text-align: center;
line-height: 28px;
border-radius: 20px;
width: 50px;
cursor: pointer;
color: white;
}
.add-emoji {
background-color: #38adff;
}
.remove-emoji {
background-color: #ff5722;
}
}
}
}
.footer {
height: 50px;
background: rgba(247, 245, 245, 0.66);
text-align: center;
line-height: 50px;
.btn {
width: 150px;
border-radius: 2px;
}
}
}
</style>

View File

@@ -1,49 +0,0 @@
<template>
<div class="empty-content">
<div class="image">
<img :src="src" />
</div>
<div class="text" v-text="text" />
</div>
</template>
<script>
export default {
name: 'Empty',
props: {
text: {
type: String,
default: '数据为空...',
},
src: {
type: String,
default: require('@/assets/image/no-oncall.6b776fcf.png'),
},
},
data() {
return {}
},
created() {},
methods: {},
}
</script>
<style lang="less" scoped>
.empty-content {
width: 100%;
height: 60%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 13px;
.image {
width: 200px;
height: 200px;
img {
width: 100%;
}
}
}
</style>

View File

@@ -1,313 +0,0 @@
<template>
<div class="loading-content">
<div class="ant-spin ant-spin-lg ant-spin-spinning">
<span class="ant-spin-dot ant-spin-dot-spin">
<i class="ant-spin-dot-item" />
<i class="ant-spin-dot-item" />
<i class="ant-spin-dot-item" />
<i class="ant-spin-dot-item" />
</span>
</div>
<p>{{ text }}</p>
</div>
</template>
<script>
export default {
name: 'Loading',
props: {
text: {
type: String,
default: '数据加载中 ...',
},
},
}
</script>
<style lang="less" scoped>
.loading-content {
width: 100%;
height: 60%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 13px;
p {
margin-top: 10px;
}
}
/* ant-spin 加载动画 start */
.ant-spin {
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
-webkit-font-feature-settings: 'tnum';
font-feature-settings: 'tnum';
position: absolute;
display: none;
color: #1890ff;
text-align: center;
vertical-align: middle;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-nested-loading {
position: relative;
}
.ant-spin-nested-loading > div > .ant-spin {
position: absolute;
top: 0;
left: 0;
z-index: 4;
display: block;
width: 100%;
height: 100%;
max-height: 400px;
}
.ant-spin-nested-loading > div > .ant-spin .ant-spin-dot {
position: absolute;
top: 50%;
left: 50%;
margin: -10px;
}
.ant-spin-nested-loading > div > .ant-spin .ant-spin-text {
position: absolute;
top: 50%;
width: 100%;
padding-top: 5px;
text-shadow: 0 1px 2px #fff;
}
.ant-spin-nested-loading > div > .ant-spin.ant-spin-show-text .ant-spin-dot {
margin-top: -20px;
}
.ant-spin-nested-loading > div > .ant-spin-sm .ant-spin-dot {
margin: -7px;
}
.ant-spin-nested-loading > div > .ant-spin-sm .ant-spin-text {
padding-top: 2px;
}
.ant-spin-nested-loading > div > .ant-spin-sm.ant-spin-show-text .ant-spin-dot {
margin-top: -17px;
}
.ant-spin-nested-loading > div > .ant-spin-lg .ant-spin-dot {
margin: -16px;
}
.ant-spin-nested-loading > div > .ant-spin-lg .ant-spin-text {
padding-top: 11px;
}
.ant-spin-nested-loading > div > .ant-spin-lg.ant-spin-show-text .ant-spin-dot {
margin-top: -26px;
}
.ant-spin-container {
position: relative;
-webkit-transition: opacity 0.3s;
transition: opacity 0.3s;
}
.ant-spin-container::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
display: none \9;
width: 100%;
height: 100%;
background: #fff;
opacity: 0;
-webkit-transition: all 0.3s;
transition: all 0.3s;
content: '';
pointer-events: none;
}
.ant-spin-blur {
clear: both;
overflow: hidden;
opacity: 0.5;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}
.ant-spin-blur::after {
opacity: 0.4;
pointer-events: auto;
}
.ant-spin-tip {
color: rgba(0, 0, 0, 0.45);
}
.ant-spin-dot {
position: relative;
display: inline-block;
font-size: 20px;
width: 1em;
height: 1em;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antSpinMove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antRotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-sm .ant-spin-dot {
font-size: 14px;
}
.ant-spin-sm .ant-spin-dot i {
width: 6px;
height: 6px;
}
.ant-spin-lg .ant-spin-dot {
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
.ant-spin.ant-spin-show-text .ant-spin-text {
display: block;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
.ant-spin-rtl {
direction: rtl;
}
.ant-spin-rtl .ant-spin-dot-spin {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-animation-name: antRotateRtl;
animation-name: antRotateRtl;
}
@-webkit-keyframes antRotateRtl {
to {
-webkit-transform: rotate(-405deg);
transform: rotate(-405deg);
}
}
@keyframes antRotateRtl {
to {
-webkit-transform: rotate(-405deg);
transform: rotate(-405deg);
}
}
/* ant-spin 加载动画 end */
</style>

View File

@@ -1,448 +0,0 @@
<template>
<div class="lum-dialog-mask">
<el-container class="lum-dialog-box" v-outside="close">
<el-header class="no-padding header no-select" height="60px">
<p>
{{ from.groupId == 0 ? '创建群组' : '请选择需要邀请的好友' }}
</p>
<p class="tools">
<i class="el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main no-padding">
<el-container class="full-height">
<el-aside width="250px" class="aside-border">
<el-container class="full-height no-select">
<el-header
class="no-padding search-header"
height="50px"
:class="{ shadow: searchHeaderShadow }"
>
<el-input
v-model="keywords"
placeholder="搜索 | 好友 or 群组"
prefix-icon="el-icon-search"
clearable
size="small"
/>
</el-header>
<el-main class="no-padding">
<el-scrollbar
ref="scrollbar"
class="full-height"
tag="section"
:native="false"
>
<ul class="friend-items no-select">
<li
v-for="item in search"
:key="item.id"
@click="triggerContacts(item)"
>
<el-avatar
class="avatar"
style="margin-top: 5px"
:size="25"
:src="item.avatar"
>
<img src="~@/assets/image/detault-avatar.jpg" />
</el-avatar>
<span class="nickname">{{ item.nickname }}</span>
<span class="select-btn">
<i
class="el-icon-success"
:class="{ 'icon-active': item.checked }"
/>
</span>
</li>
</ul>
</el-scrollbar>
</el-main>
</el-container>
</el-aside>
<el-main class="no-padding">
<el-container class="full-height">
<el-header height="50px" v-show="!readonly">
<div class="group-from no-select">
<label>群名称</label>
<p>
<el-input
v-model="from.groupName"
placeholder="请输入群名称(必填)"
size="small"
:maxlength="20"
/>
</p>
</div>
</el-header>
<el-header height="40px" :class="{ mt40: !readonly }">
<el-divider content-position="left" class="no-select">
<span style="color: #c4c5c7">
邀请成员 ({{ selected.length }})
</span>
</el-divider>
</el-header>
<el-main>
<el-scrollbar :native="false" tag="section" class="full-height">
<div class="selectd-items">
<div
v-for="item in selected"
:key="item.id"
class="selectd-item no-select"
>
<el-avatar :size="25" :src="item.avatar" />
<p>{{ item.nickname }}</p>
<div class="triangle-topleft"></div>
<div class="del-mask" @click="delContacts(item)">
<i class="el-icon-delete" />
</div>
</div>
</div>
</el-scrollbar>
</el-main>
</el-container>
</el-main>
</el-container>
</el-main>
<el-footer height="50px" class="no-padding footer">
<el-button size="small" @click="close" plain>取消</el-button>
<el-button
v-if="from.groupId == 0"
type="primary"
size="small"
@click="createSubmit"
>
创建群组<span v-show="selected.length">({{ selected.length }})</span>
</el-button>
<el-button v-else type="primary" size="small" @click="inviteSubmit">
立即邀请({{ selected.length }})
</el-button>
</el-footer>
</el-container>
</div>
</template>
<script>
import {
ServeCreateGroup,
ServeInviteGroup,
ServeGetInviteFriends,
} from '@/api/group'
export default {
name: 'GroupLaunch',
props: {
groupId: {
type: [String, Number],
default: null,
},
},
data() {
return {
readonly: false,
from: {
groupId: 0,
groupName: '',
},
contacts: [],
search: [],
searchHeaderShadow: false,
keywords: '',
isAvatarCropper: false,
}
},
computed: {
selected() {
return this.contacts.filter(item => item.checked)
},
},
watch: {
keywords(val) {
this.search =
val == ''
? this.contacts
: this.contacts.filter(item => {
return item.nickname.match(this.keywords) != null
})
},
contacts(arr) {
if (this.keywords == '') {
this.search = arr
}
},
},
created() {
if (this.groupId > 0) {
this.readonly = true
this.from.groupId = this.groupId
}
this.friendsApi()
},
mounted() {
this.handleScroll()
},
methods: {
// 触发选择联系人事件
triggerContacts(item) {
let index = this.contacts.findIndex(val => {
return val.id === item.id
})
this.contacts[index].checked = !this.contacts[index].checked
},
// 取消选中的联系人
delContacts(item) {
let index = this.contacts.findIndex(val => {
return val.id === item.id
})
this.contacts[index].checked = false
},
// 移除所有选中选项
delAll() {
this.contacts.forEach((item, i) => {
this.contacts[i].checked = false
})
},
// 关闭窗口
close() {
this.$emit('close')
},
// 获取选中的ID列表
getIds() {
return this.selected.map(item => item.id)
},
// 加载好友列表
friendsApi() {
ServeGetInviteFriends({
group_id: this.from.groupId,
}).then(res => {
if (res.code == 200 && res.data) {
this.contacts = []
let data = res.data.map(item => {
return Object.assign(item, {
nickname: item.friend_remark ? item.friend_remark : item.nickname,
checked: false,
})
})
this.contacts.push(...data)
}
})
},
//创建聊天群
createSubmit() {
let data = {
group_avatar: '',
group_name: this.from.groupName,
group_profile: '',
uids: this.getIds().join(','),
}
if (data.group_name == '') {
this.$message('群聊名称不能为空!')
return
}
if (this.getIds().length < 2) {
this.$message('群聊人数必须大于俩人!')
return
}
ServeCreateGroup(data).then(res => {
if (res.code == 200) {
this.$emit('create-success', res.data)
} else {
this.$message('创建群聊失败!')
}
})
},
//好友邀请提交
inviteSubmit() {
ServeInviteGroup({
group_id: this.from.groupId,
uids: this.getIds().join(','),
}).then(res => {
if (res.code == 200) {
this.$emit('invite-success')
} else {
this.$message('邀请好友失败!')
}
})
},
// 滚动条监听
handleScroll() {
let scrollbarEl = this.$refs.scrollbar.wrap
scrollbarEl.onscroll = () => {
this.searchHeaderShadow = scrollbarEl.scrollTop != 0
}
},
},
}
</script>
<style lang="less" scoped>
/deep/.el-scrollbar__wrap {
overflow-x: hidden;
}
.lum-dialog-box {
width: 650px;
max-width: 650px;
height: 550px;
.main {
.aside-border {
border-right: 1px solid #f5eeee;
}
}
.footer {
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 10px;
border-top: 1px solid #f5eeee;
}
}
.search-header {
padding: 8px;
display: flex;
justify-content: center;
align-items: center;
&.shadow {
box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.05);
}
}
.friend-items {
li {
padding: 10px;
cursor: pointer;
position: relative;
&:hover {
background: #f5f5f5;
}
.avatar {
margin-top: 3px;
}
.nickname {
width: 60%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
position: absolute;
top: 10px;
left: 52px;
height: 35px;
line-height: 35px;
font-weight: 400;
font-size: 13px;
}
.select-btn {
position: absolute;
width: 32px;
height: 35px;
right: 3px;
top: 10px;
line-height: 35px;
text-align: center;
i {
color: #ccc;
&.icon-active {
color: #26bcfe !important;
}
}
}
}
}
.group-from {
label {
height: 45px;
line-height: 47px;
width: 50px;
color: #606266;
padding-right: 3px;
font-size: 13px;
}
input {
height: 25px;
width: 100%;
text-indent: 3px;
color: #a9a4a4;
font-size: 12px;
border-bottom: 1px solid #efebeb;
}
}
.selectd-items {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
.selectd-item {
width: 23%;
height: 65px;
margin: 6px 2px 0px 2px;
cursor: pointer;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
box-shadow: 0px 0px 3px 0 rgba(0, 0, 0, 0.1);
p {
width: 90%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 10px;
margin-top: 8px;
text-align: center;
}
.del-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(31, 35, 41, 0.5);
display: none;
justify-content: center;
align-items: center;
color: white;
transition: ease 0.5s;
border-radius: 2px;
}
&:hover .del-mask {
display: flex;
}
}
}
.mt40 {
margin-top: 40px;
}
</style>

View File

@@ -1,926 +0,0 @@
<template>
<div class="lum-dialog-mask">
<div class="lum-dialog-box">
<el-container class="container">
<el-header class="header no-select" height="60px">
<p>群管理 ({{ detail.group_name }})</p>
<p class="tools">
<i class="el-icon-close" @click="$emit('close')" />
</p>
</el-header>
<el-main class="main no-padding">
<el-container class="full-height">
<el-aside width="100px" class="aside-border no-select">
<div
v-for="(menu, index) in menus"
:key="menu.name"
class="menu-list"
:class="{ selectd: tabIndex == index }"
v-text="menu.name"
@click="triggerTab(index)"
/>
</el-aside>
<!-- 群介绍模块 -->
<el-main v-if="tabIndex == 0">
<el-row>
<el-col :span="14">
<el-form ref="groupForm" :model="form" :rules="rules">
<el-form-item label="群名:" prop="group_name">
<el-input
v-model="form.group_name"
size="medium"
placeholder="请输入群名称"
maxlength="30"
show-word-limit
/>
</el-form-item>
<el-form-item label="群描述:">
<el-input
v-model="form.profile"
type="textarea"
rows="3"
placeholder="请输入群描述"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-edit"
size="small"
:loading="loading"
@click="editGroup"
>修改信息
</el-button>
</el-form-item>
</el-form>
</el-col>
<el-col :span="10" class="avatar-col">
<div class="avatar-box">
<img v-show="form.avatar" :src="form.avatar" />
<div class="upload-icon">
<i class="el-icon-upload" />
</div>
<div class="upload-mask" @click="isAvatarCropper = true">
<i class="el-icon-plus" />
</div>
</div>
<p style="margin-top: 20px">设置头像</p>
</el-col>
</el-row>
</el-main>
<!-- 群成员模块 -->
<el-main v-else-if="tabIndex == 1" class="no-padding">
<el-container class="full-height">
<el-header height="50px" class="notice-header">
<el-input
v-model="searchMembers"
style="width: 200px"
size="small"
clearable
prefix-icon="el-icon-search"
:placeholder="`搜索群成员 ( 共${members.length}人 )`"
/>
<p>
<el-button
plain
size="small"
icon="el-icon-plus"
@click="inviteFriendBox = true"
>邀请好友
</el-button>
<el-button
v-if="batchDelMember"
type="danger"
size="small"
icon="el-icon-delete"
@click="deleteMembers"
>确认删除
</el-button>
<el-button
v-else
plain
size="small"
icon="el-icon-finished"
@click="batchDelMember = true"
>批量操作
</el-button>
</p>
</el-header>
<el-main class="no-padding">
<el-scrollbar
class="full-height"
tag="section"
:native="false"
>
<div class="members">
<div
v-for="member in filterMembers"
class="member no-select"
:class="{
selectd: member.is_delete && batchDelMember,
}"
:key="member.user_id"
>
<div class="item-header">
<div class="avatar" @click="catUserDetail(member)">
<el-avatar :size="30" :src="member.avatar">
<img src="~@/assets/image/detault-avatar.jpg" />
</el-avatar>
<span class="nickname" v-text="member.nickname" />
<span class="larkc-tag" v-show="member.leader == 2">
群主
</span>
</div>
<div
v-show="batchDelMember && member.leader != 2"
class="tools"
>
<i
class="el-icon-success"
:class="{ 'is-delete': member.is_delete }"
@click.stop="triggerDelBtn(member)"
/>
</div>
</div>
<div class="profile">
签名 | {{ member.motto ? member.motto : '未设置' }}
</div>
</div>
</div>
</el-scrollbar>
</el-main>
</el-container>
</el-main>
<!-- 群公告模块 -->
<el-main v-else-if="tabIndex == 2" class="no-padding">
<el-container class="full-height">
<el-header
class="notice-header"
height="50px"
style="padding-left: 14px"
>
<span>群公告 ({{ notice.items.length }})</span>
<el-button
plain
size="small"
icon="el-icon-plus"
@click="showNoticeBox(0, '', '')"
>
添加公告
</el-button>
</el-header>
<el-main class="no-padding">
<el-scrollbar
class="full-height"
tag="section"
:native="false"
>
<div v-if="notice.items.length == 0" class="empty-notice">
<SvgNotData style="width: 80px; margin-bottom: 10px" />
<span>暂无群公告</span>
</div>
<div v-else class="notices">
<div
v-for="(item, index) in notice.items"
:key="item.id"
class="notice"
>
<div class="title">
<span
class="left-title"
v-text="item.title"
@click="
showNoticeBox(item.id, item.title, item.content)
"
></span>
<span
class="right-tools no-select"
@click="catNoticeDetail(index)"
>{{ item.isShow ? '收起' : '展开' }}</span
>
</div>
<p class="datetime">
<el-avatar :size="15" :src="item.avatar">
<img src="~@/assets/image/detault-avatar.jpg" />
</el-avatar>
<span
class="text nickname"
v-text="item.nickname"
@click="$refs.userBusinessCard.open(item.user_id)"
></span>
<span class="text">
发表于 {{ item.created_at.substr(0, 16) }}
</span>
</p>
<p
class="content"
:class="{ retract: !item.isShow }"
v-text="item.content"
></p>
</div>
</div>
</el-scrollbar>
</el-main>
</el-container>
</el-main>
<el-main v-else-if="tabIndex == 3" class="no-padding"> </el-main>
</el-container>
</el-main>
</el-container>
</div>
<!-- 编辑公告信息 -->
<div class="lum-dialog-mask animated fadeIn" v-show="notice.isShowform">
<div class="notice-box">
<h4>编辑群公告</h4>
<el-form ref="noticeForm" :model="notice.form" :rules="notice.rules">
<el-form-item label="标题" prop="title">
<el-input
v-model="notice.form.title"
size="medium"
placeholder="请输入标题..."
maxlength="30"
show-word-limit
/>
</el-form-item>
<el-form-item label="详情" prop="content">
<el-input
v-model="notice.form.content"
type="textarea"
rows="5"
placeholder="请输入公告详情..."
maxlength="500"
/>
</el-form-item>
<el-form-item style="text-align: right">
<el-button plain size="small" @click="notice.isShowform = false">
取消
</el-button>
<el-button
type="primary"
size="small"
:loading="notice.loading"
@click="onSubmitNotice"
>提交
</el-button>
</el-form-item>
</el-form>
</div>
</div>
<transition name="el-fade-in-linear">
<AvatarCropper v-if="isAvatarCropper" @close="closeAvatarCropper" />
</transition>
<!-- 查看好友用户信息 -->
<UserBusinessCard ref="userBusinessCard" />
<transition name="el-fade-in-linear">
<GroupLaunch
v-if="inviteFriendBox"
:group-id="groupId"
@close="inviteFriendBox = false"
@invite-success="inviteSuccess"
/>
</transition>
</div>
</template>
<script>
import AvatarCropper from '@/components/layout/AvatarCropper'
import UserBusinessCard from '@/components/user/UserBusinessCard'
import GroupLaunch from '@/components/group/GroupLaunch'
import { SvgNotData } from '@/core/icons'
import {
ServeGetGroupMembers,
ServeGetGroupNotices,
ServeEditGroupNotice,
ServeRemoveMembersGroup,
ServeGroupDetail,
ServeEditGroup,
} from '@/api/group'
export default {
name: 'GroupManage',
props: {
groupId: {
type: [String, Number],
default: null,
},
},
components: {
AvatarCropper,
UserBusinessCard,
GroupLaunch,
SvgNotData,
},
data() {
return {
// 当前选中菜单
tabIndex: 0,
menus: [
{ name: '群信息' },
{ name: '群成员' },
{ name: '群公告' },
{ name: '群设置' },
],
loading: false,
form: {
group_name: '',
profile: '',
avatar: '',
},
rules: {
group_name: [
{
required: true,
message: '用户昵称不能为空!',
trigger: 'blur',
},
],
},
detail: {
group_name: '',
profile: '',
avatar: '',
},
// 群成员列表
searchMembers: '',
batchDelMember: false,
members: [],
// 群公告相关数据
notice: {
isShowform: false,
loading: false,
form: {
id: 0,
title: '',
content: '',
},
rules: {
title: [
{
required: true,
message: '标题不能为空!',
trigger: 'blur',
},
],
content: [
{
required: true,
message: '详情不能为空',
trigger: 'blur',
},
],
},
items: [],
},
inviteFriendBox: false,
isAvatarCropper: false,
}
},
computed: {
filterMembers() {
return this.searchMembers == ''
? this.members
: this.members.filter(item => {
return (
item.nickname.match(this.searchMembers) != null ||
item.visit_card.match(this.searchMembers) != null
)
})
},
},
created() {
this.loadGroupDetail()
this.loadMembers()
this.loadNotices()
},
methods: {
// 加载群信息
loadGroupDetail() {
ServeGroupDetail({
group_id: this.groupId,
}).then(({ code, data }) => {
if (code == 200) {
this.form = this.detail = {
group_name: data.group_name,
profile: data.profile,
avatar: data.avatar,
}
}
})
},
// 加载群组成员列表
loadMembers() {
ServeGetGroupMembers({
group_id: this.groupId,
}).then(res => {
if (res.code == 200) {
this.members = res.data.map(item => {
item.is_delete = false
return item
})
}
})
},
// 加载群组公告列表
loadNotices() {
ServeGetGroupNotices({
group_id: this.groupId,
}).then(res => {
if (res.code == 200) {
this.notice.items = res.data.map(item => {
item.isShow = false
return item
})
}
})
},
// 修改群信息
editGroup() {
this.$refs.groupForm.validate(valid => {
if (!valid) return false
this.loading = true
ServeEditGroup({
group_id: this.groupId,
group_name: this.form.group_name,
profile: this.form.profile,
avatar: this.form.avatar,
})
.then(res => {
if (res.code == 200) {
this.detail.group_name = this.form.group_name
this.detail.profile = this.form.profile
this.detail.avatar = this.form.avatar
this.$message({
message: '信息修改成功...',
type: 'success',
})
} else {
this.$message('信息修改失败...')
}
})
.finally(() => {
this.loading = false
})
})
},
// 左侧菜单栏切换事件
triggerTab(i) {
this.tabIndex = i
},
// 关闭头像裁剪弹出层
closeAvatarCropper(type, avatar = '') {
this.isAvatarCropper = false
if (type == 1 && avatar != '') {
this.form.avatar = avatar
}
},
// 显示编辑公告窗口
showNoticeBox(id = 0, title = '', content = '') {
this.notice.isShowform = true
this.notice.form.id = id
this.notice.form.title = title
this.notice.form.content = content
},
// 编辑公告提交事件
onSubmitNotice() {
this.$refs.noticeForm.validate(valid => {
if (!valid) return false
this.notice.loading = true
ServeEditGroupNotice({
notice_id: this.notice.form.id,
group_id: this.groupId,
title: this.notice.form.title,
content: this.notice.form.content,
is_top: 0,
is_confirm: 0,
})
.then(res => {
if (res.code == 200) {
this.notice.isShowform = false
this.loadNotices()
this.$notify({
title: '消息提示',
message: this.notice.form.id
? '群公告修改成功...'
: '群公告添加成功...',
type: 'success',
})
} else {
this.$notify({
title: '消息提示',
message: this.notice.form.id
? '群公告修改失败...'
: '群公告添加失败...',
type: 'success',
})
}
})
.catch(() => {
this.$notify({
title: '消息提示',
message: '网络异常,请稍后再试...',
position: 'bottom-right',
type: 'warning',
})
})
.finally(() => {
this.notice.loading = false
})
})
},
// 展开/收起群公告详情
catNoticeDetail(index) {
this.notice.items[index].isShow = !this.notice.items[index].isShow
},
// 查看群成员信息事件
catUserDetail(item) {
this.$refs.userBusinessCard.open(item.user_id)
},
// 选中删除成员事件
triggerDelBtn(member) {
let i = this.members.findIndex(item => {
return item.id === member.id
})
this.members[i].is_delete = !this.members[i].is_delete
},
// 批量删除群成员
deleteMembers() {
let ids = [],
names = []
this.members.forEach(item => {
if (item.is_delete) {
ids.push(item.user_id)
names.push(item.nickname)
}
})
if (ids.length == 0) {
this.batchDelMember = false
return
}
this.$confirm(`您确定要将【 ${names.join('、')}】移出群聊?`, '温馨提示', {
confirmButtonText: '确定删除',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
customClass: 'border-radius0',
})
.then(() => {
ServeRemoveMembersGroup({
group_id: this.groupId,
members_ids: ids.join(','),
}).then(res => {
if (res.code == 200) {
this.loadMembers()
this.$notify({
title: '删除成功',
message: `已成功将群成员移除群组...`,
type: 'success',
})
}
})
})
.catch(() => {
this.members.map(item => {
return (item.is_delete = false)
})
this.batchDelMember = false
})
},
// 好友邀请成功回调方法
inviteSuccess() {
this.inviteFriendBox = false
this.loadMembers()
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 80%;
height: 500px;
max-width: 800px;
}
.aside-border {
display: flex;
flex-direction: column;
padding: 8px;
border-right: 1px solid #f5f5f5;
.menu-list {
height: 25px;
line-height: 25px;
margin: 8px 2px;
font-weight: 400;
font-size: 13px;
background-color: white;
cursor: pointer;
border-left: 3px solid white;
padding-left: 10px;
&.selectd {
color: #2196f3;
border-color: #2196f3;
}
}
}
.avatar-col {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.avatar-box {
width: 100px;
height: 100px;
box-shadow: 0px 0px 7px 1px #e8e4e4;
border-radius: 50%;
position: relative;
cursor: pointer;
transition: ease 0.5s;
.upload-icon {
position: absolute;
right: 0;
top: 0;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgba(184, 184, 197, 0.2);
display: flex;
justify-content: center;
align-items: center;
i {
font-size: 30px;
color: #1bb0f3;
}
}
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
z-index: 0;
}
.upload-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
z-index: 3;
display: none;
justify-content: center;
align-items: center;
i {
font-size: 30px;
color: white;
}
}
&:hover .upload-mask {
display: flex;
}
}
/* 群成员相关 start */
.members {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 5px 20px;
justify-content: space-between;
.member {
width: 48%;
height: 70px;
border-radius: 3px;
cursor: pointer;
border: 1px dashed #e2dcdc;
margin: 5px 0;
padding: 3px;
transition: ease 0.5s;
.larkc-tag {
color: #3370ff;
background-color: #e1eaff;
}
.item-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.avatar {
flex: 1 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
flex-direction: row;
align-items: center;
padding: 3px 5px;
.nickname {
font-size: 13px;
margin: 0 5px 0 15px;
}
}
.tools {
flex-basis: 50px;
overflow: hidden;
text-align: right;
padding-right: 5px;
i {
color: #cccccc;
}
.is-delete {
color: #03a9f4;
}
}
}
.profile {
color: #8f8a8a;
font-size: 12px;
padding: 3px 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 3px 0;
}
&:hover,
&.selectd {
border-color: #2196f3;
}
}
}
/* 群成员相关 end */
/* 公告相关 start */
.notice-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.empty-notice {
width: 100%;
height: 100%;
min-height: 200px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
span {
color: #cccccc;
font-size: 13px;
}
}
.notices {
.notice {
cursor: pointer;
min-height: 76px;
overflow: hidden;
border-bottom: 1px dashed #e2dcdc;
margin-bottom: 15px;
margin-right: 15px;
padding-bottom: 5px;
margin: 2px 20px 15px 15px;
h6 {
font-size: 15px;
font-weight: 300;
}
.title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 30px;
.left-title {
flex: 1 1;
height: 100%;
line-height: 30px;
font-size: 14px;
}
.right-tools {
flex-basis: 70px;
flex-shrink: 0;
height: 100%;
line-height: 30px;
text-align: right;
font-weight: 300;
font-size: 12px;
color: #2196f3;
}
}
.datetime {
font-size: 10px;
color: #a59696;
font-weight: 300;
display: flex;
flex-direction: row;
align-items: center;
margin: 10px 0;
.text {
margin: 0 5px;
}
.nickname {
color: #2196f3;
font-weight: 400;
}
}
.retract {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.content {
font-size: 12px;
line-height: 28px;
font-weight: 500;
color: #7d7a7a;
}
}
}
.notice-box {
position: relative;
padding: 28px;
background: #fff;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
border-radius: 4px;
overflow: hidden;
box-sizing: border-box;
height: 415px;
width: 420px;
h4 {
margin-bottom: 20px;
font-weight: 400;
}
}
/* 公告相关 end */
/deep/.el-scrollbar__wrap {
overflow-x: hidden;
}
</style>

View File

@@ -1,231 +0,0 @@
<template>
<div class="lum-dialog-mask">
<el-container class="lum-dialog-box">
<el-header class="header no-select" height="60px">
<p>群公告({{ items.length }})</p>
<p class="tools">
<i class="el-icon-close" @click="$emit('close')" />
</p>
</el-header>
<el-main class="main no-padding">
<template v-if="loadStatus == 0">
<Loading text="正在努力加载中 ..." />
</template>
<template v-else-if="loadStatus == 1 && items.length == 0">
<div class="loading">
<SvgNotData class="svg-icon" />
<p>暂无群公告</p>
</div>
</template>
<template v-if="loadStatus == 2">
<div class="loading">
<i
class="el-icon-warning"
style="font-size: 50px; color: #ff5151"
/>
<p>
加载失败
<a @click="loadNotices" class="pointer">点击重试</a>...
</p>
</div>
</template>
<template v-else>
<el-scrollbar class="full-height" :native="false" tag="section">
<div
v-for="(item, index) in items"
:key="item.id"
class="notice-item"
>
<div class="title">
<span class="left-title">{{ item.title }}</span>
<span
class="right-tools no-select"
@click="catNoticeDetail(index)"
>
{{ item.isShow ? '收起' : '展开' }}
</span>
</div>
<p class="datetime">
<el-avatar :size="15" :src="item.avatar">
<img src="~@/assets/image/detault-avatar.jpg" />
</el-avatar>
<span
class="text nickname"
@click="$refs.userBusinessCard.open(item.user_id)"
>
{{ item.nickname }}
</span>
<span class="text">发表于 {{ item.created_at }}</span>
</p>
<p class="content" :class="{ retract: !item.isShow }">
{{ item.content }}
</p>
</div>
</el-scrollbar>
</template>
</el-main>
</el-container>
</div>
</template>
<script>
import { ServeGetGroupNotices } from '@/api/group'
import { SvgNotData } from '@/core/icons'
import Loading from '@/components/global/Loading'
export default {
name: 'GroupNotice',
props: {
groupId: {
type: [String, Number],
default: null,
},
},
components: {
SvgNotData,
Loading,
},
data() {
return {
// 公告列表
items: [],
loadStatus: 0,
}
},
created() {
this.loadNotices()
},
methods: {
// 加载群组公告列表
loadNotices() {
this.loadStatus = 0
ServeGetGroupNotices({
group_id: this.groupId,
})
.then(({ code, data }) => {
if (code == 200) {
this.items = data.map(item => {
item.isShow = false
return item
})
this.loadStatus = 1
} else {
this.loadStatus = 2
}
})
.catch(() => {
this.loadStatus = 2
})
},
// 展开/收起群公告详情
catNoticeDetail(index) {
this.items[index].isShow = !this.items[index].isShow
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 650px;
max-width: 650px;
height: 550px;
.main {
overflow: hidden;
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 70%;
font-size: 12px;
p {
margin-top: 20px;
}
.svg-icon {
width: 80px;
margin-bottom: 10px;
}
}
.notice-item {
cursor: pointer;
min-height: 76px;
overflow: hidden;
border-bottom: 1px dashed #e2dcdc;
padding-bottom: 5px;
margin: 2px 20px 15px 15px;
h6 {
font-size: 15px;
font-weight: 300;
}
.title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 30px;
.left-title {
flex: 1 1;
height: 100%;
line-height: 30px;
font-size: 15px;
}
.right-tools {
flex-basis: 70px;
flex-shrink: 0;
height: 100%;
line-height: 30px;
text-align: right;
font-weight: 300;
font-size: 12px;
color: #2196f3;
}
}
.datetime {
font-size: 10px;
color: #a59696;
font-weight: 300;
display: flex;
flex-direction: row;
align-items: center;
margin: 10px 0;
.text {
margin: 0 5px;
}
.nickname {
color: #2196f3;
font-weight: 400;
}
}
.retract {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.content {
font-size: 13px;
line-height: 28px;
font-weight: 400;
color: #706a6a;
}
}
}
}
/deep/.el-scrollbar__wrap {
overflow-x: hidden;
}
</style>

View File

@@ -1,817 +0,0 @@
<template>
<el-container class="container">
<el-header class="header">
<span>群信息</span>
<el-tooltip content="发送消息" placement="top">
<i class="icon-send el-icon-chat-line-square" @click="sendGroup" />
</el-tooltip>
<i class="el-icon-close" @click="$emit('close')" />
</el-header>
<el-main class="main lum-scrollbar">
<div class="list-item flex">
<p>
<span>群名称</span>
<span class="group-setting-title">{{ detail.groupName }}</span>
</p>
<span
v-show="detail.is_manager"
class="more"
@click="isShowManager = true"
>管理
</span>
</div>
<div class="list-item">
<span>群主</span>
<span class="group-boss-name">{{ detail.groupOwner }}</span>
</div>
<div class="list-item">
<span>我的群昵称</span>
<span v-if="!isEditRemark" class="edit-visit-card">
<span>
{{ detail.visitCard }}
<span v-show="!detail.visitCard" style="font-size: 12px">
添加群名片
</span>
</span>
<i
class="el-icon-edit-outline edit-remark-icon"
@click="
isEditRemark = true
editRemarkText = detail.visitCard
"
/>
</span>
<span v-else>
<input
v-model="editRemarkText"
class="edit-input"
type="text"
v-focus
@keyup.enter="editRemark"
/>
<span class="input-submit" @click="editRemark">确认</span>
</span>
</div>
<div class="list-item flex">
<span>消息免打扰</span>
<el-switch
v-model="detail.disturb"
inactive-color="#e0d6d6"
:disabled="disturbDisabled"
@change="editDisturb"
/>
</div>
<!-- 预留 -->
<div class="list-item flex">
<span>全员禁言</span>
<el-switch v-model="detail.no_message" inactive-color="#e0d6d6" />
</div>
<div class="list-item">
<span>群成员</span>
<span>{{ members.length }} </span>
</div>
<div class="list-item-tips">群主已开启新成员入群可查看所有聊天记录</div>
<div class="list-item">群简介</div>
<div class="list-item-tips">
{{ detail.groupProfile ? detail.groupProfile : '暂无群简介' }}
</div>
<div class="list-item flex">
<span>群公告</span>
<span
v-show="detail.group_notice"
class="more"
@click="isShowGroupNotice = true"
>更多
</span>
</div>
<div class="list-item-tips group-notice">
<span v-if="detail.group_notice.title">
<b>#{{ detail.group_notice.title }}#</b><br />
{{ detail.group_notice.content }}
</span>
<span v-else>暂无群公告</span>
</div>
<div class="list-item">
<p class="group-invite" @click="addGroupMembers">
<i class="el-icon-plus" />
<span>&nbsp;邀请好友</span>
</p>
</div>
<div class="list-item">
<div class="member-box">
<div class="view-box">
<i class="iconfont icon-sousuo i-sousuo" />
<input type="text" placeholder="搜索群成员" v-model="keywords" />
</div>
<el-row class="row-header">
<el-col :span="11">昵称</el-col>
<el-col :span="8">名片</el-col>
<el-col :span="5">性别</el-col>
</el-row>
<template v-if="searchs.length == 0">
<el-row class="row-items">
<el-col :span="24">
<p style="text-align:center;">无数据</p>
</el-col>
</el-row>
</template>
<template v-else>
<el-row
v-for="member in searchs"
:key="member.user_id"
class="row-items"
@click.native="openUserDetail(member.user_id)"
>
<el-col :span="11">
<img
width="20px"
:src="member.avatar"
:onerror="$store.state.detaultAvatar"
/>
<span class="nickname">{{ member.nickname }}</span>
</el-col>
<el-col :span="8">
<span>{{ member.visit_card ? member.visit_card : '-' }}</span>
</el-col>
<el-col :span="5">
<span v-if="member.gender == 1"></span>
<span v-else-if="member.gender == 2"></span>
<span v-else>未知</span>
</el-col>
</el-row>
</template>
</div>
</div>
</el-main>
<el-footer class="footer">
<button @click="isShowSignout = true">退出该群聊</button>
</el-footer>
<!-- 退群提示层 -->
<div class="signout-box no-select" v-show="isShowSignout">
<p v-show="signoutStatus == 0">您确认退出当前群聊吗</p>
<p v-show="signoutStatus == 0">退群后群聊信息将不能查看</p>
<p v-show="signoutStatus == 0" class="signout-btn">
<button @click="signout">确认</button>
<button @click="isShowSignout = false">取消</button>
</p>
<p v-show="signoutStatus == 1" class="signout-btn mt38">
<span style="color: #ccc">
<i class="el-icon-loading" />
正在退出群聊...
</span>
</p>
<p v-show="signoutStatus == 2" class="signout-btn mt38">
<span style="color: #cccccc">退出群聊失败请3(s)后再试...</span>
</p>
<p v-show="signoutStatus == 3" class="signout-btn mt38">
<span style="color: #339e19">
<i class="iconfont icon-success_no_circle" /> 已成功退出群聊...
</span>
</p>
</div>
<!-- 查看好友用户信息 -->
<UserBusinessCard ref="userBusinessCard" />
<!-- 邀请好友组件 -->
<transition name="el-fade-in-linear">
<GroupLaunch
v-if="inviteFriendBox"
:group-id="groupId"
@close="inviteFriendBox = false"
@invite-success="inviteSuccess"
/>
</transition>
<!-- 群管理组件 -->
<transition name="el-fade-in-linear">
<GroupManage
v-if="isShowManager"
:group-id="groupId"
@close="isShowManager = false"
/>
</transition>
<!-- 群公告组件 -->
<transition name="el-fade-in-linear">
<GroupNotice
v-if="isShowGroupNotice"
:group-id="groupId"
@close="isShowGroupNotice = false"
/>
</transition>
</el-container>
</template>
<script>
import { ServeSetNotDisturb } from '@/api/chat'
import {
ServeGroupDetail,
ServeUpdateGroupCard,
ServeSecedeGroup,
ServeGetGroupMembers,
} from '@/api/group'
//创建群聊组件
import GroupLaunch from '@/components/group/GroupLaunch'
import UserBusinessCard from '@/components/user/UserBusinessCard'
import GroupManage from '@/components/group/GroupManage'
import GroupNotice from '@/components/group/GroupNotice'
export default {
name: 'GroupPanel',
components: {
GroupLaunch,
UserBusinessCard,
GroupManage,
GroupNotice,
},
props: {
groupId: {
type: [String, Number],
default: null,
},
},
data() {
return {
detail: {
groupAvatar: '',
groupId: 0,
groupName: '',
groupOwner: '',
groupProfile: '',
disturb: 0,
no_message: false,
visitCard: '',
is_manager: false,
group_notice: [],
},
keywords: '',
members: [],
isEditRemark: false,
editRemarkText: '',
inviteFriendBox: false,
isShowSignout: false,
signoutStatus: 0,
disturbDisabled: false,
// 是否显示群管理窗口
isShowManager: false,
// 是否显示群公告窗口
isShowGroupNotice: false,
}
},
watch: {
groupId: function(value) {
if (value > 0) {
this.loadGroupDetail()
this.loadMembers()
}
},
},
computed: {
searchs() {
return this.keywords == ''
? this.members
: this.members.filter(item => {
return (
item.nickname.match(this.keywords) != null ||
item.user_card.match(this.keywords) != null
)
})
},
},
created() {
if (parseInt(this.groupId) > 0) {
this.loadGroupDetail()
this.loadMembers()
}
},
methods: {
// 加载群组成员列表
loadMembers() {
ServeGetGroupMembers({
group_id: this.groupId,
}).then(res => {
if (res.code == 200) {
this.members = res.data
this.$emit('group-info', {
group_id: this.members.groupId,
members_num: this.members.length,
})
}
})
},
// 加载群信息
loadGroupDetail() {
this.isEditRemark = false
ServeGroupDetail({
group_id: this.groupId,
}).then(res => {
if (res.code == 200) {
let result = res.data
this.detail.groupAvatar = result.avatar
this.detail.groupId = result.group_id
this.detail.userId = res.data.user_id
this.detail.groupName = result.group_name
this.detail.groupOwner = result.manager_nickname
this.detail.groupProfile = result.group_profile
this.detail.disturb = result.not_disturb == 1 ? true : false
this.detail.visitCard = result.visit_card
this.detail.is_manager = result.is_manager
if (result.notice) {
this.detail.group_notice = result.notice
}
}
})
},
// 设置群免打扰状态
editDisturb(value) {
this.disturbDisabled = true
ServeSetNotDisturb({
type: 2,
receive_id: this.groupId,
not_disturb: value ? 1 : 0,
})
.then(res => {
if (res.code == 200) {
this.$emit('disturb-change', {
group_id: this.groupId,
status: value ? 1 : 0,
})
} else {
this.detail.disturb = value ? 0 : 1
}
})
.catch(() => {
this.detail.disturb = value ? 0 : 1
})
.finally(() => {
this.disturbDisabled = false
})
},
// 设置用户群名片
editRemark() {
if (this.editRemarkText == '') {
this.isEditRemark = false
return
}
if (this.detail.visitCard == this.editRemarkText) {
this.isEditRemark = false
return
}
ServeUpdateGroupCard({
group_id: this.groupId,
visit_card: this.editRemarkText,
}).then(res => {
if (res.code == 200) {
this.isEditRemark = false
this.detail.visitCard = this.editRemarkText
}
})
},
// 查看用户信息
openUserDetail(user_id) {
this.$refs.userBusinessCard.open(user_id)
},
// 邀请好友加入群聊
addGroupMembers() {
sessionStorage.setItem('invite_group_id', this.detail.groupId)
this.inviteFriendBox = true
},
// 邀请好友成功之后的回调事件
inviteSuccess() {
this.inviteFriendBox = false
this.loadMembers()
this.$notify({
title: '邀请成功',
message: `好友已成功加入群组...`,
type: 'success',
})
},
// 发送群聊
sendGroup() {
this.$emit('send-group', this.detail.groupId)
},
// 退出群操操作
signout() {
this.signoutStatus = 1
ServeSecedeGroup({
group_id: this.detail.groupId,
})
.then(res => {
if (res.code == 200) {
this.signoutStatus = 3
setTimeout(() => {
this.signoutStatus = 0
this.isShowSignout = false
this.$emit('quit-group')
}, 1500)
} else {
this.signoutStatus = 2
setTimeout(() => {
this.signoutStatus = 0
}, 3000)
}
})
.catch(() => {
this.signoutStatus = 2
setTimeout(() => {
this.signoutStatus = 0
}, 3000)
})
},
},
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
.header {
height: 60px;
line-height: 60px;
padding: 0;
text-align: center;
box-shadow: -1px 0px 5px 0px #cccccc45;
position: relative;
span {
font-size: 16px;
font-weight: 400;
}
.icon-send {
position: absolute;
left: 15px;
top: 22px;
font-size: 18px;
cursor: pointer;
}
.el-icon-close {
position: absolute;
right: 15px;
top: 22px;
font-size: 18px;
cursor: pointer;
}
}
.main {
padding: 0;
}
}
.list-item {
position: relative;
padding: 16px 16px 0;
min-height: 18px;
font-size: 14px;
&.flex {
display: flex;
justify-content: space-between;
}
.edit-visit-card {
position: initial;
color: #a29f9f;
}
.edit-remark-icon {
margin-left: 5px;
color: rgb(169, 184, 187);
position: absolute;
top: 20px;
cursor: pointer;
}
.edit-input {
width: 46%;
height: 25px;
line-height: 25px;
border: 1px solid #92cbff;
padding-left: 5px;
border-radius: 3px;
}
.input-submit {
width: 55px;
text-align: center;
height: 25px;
line-height: 25px;
background-color: #008cee;
border-radius: 2px;
display: inline-block;
color: #fff !important;
font-size: 12px;
margin-left: 10px;
cursor: pointer;
}
.group-setting-title {
max-width: 250px;
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: bottom;
}
.group-boss-name {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
max-width: 180px;
position: relative;
top: 4px;
}
.group-invite {
height: 30px;
line-height: 30px;
text-align: center;
color: #877272;
cursor: pointer;
border-radius: 2px;
border: 1px dashed #d9bbbb;
font-size: 13px;
transition: all 0.5s ease-in-out;
&:hover {
color: #ff5722;
border-color: #ff5722;
transform: scale(1.01);
}
}
.more {
color: #409eff;
cursor: pointer;
font-size: 12px;
}
}
.list-item-tips {
font-size: 12px;
color: #b1b1b1;
margin-top: 5px;
padding-left: 16px;
padding-right: 16px;
font-weight: 300;
overflow: hidden;
word-break: break-word;
}
.group-notice {
line-height: 22px;
}
.member-box {
min-height: 180px;
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ecebeb;
border-radius: 3px;
.view-box {
width: 100%;
height: 30px;
margin-top: 20px;
margin-bottom: 15px;
position: relative;
input {
width: calc(100% - 40px);
height: 30px;
line-height: 28px;
border-radius: 3px;
border: 1px solid #f1e9e9;
color: #b3b0b0;
font-size: 13px;
padding: 0 10px 0 30px;
&::-webkit-input-placeholder {
color: #ccc9c9;
font-size: 13px;
}
}
.i-sousuo {
color: rgb(179, 176, 176);
position: absolute;
left: 10px;
top: 9px;
}
span {
position: relative;
i {
font-size: 24px;
top: -3px;
left: 10px;
position: absolute;
color: #ccc;
}
}
}
.row-header {
width: 100%;
height: 30px;
margin-bottom: 10px;
border-bottom: 1px solid #e0dddd;
div {
height: 30px;
line-height: 30px;
&:nth-child(2) {
text-align: center;
}
&:nth-child(3) {
text-align: right;
}
}
}
.row-items {
width: 100%;
height: 30px;
margin-bottom: 3px;
font-size: 12px;
cursor: pointer;
&:hover {
background: #f6f6f6;
}
div {
height: 30px;
line-height: 30px;
&:nth-child(2) {
text-align: center;
}
&:nth-child(3) {
text-align: right;
}
}
img {
display: inline-block;
border-radius: 50%;
position: relative;
top: 4px;
}
.nickname {
margin-left: 5px;
&:hover {
color: #3685d6;
}
}
}
}
.signout-box {
width: 100%;
height: 100px;
background: #ffffff;
position: absolute;
z-index: 2;
bottom: 0;
box-shadow: -1px -3px 11px 0px #cccccc82;
-webkit-animation: showFooter 0.35s ease-in-out;
-moz-animation: showFooter 0.35s ease-in-out;
animation: showFooter 0.35s ease-in-out;
p {
&:first-child {
text-align: center;
height: 35px;
line-height: 35px;
}
&:nth-child(2) {
text-align: center;
font-size: 12px;
color: #cccccc;
}
}
.mt38 {
margin-top: 38px;
}
}
.signout-btn {
text-align: center;
margin-top: 10px;
button {
height: 30px;
width: 90px;
line-height: 30px;
background: #007fbb;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
&:first-child {
background: #ff3333;
color: white;
}
&:last-child {
background: #f1eded;
}
}
}
.container .footer {
height: 60px;
padding: 0;
line-height: 60px;
text-align: center;
background-color: #f8f8f8;
button {
width: 180px;
height: 35px;
line-height: 35px;
background: #ed3c3b;
border-radius: 3px;
color: white;
cursor: pointer;
font-size: 12px;
margin: auto auto;
&:active {
background: #f5b8b8;
}
}
}
@keyframes showFooter {
0% {
transform: translateY(75px);
}
to {
transform: translateY(0);
}
}
@-webkit-keyframes showFooter {
0% {
-webkit-transform: translateY(75px);
}
to {
-webkit-transform: translateY(0);
}
}
</style>

View File

@@ -1,80 +0,0 @@
<template>
<div>
<div class="abs-module" v-show="isShow">
<div class="abs-box">
<i class="el-icon-circle-close" @click="close" />
<a href="https://www.aliyun.com/1111/new?userCode=kqyyppx2">
<img src="~@/assets/image/aliyun-abs.jpg" width="300" />
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
}
},
created() {
if (this.getNum() <= 2) {
setTimeout(() => {
this.isShow = true
}, 1000 * 60 * 2)
}
},
methods: {
getNum() {
return parseInt(sessionStorage.getItem('ABS_BOX')) || 0
},
close() {
sessionStorage.setItem('ABS_BOX', this.getNum() + 1)
this.isShow = false
},
},
}
</script>
<style lang="less" scoped>
.abs-module {
position: fixed;
width: 300px;
height: 163.63px;
right: 20px;
top: 20px;
border-radius: 5px;
z-index: 9999;
overflow: hidden;
transition: all 2s;
animation: absfade 1000ms infinite;
.abs-box {
width: 100%;
height: 100%;
position: relative;
i {
position: absolute;
right: 10px;
top: 10px;
color: white;
cursor: pointer;
font-size: 22px;
}
}
}
@keyframes absfade {
from {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
to {
transform: scale(1);
}
}
</style>

View File

@@ -1,246 +0,0 @@
<template>
<div class="lum-dialog-mask">
<el-container class="lum-dialog-box">
<el-header class="header" height="50px">
<p>选择头像</p>
<p class="tools">
<i class="el-icon-close" @click="$emit('close', 0)" />
</p>
</el-header>
<el-main class="main">
<el-container class="full-height">
<el-aside width="400px">
<div class="cropper-box">
<vue-cropper
ref="cropper"
mode="cover"
:img="option.img"
:output-size="option.size"
:output-type="option.outputType"
:info="true"
:full="option.full"
:fixed="fixed"
:fixed-number="fixedNumber"
:can-move="option.canMove"
:can-move-box="option.canMoveBox"
:fixed-box="option.fixedBox"
:original="option.original"
:auto-crop="option.autoCrop"
:auto-crop-width="option.autoCropWidth"
:auto-crop-height="option.autoCropHeight"
:center-box="option.centerBox"
:high="option.high"
@real-time="realTime"
/>
<input
type="file"
id="uploads"
ref="fileInput"
accept="image/png, image/jpeg, image/jpg"
style="display: none"
@change="uploadImg($event, 1)"
/>
</div>
<div class="tools tools-flex">
<el-button
size="small"
plain
icon="el-icon-upload"
@click="clickUpload"
>上传图片
</el-button>
<el-button
size="small"
plain
icon="el-icon-refresh"
@click="refreshCrop"
>刷新
</el-button>
<el-button
size="small"
plain
icon="el-icon-refresh-left"
@click="rotateLeft"
>左转
</el-button>
<el-button
size="small"
plain
icon="el-icon-refresh-right"
@click="rotateRight"
>右转
</el-button>
</div>
</el-aside>
<el-main class="no-padding">
<div class="cropper-box">
<div class="preview-img">
<img v-if="cusPreviewsImg" :src="cusPreviewsImg" />
</div>
</div>
<div class="tools">
<el-button type="primary" size="small" @click="uploadService">
保存图片
</el-button>
</div>
</el-main>
</el-container>
</el-main>
</el-container>
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper'
import { ServeUploadFileStream } from '@/api/upload'
export default {
name: 'AvatarCropper',
components: {
VueCropper,
},
data() {
return {
cusPreviewsImg: '',
previews: {},
option: {
img: '',
size: 1,
full: false,
outputType: 'png',
canMove: true,
fixedBox: true,
original: false,
canMoveBox: true,
autoCrop: true,
// 只有自动截图开启 宽度高度才生效
autoCropWidth: 200,
autoCropHeight: 150,
centerBox: false,
high: true,
},
fixed: true,
fixedNumber: [1, 1],
}
},
methods: {
clickUpload() {
this.$refs.fileInput.click()
},
clearCrop() {
if (!this.cusPreviewsImg) return false
this.$refs.cropper.clearCrop()
},
refreshCrop() {
if (!this.cusPreviewsImg) return false
this.$refs.cropper.refresh()
},
rotateLeft() {
if (!this.cusPreviewsImg) return false
this.$refs.cropper.rotateLeft()
},
rotateRight() {
if (!this.cusPreviewsImg) return false
this.$refs.cropper.rotateRight()
},
// 实时预览函数
realTime() {
this.$refs.cropper.getCropData(img => {
this.cusPreviewsImg = img
})
},
// 上传回调事件
uploadImg(e, num) {
let file = e.target.files[0]
if (!/\.(gif|jpg|jpeg|png|bmp|GIF|JPG|PNG)$/.test(e.target.value)) {
this.$message('图片类型必须是.gif,jpeg,jpg,png,bmp中的一种')
return false
}
let reader = new FileReader()
reader.onload = e => {
let data
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
data = window.URL.createObjectURL(new Blob([e.target.result]))
} else {
data = e.target.result
}
if (num === 1) {
this.option.img = data
} else if (num === 2) {
this.example2.img = data
}
}
// 转化为base64
// reader.readAsDataURL(file)
// 转化为blob
reader.readAsArrayBuffer(file)
},
// 上传图片到服务器
uploadService() {
if (this.cusPreviewsImg == '') return
ServeUploadFileStream({
fileStream: this.cusPreviewsImg,
})
.then(res => {
if (res.code == 200) {
this.$emit('close', 1, res.data.avatar)
} else {
this.$message('文件上传失败,请稍后再试...')
}
})
.catch(() => {
this.$message('文件上传失败,请稍后再试...')
})
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
height: 550px;
max-width: 800px;
.main {
.cropper-box {
height: 400px;
display: flex;
justify-content: center;
align-items: center;
.preview-img {
width: 180px;
height: 180px;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 0 4px #ccc;
img {
width: 100%;
height: 100%;
}
}
}
.tools {
height: 40px;
margin-top: 20px;
text-align: center;
button {
border-radius: 1px;
}
}
.tools-flex {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
}
</style>

View File

@@ -1,129 +0,0 @@
<template>
<div>
<div class="reward" v-show="isShow">
<div class="title">
<span>Donate</span>
<i class="el-icon-circle-close" @click="close" />
</div>
<div class="main">
<div class="pay-box">
<img
src="https://cdn.learnku.com/uploads/images/202101/30/46424/PPYHOUhCb4.jpg"
/>
<p>支付宝</p>
</div>
<div class="pay-box">
<img
src="https://cdn.learnku.com/uploads/images/202101/30/46424/XLmCJjbvlQ.png"
/>
<p>微信</p>
</div>
</div>
<div class="footer">
开源不易如果你觉得项目对你有帮助可以请作者喝杯咖啡鼓励下...
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
}
},
created() {
if (this.getNum() <= 3) {
setTimeout(() => {
this.isShow = true
}, 1000 * 30)
}
},
methods: {
getNum() {
return parseInt(localStorage.getItem('REWARD_BOX')) || 0
},
close() {
localStorage.setItem('REWARD_BOX', this.getNum() + 1)
this.isShow = false
},
},
}
</script>
<style lang="less" scoped>
.reward {
position: fixed;
width: 550px;
height: 400px;
right: 20px;
bottom: 20px;
border-radius: 5px;
box-shadow: 0 0 12px #ccc;
border: 1px solid rgb(228, 225, 225);
box-sizing: border-box;
overflow: hidden;
user-select: none;
z-index: 9999;
background: white;
.title {
height: 50px;
line-height: 50px;
padding-left: 20px;
width: 100%;
font-size: 16px;
background: #f9f7f7;
position: relative;
box-sizing: border-box;
i {
position: absolute;
right: 15px;
top: 18px;
font-size: 18px;
cursor: pointer;
}
}
.main {
height: 300px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.pay-box {
width: 200px;
height: 240px;
background: #1977ff;
margin: 0 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 5px;
img {
width: 150px;
height: 150px;
}
p {
margin-top: 20px;
color: white;
}
&:last-child {
background: #22ab38;
}
}
}
.footer {
height: 50px;
line-height: 50px;
text-align: center;
font-size: 13px;
}
}
</style>

View File

@@ -1,38 +0,0 @@
<template>
<div></div>
</template>
<script>
// 待开发
export default {
data() {
return {
skins: [
{
theme: '',
class: 'default',
text: '默认',
},
{
theme: '',
class: 'red',
text: '红色',
},
{
theme: '',
class: 'dark',
text: '暗黑',
},
{
theme: '',
class: 'blue',
text: '浅蓝',
},
],
}
},
created() {},
methods: {},
}
</script>
<style lang="less" scoped></style>

View File

@@ -1,57 +0,0 @@
<template>
<div class="welcome-box">
<div class="famous-box">
<img src="~@/assets/image/chat.png" width="300" />
<p v-if="text">
{{ text }}
</p>
<p v-else>
不是井里没有水而是你挖的不够深<br />
不是成功来得慢而是你努力的不够多<br />
加油吧 ......
</p>
</div>
</div>
</template>
<script>
export default {
props: {
text: {
type: String,
default: ''
}
},
components: {},
data() {
return {}
},
created() {},
methods: {},
}
</script>
<style lang="less" scoped>
.welcome-box {
height: 100%;
width: 100%;
.famous-box {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%;
font-size: 24px;
user-select: none;
p {
width: 100%;
font-weight: 300;
text-align: center;
font-size: 15px;
color: #b9b4b4;
margin-top: -30px;
}
}
}
</style>

View File

@@ -1,246 +0,0 @@
<template>
<div>
<p>笔记附件列表({{ files.length }})</p>
<div class="annex-box lum-scrollbar">
<input ref="uploads" type="file" @change="uploadAnnex" />
<div class="annex-main">
<p v-show="files.length == 0" class="empty-text">
暂无附件
</p>
<div v-for="(file, i) in files" :key="file.id" class="file-item">
<div class="suffix">{{ file.file_suffix }}</div>
<div class="content">
<div class="filename">{{ file.original_name }}</div>
<div class="filetool">
<span>{{ formateTime(file.created_at) }}</span>
<span class="size">
{{ formateSize(file.file_size) }}
</span>
<div class="tools">
<i class="el-icon-download" @click="downloadAnnex(file.id)" />
<i class="el-icon-delete" @click="deleteAnnex(file.id, i)" />
</div>
</div>
</div>
</div>
</div>
<div class="annex-footer">
<p class="notice-text">最多可支持上传{{ maxNum }}个附件</p>
<el-button
type="primary"
size="small"
icon="el-icon-upload"
:disabled="files.length >= maxNum"
:loading="loadStatus"
@click="$refs.uploads.click()"
>{{ loadStatus ? '上传中...' : '上传附件' }}
</el-button>
</div>
</div>
</div>
</template>
<script>
import {
ServeDeleteArticleAnnex,
ServeDownloadAnnex as downloadAnnex,
ServeUploadArticleAnnex,
} from '@/api/article'
import { formateSize, formateTime, parseTime } from '@/utils/functions'
export default {
name: 'NoteAnnexBox',
props: {
id: {
type: Number,
default: 0,
},
files: {
type: Array,
default() {
return []
},
},
},
data() {
return {
loadStatus: false,
disabled: false,
maxNum: 10,
}
},
methods: {
// 格式化文件大小
formateSize,
// 格式化时间显示格式
formateTime,
// 下载笔记附件
downloadAnnex,
// 删除笔记附件
deleteAnnex(annex_id, index) {
ServeDeleteArticleAnnex({
annex_id,
}).then(({ code }) => {
if (code == 200) {
this.$delete(this.files, index)
}
})
},
// 上传笔记附件文件
uploadAnnex(e) {
if (e.target.files.length == 0) {
return false
}
let file = e.target.files[0]
if (file.size / (1024 * 1024) > 5) {
this.$message('笔记附件不能大于5M!')
return false
}
let fileData = new FormData()
fileData.append('annex', file)
fileData.append('article_id', this.id)
this.loadStatus = true
ServeUploadArticleAnnex(fileData)
.then(({ code, data }) => {
if (code == 200) {
this.files.push({
id: data.id,
original_name: data.original_name,
created_at: parseTime(new Date()),
file_size: data.file_size,
file_suffix: data.file_suffix,
})
}
})
.finally(() => {
this.loadStatus = false
})
},
},
}
</script>
<style lang="less" scoped>
/* 文件管理弹出层 */
.annex-box {
width: 300px;
min-height: 50px;
max-height: 675px;
background-color: white;
overflow-y: auto;
.annex-main {
min-height: 30px;
border-bottom: 1px solid rgb(239, 233, 233);
margin-bottom: 8px;
padding: 5px 0;
.empty-text {
color: #969292;
font-size: 12px;
margin-top: 10px;
}
.file-item {
height: 50px;
margin-bottom: 5px;
margin-top: 10px;
.suffix {
width: 50px;
height: 100%;
background-color: #ffcc80;
border-radius: 3px;
float: left;
line-height: 50px;
text-align: center;
color: white;
}
.content {
float: left;
width: 247px;
height: 100%;
.filename {
padding-left: 5px;
color: #172b4d;
font-size: 14px;
font-weight: 400;
line-height: 1.6;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.filetool {
color: #505f79;
font-size: 12px;
font-weight: 400;
line-height: 1.6;
padding-left: 5px;
margin-top: 9px;
position: relative;
span {
margin: 0 3px;
&.size {
color: #3a8ee6;
}
}
}
.tools {
position: absolute;
top: -5px;
right: 5px;
width: 55px;
height: 24px;
text-align: right;
line-height: 28px;
i {
font-size: 16px;
cursor: pointer;
margin-right: 5px;
}
.el-icon-download {
color: #66b1ff;
}
.el-icon-delete {
color: red;
}
}
}
}
}
input {
display: none;
}
.annex-footer {
.notice-text {
font-size: 12px;
color: #ccc;
text-align: left;
float: left;
padding-top: 10px;
}
button {
float: right;
}
}
}
</style>

View File

@@ -1,206 +0,0 @@
<template>
<div class="lum-dialog-mask">
<el-container
class="lum-dialog-box animated bounceInDown"
v-outside="close"
>
<el-header height="60px" class="header no-select">
<p> 30 天删除的附件({{ tableData.length }})</p>
<p class="tools">
<i class="el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main lum-scrollbar">
<el-table :data="tableData" size="mini">
<div slot="empty">暂无相关数据</div>
<el-table-column
prop="original_name"
label="附件名称"
width="180"
:show-overflow-tooltip="true"
>
<template slot-scope="scope">
<el-button type="text" @click="downloadAnnex(scope.row.id)">{{
scope.row.original_name
}}</el-button>
</template>
</el-table-column>
<el-table-column
prop="title"
label="所属笔记"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column
prop="day"
label="剩余天数"
align="center"
width="80"
>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100"
align="center"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="recoverAnnex(scope.row)"
>恢复</el-button
>
<el-popover
placement="top"
@hide="lock(false)"
@show="lock(true)"
:ref="`popover-${scope.$index}`"
>
<p style="margin-bottom: 10px">
{{
scope.row.original_name
}}附件您确定要永久删除吗<br />
</p>
<div style="text-align: right; margin: 0">
<el-button
size="mini"
type="text"
@click="
scope._self.$refs[`popover-${scope.$index}`].doClose()
"
>
取消</el-button
>
<el-button
type="primary"
size="mini"
@click="deleteAnnex(scope.row, scope.$index)"
>确定</el-button
>
</div>
<el-button
slot="reference"
type="text"
size="small"
style="color: #ec5252; margin-left: 5px"
>删除</el-button
>
</el-popover>
</template>
</el-table-column>
</el-table>
</el-main>
<el-footer class="footer" height="30px">
<p class="footer-tip">移动至回收站的附件和笔记将在 30 天后自动清除</p>
</el-footer>
</el-container>
</div>
</template>
<script>
import Vue from 'vue'
import { Table, TableColumn } from 'element-ui'
Vue.use(Table)
Vue.use(TableColumn)
import {
ServeGetRecoverAnnexList,
ServeRecoverArticleAnnex,
ServeDownloadAnnex,
ServeForeverDeleteAnnex,
} from '@/api/article'
export default {
name: 'NoteAnnexRecycle',
data() {
return {
tableData: [],
closeLock: false,
}
},
created() {
this.loadList()
},
methods: {
loadList() {
ServeGetRecoverAnnexList().then(res => {
if (res.code == 200) {
this.tableData = res.data.rows
}
})
},
// 关闭当前窗口
close() {
if (!this.closeLock) this.$emit('close')
},
// 给遮罩层加锁
lock(value) {
this.closeLock = value
},
// 恢复附件
recoverAnnex(data, index) {
ServeRecoverArticleAnnex({
annex_id: data.id,
}).then(res => {
if (res.code == 200) {
this.tableData.splice(index, 1)
}
})
},
//永久删除附件
deleteAnnex(data, index) {
ServeForeverDeleteAnnex({
annex_id: data.id,
})
.then(res => {
this.$refs[`popover-${index}`].doClose()
this.lock(false)
if (res.code == 200) {
this.tableData.splice(index, 1)
} else {
this.$notify({
message: '附件删除失败...',
})
}
})
.catch(() => {
this.$refs[`popover-${index}`].doClose()
this.lock(false)
})
},
//下载笔记附件
downloadAnnex: ServeDownloadAnnex,
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 700px;
height: 80%;
max-width: 700px;
.main {
padding-top: 0;
}
.footer {
.footer-tip {
color: #a7afbc;
font-size: 12px;
font-weight: 400;
line-height: 1.6;
}
}
}
/deep/ .tab-header-row .cell {
font-size: 14px;
font-weight: 400;
color: rgb(172, 167, 167);
}
</style>

View File

@@ -1,220 +0,0 @@
<template>
<div class="tag-manage">
<div class="title">
<span>已选择</span>
</div>
<div class="tag-groups">
<p
v-for="(tag, i) in tags"
v-show="tag.isSelectd"
:key="i"
class="tag-item"
>
<span>{{ tag.name }}</span>
<i class="el-icon-close" @click="active(tag.id, tag.isSelectd)" />
</p>
</div>
<div class="title">
<span>标签栏</span>
</div>
<div class="tag-groups">
<p
v-for="(tag, i) in tags"
:key="i"
class="tag-item"
:class="{ active: tag.isSelectd }"
@click="active(tag.id, tag.isSelectd)"
>
<span>{{ tag.name }}</span>
</p>
</div>
<el-button
v-show="!isInput"
type="primary"
class="addbtn"
@click="isInput = !isInput"
>+ 添加标签
</el-button>
<div class="tag-input" v-show="isInput">
<input
ref="editTaginput"
type="text"
placeholder="回车保存..."
v-model.trim="tagText"
@keyup.enter="save"
/>
<el-button type="primary" size="small" @click="isInput = false"
>取消编辑
</el-button>
</div>
</div>
</template>
<script>
import { ServeUpdateArticleTag, ServeEditArticleTag } from '@/api/article'
export default {
name: 'NoteTagBox',
props: {
note_id: {
type: Number,
default: 0,
},
tag_ids: {
type: Array,
default() {
return []
},
},
},
data() {
return {
ids: [],
isInput: false,
tagText: '',
}
},
created() {
this.ids = this.tag_ids.map(tag => tag.id)
},
computed: {
tags() {
let tags = []
this.$store.state.note.tags.forEach(tag => {
tags.push({
id: tag.id,
name: tag.tag_name,
isSelectd: this.ids.includes(tag.id),
})
})
return tags
},
},
methods: {
// 设置笔记标签事件
active(tag_id, isSelect) {
if (isSelect) {
this.ids.forEach((item, index) => {
if (item == tag_id) {
this.ids.splice(index, 1)
}
})
} else {
this.ids.push(tag_id)
}
ServeUpdateArticleTag({
article_id: this.note_id,
tags: this.getSelectTags(),
})
},
// 获取选中的标签ids
getSelectTags() {
let ids = []
for (let item of this.tags) {
if (item.isSelectd) ids.push(item.id)
}
return ids
},
// 保存标签事件
save() {
let tag_name = this.tagText
ServeEditArticleTag({
tag_id: 0,
tag_name,
}).then(({ code, data }) => {
if (code !== 200) return false
this.$store.commit('PUSH_NOTE_TAG', {
id: data.id,
tag_name: tag_name,
count: 0,
})
this.tagText = ''
this.isInput = false
})
},
},
}
</script>
<style lang="less" scoped>
.tag-manage {
.title {
height: 20px;
line-height: 20px;
font-size: 14px;
color: #ccc;
border-bottom: 1px solid #f0e9e9;
padding-bottom: 5px;
position: relative;
}
.tag-groups {
padding: 8px 8px 8px 0;
cursor: pointer;
.tag-item {
display: inline-block;
height: 25px;
line-height: 25px;
padding: 0 10px;
font-size: 12px;
box-sizing: border-box;
white-space: nowrap;
margin: 0 3px 5px 0;
color: #409eff;
background: rgba(64, 158, 255, 0.1);
border-radius: 1px;
i {
cursor: pointer;
margin-left: 5px;
}
&.active {
background: #70b5fb;
color: #ffffff;
}
}
}
.addbtn {
height: 33px;
width: 100%;
margin-top: 20px;
line-height: 9px;
border-radius: 3px;
}
.tag-input {
margin-top: 20px;
min-height: 30px;
display: flex;
align-items: center;
input {
width: 200px;
height: 30px;
border: 1px solid #66b1ff;
padding: 0 5px;
border-radius: 3px;
margin-right: 5px;
&::-webkit-input-placeholder {
font-size: 13px;
color: #ccc;
}
}
}
}
</style>

View File

@@ -1,38 +0,0 @@
<template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'svg-icon',
props: {
iconClass: {
type: String,
required: true,
},
className: {
type: String,
default: '',
},
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
},
}
</script>
<style scoped>
.svg-icon {
fill: currentColor;
overflow: hidden;
}
</style>

View File

@@ -1,529 +0,0 @@
<template>
<div v-show="isShow" class="lum-dialog-mask animated fadeIn">
<el-container class="container" v-outside="close">
<el-header class="no-padding header" height="180px">
<i class="close el-icon-error pointer" @click="close" />
<div class="img-banner">
<img :src="userInfo.imgbag" class="img-banner" />
</div>
<div class="user-header">
<div class="avatar">
<div class="avatar-box">
<img
:src="userInfo.avatar"
:onerror="$store.state.detaultAvatar"
/>
</div>
</div>
<div class="nickname">
<i class="iconfont icon-qianming" />
<span>{{ userInfo.nickname || '未设置昵称' }}</span>
<div class="share no-select" @click="contacts = true">
<i class="iconfont icon-fenxiang3" /> <span>分享</span>
</div>
</div>
</div>
</el-header>
<el-main class="no-padding main">
<div class="user-sign">
<div class="sign-arrow"></div>
<i class="iconfont icon-bianji" />
<span>编辑个签展示我的独特态度 </span>
</div>
<div class="card-rows no-select">
<div class="card-row">
<label>手机</label>
<span>{{ mobile(userInfo.mobile) }}</span>
</div>
<div class="card-row">
<label>昵称</label>
<span>{{ userInfo.nickname || '未设置昵称' }}</span>
</div>
<div class="card-row">
<label>性别</label>
<span v-if="userInfo.gender == 1"></span>
<span v-else-if="userInfo.gender == 2"></span>
<span v-else>未知</span>
</div>
<div v-show="userInfo.friendStatus == 2" class="card-row">
<label>备注</label>
<span v-if="!editRemark.isShow">
{{
userInfo.nicknameRemark ? userInfo.nicknameRemark : '暂无备注'
}}
</span>
<span v-else>
<input
v-model="editRemark.text"
v-focus
class="friend-remark"
type="text"
@keyup.enter="editRemarkSubmit"
/>
</span>
<i
v-show="!editRemark.isShow"
class="el-icon-edit-outline"
@click="clickEditRemark"
/>
</div>
<div class="card-row">
<label>邮箱</label>
<span>未设置</span>
</div>
</div>
</el-main>
<el-footer
v-show="userInfo.friendStatus !== 0"
class="no-padding footer"
height="50px"
>
<el-button
v-if="userInfo.friendStatus == 1 && userInfo.friendApply == 0"
type="primary"
size="small"
icon="el-icon-circle-plus-outline"
@click="applyFrom.isShow = true"
>添加好友
</el-button>
<el-button
v-else-if="userInfo.friendApply == 1"
type="primary"
size="small"
>已发送好友申请请耐心等待...
</el-button>
<el-button
v-else-if="userInfo.friendStatus == 2"
type="primary"
size="small"
icon="el-icon-s-promotion"
@click="sendMessage(userInfo)"
>发消息
</el-button>
</el-footer>
<!-- 添加好友申请表单 -->
<div
v-outside="closeApplyFrom"
class="friend-from"
:class="{ 'friend-from-show': applyFrom.isShow }"
>
<p>
<span>请填写好友申请备注</span>
<span @click="closeApplyFrom">取消</span>
</p>
<div>
<input
v-model="applyFrom.text"
type="text"
placeholder="(必填项)"
@keyup.enter="sendApply"
/>
<el-button type="primary" size="small" @click="sendApply">
立即提交
</el-button>
</div>
</div>
</el-container>
<UserContacts
v-if="contacts"
@confirm="confirmContact"
@close="contacts = false"
/>
</div>
</template>
<script>
import UserContacts from '@/components/chat/UserContacts'
import { ServeCreateTalkList } from '@/api/chat'
import { ServeSearchUser } from '@/api/user'
import { ServeCreateContact, ServeEditContactRemark } from '@/api/contacts'
export default {
name: 'UserBusinessCard',
components: {
UserContacts,
},
data() {
return {
isShow: false,
// 用户ID
user_id: 0,
// 用户相关信息
userInfo: {
mobile: '',
nickname: '',
avatar: '',
motto: '',
friendStatus: 0,
friendApply: 0,
nicknameRemark: '',
imgbag: require('@/assets/image/default-user-banner.png'),
gender: 0, //[0:未知;1:男;2:女;默认0]
},
// 好友备注表单
editRemark: {
isShow: false,
text: '',
},
// 好友申请表单
applyFrom: {
isShow: false,
text: '',
},
contacts: false,
}
},
methods: {
// 显示窗口
open(user_id) {
this.isShow = true
this.user_id = user_id
this.findUserDetail()
},
// 关闭窗口
close() {
if (this.contacts) {
return false
}
this.isShow = false
},
// 手机号格式化
mobile(mobile) {
return (
mobile.substr(0, 3) +
' ' +
mobile.substr(3, 4) +
' ' +
mobile.substr(7, 4)
)
},
// 点击编辑备注信息
clickEditRemark() {
this.editRemark.isShow = true
this.editRemark.text = this.userInfo.nicknameRemark
},
// 获取用户信息
findUserDetail() {
ServeSearchUser({
user_id: this.user_id,
}).then(res => {
if (res.code == 200) {
let data = res.data
this.userInfo.user_id = data.id
this.userInfo.mobile = data.mobile
this.userInfo.nickname = data.nickname
this.userInfo.nicknameRemark = data.nickname_remark
this.userInfo.motto = data.motto
this.userInfo.avatar = data.avatar
this.userInfo.friendStatus = data.friend_status
this.userInfo.friendApply = data.friend_apply
this.userInfo.gender = data.gender
}
})
},
// 发送添加好友申请
sendApply() {
if (this.applyFrom.text == '') return
ServeCreateContact({
friend_id: this.userInfo.user_id,
remarks: this.applyFrom.text,
}).then(res => {
if (res.code == 200) {
this.applyFrom.isShow = false
this.applyFrom.text = ''
this.userInfo.friendApply = 1
} else {
alert('发送好友申请失败,请稍后再试...')
}
})
},
// 编辑好友备注信息
editRemarkSubmit() {
let data = {
friend_id: this.userInfo.user_id,
remarks: this.editRemark.text,
}
if (data.remarks == this.userInfo.nicknameRemark) {
this.editRemark.isShow = false
return
}
ServeEditContactRemark(data).then(res => {
if (res.code == 200) {
this.editRemark.isShow = false
this.userInfo.nicknameRemark = data.remarks
this.$emit('changeRemark', data)
}
})
},
// 隐藏申请表单
closeApplyFrom() {
this.applyFrom.isShow = false
},
// 发送好友消息
sendMessage() {
let userInfo = this.userInfo
ServeCreateTalkList({
type: 1,
receive_id: this.user_id,
}).then(res => {
if (res.code !== 200) return
this.$root.dumpTalkPage(`1_${userInfo.user_id}`)
})
},
confirmContact(array) {
this.contacts = false
this.$notify.info({
title: '消息',
message: '分享功能正在开发中...',
})
},
},
}
</script>
<style lang="less" scoped>
.container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: white;
width: 350px;
height: 600px;
overflow: hidden;
border-radius: 3px;
.header {
position: relative;
.close {
position: absolute;
right: 10px;
top: 10px;
color: white;
transition: all 1s;
z-index: 1;
font-size: 20px;
}
.img-banner {
width: 100%;
height: 100%;
background-image: url(~@/assets/image/default-user-banner.png);
background-size: 100%;
transition: all 0.2s linear;
cursor: pointer;
overflow: hidden;
img:hover {
-webkit-transform: scale(1.1);
transform: scale(1.1);
-webkit-filter: contrast(130%);
filter: contrast(130%);
}
}
}
.main {
background-color: white;
padding: 45px 16px 0;
}
.footer {
display: flex;
justify-content: center;
align-items: center;
border-top: 1px solid #f5eeee;
button {
width: 90%;
}
}
}
.user-header {
width: 100%;
height: 80px;
position: absolute;
bottom: -40px;
display: flex;
flex-direction: row;
.avatar {
width: 100px;
flex-shrink: 0;
display: flex;
justify-content: center;
.avatar-box {
width: 80px;
height: 80px;
background-color: white;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
img {
height: 70px;
width: 70px;
border-radius: 50%;
}
}
}
.nickname {
flex: auto;
padding-top: 50px;
font-size: 16px;
font-weight: 400;
span {
margin-left: 5px;
}
.share {
display: inline-flex;
width: 50px;
height: 22px;
background: #ff5722;
color: white;
align-items: center;
justify-content: center;
padding: 3px 8px;
border-radius: 20px;
transform: scale(0.7);
cursor: pointer;
i {
margin-top: 2px;
}
span {
font-size: 14px;
margin-left: 4px;
}
}
}
}
.user-sign {
min-height: 26px;
border-radius: 5px;
padding: 5px;
line-height: 25px;
background: #f3f5f7;
color: #7d7d7d;
font-size: 12px;
margin-bottom: 20px;
position: relative;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
position: relative;
.sign-arrow {
position: absolute;
width: 0;
height: 0;
font-size: 0;
border: 5px solid hsla(0, 0%, 96.9%, 0);
border-bottom-color: #f3f5f7;
left: 28px;
top: -9px;
}
}
.card-rows {
.card-row {
height: 35px;
line-height: 35px;
font-size: 14px;
position: relative;
cursor: pointer;
color: #736f6f;
label {
margin-right: 25px;
color: #cbc5c5;
}
.friend-remark {
border-bottom: 1px dashed #bec3d0;
padding-bottom: 2px;
color: #736f6f;
width: 60%;
padding-right: 5px;
}
.el-icon-edit-outline {
margin-left: 3px !important;
}
}
}
/* 好友申请表单 */
.friend-from {
position: absolute;
background: #fbf6f6;
height: 80px;
z-index: 2;
width: 100%;
bottom: -80px;
left: 0;
transition: all 0.5s ease-in-out;
p {
height: 20px;
line-height: 20px;
padding: 7px 5px 5px 15px;
font-size: 13px;
span {
&:nth-child(2) {
float: right;
margin-right: 13px;
color: #32caff;
cursor: pointer;
}
}
}
div {
height: 31px;
line-height: 20px;
padding: 7px 5px 5px 15px;
font-size: 13px;
}
input {
height: 30px;
line-height: 30px;
width: 220px;
border-radius: 3px;
padding: 0 5px;
margin-right: 5px;
}
}
.friend-from-show {
bottom: 0;
}
</style>

View File

@@ -1,139 +0,0 @@
<template>
<div>
<div class="user-card animated fadeIn">
<div class="card-header">
<img :src="$store.state.user.visitCardBag" class="no-select" />
<div class="user-avatar no-select">
<img
:src="$store.state.user.avatar"
:onerror="$store.state.detaultAvatar"
/>
</div>
<div class="user-nickname">
<i class="iconfont icon-qianming" />
<span v-text="$store.state.user.nickname"></span>
</div>
</div>
<div class="card-main">
<div class="usersign">
<div class="arrow"></div>
<span v-if="$store.state.user.signature">
<span style="font-weight: 600">个性签名</span>
{{ $store.state.user.signature }}
</span>
<span v-else>
<i class="iconfont icon-bianji" />
<span>编辑个签展示我的独特态度</span>
</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'UserCard',
}
</script>
<style lang="less" scoped>
.user-card {
width: 320px;
min-height: 300px;
background: #ffffff;
box-shadow: -1px 1px 9px 0px #e6e3e3;
padding-bottom: 10px;
.card-header {
height: 230px;
overflow: hidden;
img {
width: 100%;
height: 180px;
-webkit-transition: all 0.2s linear;
transition: all 0.2s linear;
&:hover {
-webkit-transform: scale(1.1);
transform: scale(1.1);
-webkit-filter: contrast(130%);
filter: contrast(130%);
}
}
.user-avatar {
height: 70px;
width: 70px;
border: 5px solid #fff;
background-color: #fff;
border-radius: 50%;
position: relative;
top: -35px;
margin-left: 15px;
img {
width: 100%;
height: 100%;
border-radius: 50%;
cursor: pointer;
}
}
.user-nickname {
position: relative;
top: -72px;
text-align: left;
margin-left: 105px;
margin-right: 5px;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
span {
font-size: 16px;
font-weight: 400;
margin-left: 8px;
}
}
}
}
.user-card .card-main {
margin-top: 10px;
min-height: 50px;
text-align: left;
padding: 0 16px;
.usersign {
min-height: 26px;
border-radius: 5px;
padding: 5px;
line-height: 25px;
background: #f3f5f7;
color: #7d7d7d;
font-size: 12px;
margin-bottom: 20px;
position: relative;
cursor: pointer;
.icon-bianji {
margin-left: 10px;
}
.arrow {
position: absolute;
width: 0;
height: 0;
font-size: 0;
border: solid 5px;
top: 5px;
border-color: rgba(247, 247, 247, 0) rgba(247, 247, 247, 0) #f3f5f7
rgba(247, 247, 247, 0);
top: -10px;
left: 31px;
}
}
}
</style>

View File

@@ -1,223 +0,0 @@
<template>
<div class="lum-dialog-mask" v-show="isShow">
<el-container class="lum-dialog-box" v-outside="close">
<el-header class="header" height="50px">
<p>绑定邮箱</p>
<p class="tools">
<i class="el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item prop="email" label="邮箱">
<el-input
v-model="form.email"
class="cuborder-radius"
size="medium"
placeholder="请填写邮箱地址"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item prop="sms_code" label="验证码">
<el-input
v-model="form.sms_code"
class="cuborder-radius"
style="width: 185px"
size="medium"
maxlength="6"
placeholder="邮件验证码"
@keyup.enter.native="onSubmit('form')"
/>
<div v-if="smsLock" class="code-btn disable">正在发送 ...</div>
<div
v-else-if="smsLock == false && smsLockObj.time == null"
class="code-btn"
@click="sendSms"
>
获取验证码
</div>
<div v-else class="code-btn disable">
重新发送({{ smsLockObj.time }}s)
</div>
</el-form-item>
<el-form-item prop="password" label="密码">
<el-input
v-model="form.password"
class="cuborder-radius no-border"
type="password"
size="medium"
placeholder="登录密码验证"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item style="margin-top: 40px">
<el-button
class="submit-btn"
type="primary"
size="medium"
:loading="loading"
@click="onSubmit('form')"
>立即修改
</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import SmsLock from '@/plugins/sms-lock'
import { ServeSendEmailCode, ServeUpdateEmail } from '@/api/user'
import { isEmail } from '@/utils/validate'
export default {
name: 'UserEditEmail',
data() {
return {
loading: false,
form: {
email: '',
password: '',
sms_code: '',
},
rules: {
email: [
{
required: true,
message: '请输入邮箱地址',
trigger: 'blur',
},
{
type: 'email',
message: '请输入正确的邮箱地址',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '登录密码不能为空!',
trigger: 'blur',
},
],
sms_code: [
{
required: true,
message: '验证码不能为空!',
trigger: 'blur',
},
],
},
smsLock: false,
smsLockObj: null,
isShow: false,
}
},
created() {
this.smsLockObj = new SmsLock('CHANGE_EMAIL_SMS', 120)
},
destroyed() {
this.smsLockObj.clearInterval()
},
methods: {
// 显示窗口
open() {
this.$refs['form'].resetFields()
this.isShow = true
},
// 关闭窗口
close() {
this.isShow = false
},
//点击发送邮件验证码
sendSms() {
if (!isEmail(this.form.email)) {
this.$refs.form.validateField('email')
return false
}
this.smsLock = true
ServeSendEmailCode({
email: this.form.email,
})
.then(res => {
if (res.code == 200) {
this.smsLockObj.start()
}
})
.finally(() => {
this.smsLock = false
})
},
// 表单验证
onSubmit(formName) {
this.$refs[formName].validate(valid => {
if (!valid) return false
this.changeEmail()
})
},
// 提交修改邮箱
changeEmail() {
this.loading = true
ServeUpdateEmail({
email: this.form.email,
email_code: this.form.sms_code,
password: this.form.password,
})
.then(res => {
if (res.code == 200) {
this.$refs['form'].resetFields()
this.$emit('success')
this.close()
this.$notify({
title: '成功',
message: '修改邮箱成功...',
type: 'success',
})
} else {
this.$message(res.message)
}
})
.finally(() => {
this.loading = false
})
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 450px;
max-width: 450px;
.main {
.code-btn {
width: 140px;
height: 36px;
line-height: 36px;
display: inline-block;
background: #f3ecec;
text-align: center;
color: #777373;
cursor: pointer;
user-select: none;
margin-left: 5px;
&:active {
background: #e4dbdb;
}
&.disable {
cursor: not-allowed !important;
background: #f7f7f7 !important;
color: silver !important;
}
}
}
}
</style>

View File

@@ -1,254 +0,0 @@
<template>
<div v-show="isShow" class="lum-dialog-mask">
<el-container class="lum-dialog-box" v-outside="close">
<el-header class="header" height="50px">
<p>绑定手机</p>
<p class="tools">
<i class="close-btn el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item prop="username" label="手机号">
<el-input
v-model="form.username"
class="cuborder-radius"
maxlength="11"
size="medium"
placeholder="请填写新手机号"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item prop="sms_code" label="验证码">
<el-input
v-model="form.sms_code"
class="cuborder-radius"
style="width: 185px"
maxlength="6"
size="medium"
placeholder="验证码"
@keyup.enter.native="onSubmit('form')"
/>
<div v-if="smsLock" class="send-code-btn disable">正在发送 ...</div>
<div
v-else-if="smsLock == false && smsLockObj.time == null"
class="send-code-btn"
@click="sendSms"
>
获取短信
</div>
<div v-else class="send-code-btn disable">
重新发送({{ smsLockObj.time }}s)
</div>
</el-form-item>
<el-form-item prop="password" label="密码">
<el-input
v-model="form.password"
class="cuborder-radius no-border"
type="password"
size="medium"
placeholder="登录密码验证"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item style="margin-top: 40px">
<el-button
class="submit-btn"
type="primary"
size="medium"
:loading="loading"
@click="onSubmit('form')"
>立即修改
</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { isMobile } from '@/utils/validate'
import SmsLock from '@/plugins/sms-lock'
import { ServeSendMobileCode, ServeUpdateMobile } from '@/api/user'
export default {
name: 'UserEditMobile',
data() {
let validateMobile = (rule, value, callback) => {
if (value === '') {
callback(new Error('登录手机号不能为空!'))
} else {
if (!isMobile(value)) {
callback(new Error('登录手机号格式不正确!'))
} else {
callback()
}
}
}
return {
loading: false,
form: {
username: '',
password: '',
sms_code: '',
},
rules: {
username: [
{
required: true,
validator: validateMobile,
trigger: 'blur',
},
{
min: 11,
max: 11,
message: '手机号格式不正确!',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '登录密码不能为空!',
trigger: 'blur',
},
],
sms_code: [
{
required: true,
message: '验证码不能为空!',
trigger: 'blur',
},
],
},
smsLock: false,
smsLockObj: null,
isShow: false,
}
},
created() {
this.smsLockObj = new SmsLock('EDIT_MOBILE_SMS', 120)
},
destroyed() {
this.smsLockObj.clearInterval()
},
methods: {
// 显示窗口
open() {
this.$refs['form'].resetFields()
this.isShow = true
},
// 关闭窗口
close() {
this.isShow = false
},
//点击发送验证码
sendSms() {
if (!isMobile(this.form.username)) {
this.$refs.form.validateField('username')
return false
}
this.smsLock = true
ServeSendMobileCode({
mobile: this.form.username,
})
.then(res => {
if (res.code !== 200) {
this.$notify({
title: '提示',
message: res.message,
})
return
}
this.smsLockObj.start()
if (res.data.is_debug) {
this.form.sms_code = res.data.sms_code
setTimeout(() => {
this.$notify({
title: '提示',
message: '已自动填充验证码',
type: 'success',
})
this.form.sms_code = res.data.sms_code
}, 500)
}
})
.finally(() => {
this.smsLock = false
})
},
// 表单验证
onSubmit(formName) {
this.$refs[formName].validate(valid => {
if (!valid) return false
this.changeMobile()
})
},
// 提交修改手机号
changeMobile() {
this.loading = true
ServeUpdateMobile({
mobile: this.form.username,
sms_code: this.form.sms_code,
password: this.form.password,
})
.then(res => {
if (res.code == 200) {
this.$refs['form'].resetFields()
this.$notify({
title: '成功',
message: '更换手机号成功...',
type: 'success',
})
this.$emit('success')
this.close()
} else {
this.$message(res.message)
}
})
.finally(() => {
this.loading = false
})
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 450px;
max-width: 450px;
.main {
.send-code-btn {
width: 140px;
height: 36px;
line-height: 36px;
display: inline-block;
background: #f3ecec;
text-align: center;
color: #777373;
cursor: pointer;
user-select: none;
margin-left: 5px;
&:active {
background: #e4dbdb;
}
&.disable {
cursor: not-allowed !important;
background: #f7f7f7 !important;
color: silver !important;
}
}
}
}
</style>

View File

@@ -1,161 +0,0 @@
<template>
<!-- 用户密码修改组件 -->
<div class="lum-dialog-mask" v-show="isShow">
<el-container class="lum-dialog-box" v-outside="close">
<el-header class="header" height="50px">
<p>修改密码</p>
<p class="tools">
<i class="el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item prop="old_password" label="旧密码">
<el-input
v-model="form.old_password"
class="cuborder-radius no-border"
type="password"
size="medium"
placeholder="请填写旧密码"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item prop="new_password" label="新密码">
<el-input
v-model="form.new_password"
class="cuborder-radius no-border"
type="password"
size="medium"
placeholder="请填写新的密码"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item prop="new_password2" label="重复密码">
<el-input
v-model="form.new_password2"
class="cuborder-radius no-border"
size="medium"
type="password"
placeholder="请再次填写新密码"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item style="margin-top: 40px">
<el-button
class="submit-btn"
type="primary"
size="medium"
:loading="loading"
@click="onSubmit('form')"
>立即修改
</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { ServeUpdatePassword } from '@/api/user'
export default {
name: 'UserEditPassword',
data() {
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== this.form.new_password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
return {
loading: false,
form: {
old_password: '',
new_password: '',
new_password2: '',
},
rules: {
old_password: [
{
required: true,
message: '旧密码不能为空!',
trigger: 'blur',
},
],
new_password: [
{
required: true,
message: '新密码不能为空!',
trigger: 'blur',
},
],
new_password2: [
{
required: true,
validator: validatePass2,
trigger: 'blur',
},
],
},
isShow: false,
}
},
methods: {
// 显示窗口
open() {
this.$refs['form'].resetFields()
this.isShow = true
},
// 关闭窗口
close() {
this.isShow = false
},
// 表单验证
onSubmit(formName) {
this.$refs[formName].validate(valid => {
if (!valid) return false
this.changePassword()
})
},
// 提交修改手机号
changePassword() {
this.loading = true
ServeUpdatePassword({
old_password: this.form.old_password,
new_password: this.form.new_password,
})
.then(res => {
if (res.code == 200) {
this.$refs['form'].resetFields()
this.$notify({
title: '成功',
message: '密码修改成功...',
type: 'success',
})
} else {
this.$message(res.message)
}
this.loading = false
})
.catch(() => {
this.loading = false
})
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 450px;
max-width: 450px;
}
</style>

View File

@@ -1,126 +0,0 @@
<template>
<div>
<div class="lum-dialog-mask" v-show="isShow">
<el-container class="lum-dialog-box" v-outside="close">
<el-header class="header" height="50px">
<p>添加好友</p>
<p class="tools">
<i class="el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main">
<el-input
v-model="mobile"
id="serach-mobile"
class="input"
prefix-icon="el-icon-search"
placeholder="请输入对方手机号(精确查找)"
clearable
@keyup.enter.native="onSubmit"
@input="error = false"
/>
<p v-show="error" class="error">
无法找到该用户请检查搜索内容并重试
</p>
<el-button
type="primary"
size="small"
:loading="loading"
@click="onSubmit"
>立即查找
</el-button>
</el-main>
</el-container>
</div>
<!-- 查看好友用户信息 -->
<UserBusinessCard ref="userBusinessCard" />
</div>
</template>
<script>
import { ServeSearchContact } from '@/api/contacts'
import UserBusinessCard from '@/components/user/UserBusinessCard'
export default {
name: 'UserSearch',
components: {
UserBusinessCard,
},
data() {
return {
loading: false,
isShow: false,
mobile: '',
error: false,
}
},
methods: {
// 显示窗口
open() {
this.mobile = ''
this.isShow = true
this.$nextTick(() => {
document.getElementById('serach-mobile').focus()
})
},
// 关闭窗口
close() {
this.isShow = false
},
onSubmit() {
let { mobile } = this
if (mobile == '') return false
this.loading = true
ServeSearchContact({
mobile,
})
.then(res => {
if (res.code == 200) {
this.$refs.userBusinessCard.open(res.data.id)
this.close()
} else {
this.error = true
}
})
.finally(() => {
this.loading = false
})
},
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 450px;
max-width: 450px;
height: 250px;
.main {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.input {
width: 85%;
}
.error {
width: 85%;
color: red;
font-size: 12px;
height: 50px;
line-height: 50px;
}
button {
margin-top: 20px;
width: 100px;
}
}
}
</style>

View File

@@ -1,7 +0,0 @@
export default {
WEBSITE_NAME: process.env.VUE_APP_WEBSITE_NAME || 'CPS',
BASE_API_URL: process.env.VUE_APP_API_BASE_URL || '',
BASE_WWW_URL: process.env.VUE_APP_WWW_BASE_URL || '',
BASE_WS_URL: process.env.VUE_APP_WEB_SOCKET_URL || '',
WS_DEBUG: false,
}

View File

@@ -1,49 +0,0 @@
import Vue from 'vue'
import Clickoutside from 'element-ui/src/utils/clickoutside'
// 自定义聚焦指令
Vue.directive('focus', {
inserted(el) {
el.focus()
},
})
// 自定义粘贴指令
Vue.directive('paste', {
bind(el, binding, vnode) {
el.addEventListener('paste', function(event) {
//这里直接监听元素的粘贴事件
binding.value(event)
})
},
})
// 自定义拖拽指令
Vue.directive('drag', {
bind(el, binding, vnode) {
// 因为拖拽还包括拖动时的经过事件,离开事件,和进入事件,放下事件,
// 浏览器对于拖拽的默认事件的处理是打开拖进来的资源,
// 所以要先对这三个事件进行默认事件的禁止
el.addEventListener('dragenter', function(event) {
event.stopPropagation()
event.preventDefault()
})
el.addEventListener('dragover', function(event) {
event.stopPropagation()
event.preventDefault()
})
el.addEventListener('dragleave', function(event) {
event.stopPropagation()
event.preventDefault()
})
el.addEventListener('drop', function(event) {
// 这里阻止默认事件,并绑定事件的对象,用来在组件上返回事件对象
event.stopPropagation()
event.preventDefault()
binding.value(event)
})
},
})
// 点击其他地方隐藏指令
Vue.directive('outside', Clickoutside)

View File

@@ -1,2 +0,0 @@
import Vue from 'vue'

View File

@@ -1,22 +0,0 @@
/**
* Custom icon list
* All icons are loaded here for easy management
*
* 自定义图标加载表
* 所有图标均从这里加载,方便管理
*/
import SvgMentionDown from '@/icons/svg/mention-down.svg?inline' // path to your '*.svg?inline' file.
import SvgNotFount from '@/icons/svg/not-fount.svg?inline' // path to your '*.svg?inline' file.
import SvgNote from '@/icons/svg/note.svg?inline' // path to your '*.svg?inline' file.
import SvgNoteBook from '@/icons/svg/note-book.svg?inline' // path to your '*.svg?inline' file.
import SvgNotData from '@/icons/svg/not-data.svg?inline' // path to your '*.svg?inline' file.
import SvgZhuangFa from '@/icons/svg/zhuangfa.svg?inline' // path to your '*.svg?inline' file.
export {
SvgMentionDown,
SvgNotFount,
SvgNote,
SvgNoteBook,
SvgNotData,
SvgZhuangFa,
}

View File

@@ -1,121 +0,0 @@
import Vue from 'vue'
import 'element-ui/lib/theme-chalk/index.css'
import {
Notification,
Popover,
Switch,
Dropdown,
DropdownMenu,
DropdownItem,
Message,
Container,
Header,
Aside,
Main,
Footer,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Button,
Image,
Loading,
Row,
Col,
MessageBox,
Form,
FormItem,
Input,
Divider,
Link,
Tooltip,
Autocomplete,
Scrollbar,
Avatar,
Radio,
RadioGroup,
Progress,
Dialog,
Pagination,
Table,
TableColumn,
Tag,
Select,
Option,
Upload,
CheckboxGroup,
Checkbox,
Tabs,
TabPane,
RadioButton,
Alert,
Card,
Tree,
TimePicker,
DatePicker,
Badge,
} from 'element-ui'
Vue.use(DatePicker)
Vue.use(TimePicker)
Vue.use(Badge)
Vue.use(Tree)
Vue.use(Card)
Vue.use(Alert)
Vue.use(RadioButton)
Vue.use(Tabs)
Vue.use(TabPane)
Vue.use(CheckboxGroup)
Vue.use(Checkbox)
Vue.use(Upload)
Vue.use(Option)
Vue.use(Select)
Vue.use(Tag)
Vue.use(Pagination)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Popover)
Vue.use(Switch)
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Button)
Vue.use(Image)
Vue.use(Row)
Vue.use(Col)
Vue.use(Input)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Divider)
Vue.use(Link)
Vue.use(Tooltip)
Vue.use(Autocomplete)
Vue.use(Scrollbar)
Vue.use(Avatar)
Vue.use(Radio)
Vue.use(RadioGroup)
Vue.use(Progress)
Vue.use(Dialog)
Vue.use(Loading.directive)
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$alert = MessageBox.alert
import Contextmenu from 'vue-contextmenujs'
Vue.use(Contextmenu)
process.env.NODE_ENV !== 'production' &&
console.warn('[Lumen-IM] NOTICE: element-ui use lazy-load.')

View File

@@ -1,47 +0,0 @@
import { copyTextToClipboard as Clipboard } from '@/utils/functions'
const copyFunc = (pre, text) => {
let el = document.createElement('p')
el.className = 'fz-btn'
el.innerText = '复制'
el.onclick = () => {
Clipboard(text.replace(/(^\s*)|(\s*$)/g, ''), function() {
el.innerText = '复制成功!'
setTimeout(() => {
el.innerText = '复制'
}, 1000)
})
}
pre.appendChild(el)
}
const preNmae = (pre, lang) => {
let el = document.createElement('p')
el.className = 'lang-name'
el.innerText = lang
pre.appendChild(el)
}
function updateNodes(el, binding, vnode) {
let preNodes = el.querySelectorAll('pre')
preNodes.forEach(elPre => {
let elCode = elPre.querySelector('code')
let className = elCode.className
let language = className.split('-')[1]
copyFunc(elPre, elCode.innerText)
if (language != undefined) {
preNmae(elPre, language)
}
})
}
/**
* 代码格式化
*/
export default {
bind: updateNodes,
update: updateNodes,
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -1,9 +0,0 @@
import Vue from 'vue';
import SvgIcon from '@/components/svg-icon'; // svg component
// register globally
Vue.component('svg-icon', SvgIcon);
const req = require.context('./svg', false, /\.svg$/);
const requireAll = requireContext => requireContext.keys().map(requireContext);
requireAll(req);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Some files were not shown because too many files have changed in this diff Show More