提交后台前端基础框架
130
Backend/src/$ws.js
Executable file
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
})*/
|
||||
48
Backend/src/App.vue
Executable file
@@ -0,0 +1,48 @@
|
||||
<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>
|
||||
130
Backend/src/api/article.js
Executable file
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
}
|
||||
17
Backend/src/api/device.js
Normal file
@@ -0,0 +1,17 @@
|
||||
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)
|
||||
}
|
||||
31
Backend/src/api/emoticon.js
Executable file
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
}
|
||||
61
Backend/src/api/product.js
Normal file
@@ -0,0 +1,61 @@
|
||||
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 )
|
||||
}
|
||||
13
Backend/src/api/role.js
Executable file
@@ -0,0 +1,13 @@
|
||||
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)
|
||||
}
|
||||
5
Backend/src/api/server.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export const ServerIndex = data => {
|
||||
return post('/manage/server/index', data)
|
||||
}
|
||||
22
Backend/src/api/statistics.js
Normal file
@@ -0,0 +1,22 @@
|
||||
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)
|
||||
}
|
||||
29
Backend/src/api/task.js
Normal file
@@ -0,0 +1,29 @@
|
||||
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)
|
||||
}
|
||||
35
Backend/src/api/user.js
Executable file
@@ -0,0 +1,35 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
// 登录服务接口
|
||||
export const ServeLogin = data => {
|
||||
return post('/backend/user/login', data)
|
||||
}
|
||||
|
||||
export const ServeGetUser = () => {
|
||||
return post('/backend/user/get')
|
||||
}
|
||||
|
||||
export const ServeSetUserPassword = (data) => {
|
||||
return post('/backend/user/password', data)
|
||||
}
|
||||
|
||||
// 退出登录服务接口
|
||||
export const ServeLogout = data => {
|
||||
return post('/backend/user/logout', data)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
7
Backend/src/assets/css/common.less
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
.tk_header{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
}
|
||||
158
Backend/src/assets/css/global.less
Executable file
@@ -0,0 +1,158 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
991
Backend/src/assets/css/markdown.css
Executable file
@@ -0,0 +1,991 @@
|
||||
.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;
|
||||
}
|
||||
281
Backend/src/assets/css/page/contacts.less
Executable file
@@ -0,0 +1,281 @@
|
||||
.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);
|
||||
}
|
||||
}
|
||||
198
Backend/src/assets/css/page/login-auth.less
Executable file
@@ -0,0 +1,198 @@
|
||||
/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);
|
||||
}
|
||||
}
|
||||
328
Backend/src/assets/css/page/note-page.less
Executable file
@@ -0,0 +1,328 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Backend/src/assets/css/reset.css
Executable file
@@ -0,0 +1,62 @@
|
||||
* {
|
||||
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;
|
||||
}
|
||||
50
Backend/src/assets/css/talk/talk-records.less
Executable file
@@ -0,0 +1,50 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Backend/src/assets/css/variable.less
Executable file
@@ -0,0 +1,30 @@
|
||||
//主题皮肤 - 预留功能
|
||||
: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);
|
||||
BIN
Backend/src/assets/image/1701.mp3
Executable file
BIN
Backend/src/assets/image/59y888piCn92.mp3
Executable file
BIN
Backend/src/assets/image/RaJik9TWDi.png
Executable file
|
After Width: | Height: | Size: 78 KiB |
BIN
Backend/src/assets/image/aliyun-abs.jpg
Executable file
|
After Width: | Height: | Size: 117 KiB |
BIN
Backend/src/assets/image/background/001.jpg
Executable file
|
After Width: | Height: | Size: 830 KiB |
BIN
Backend/src/assets/image/background/002.jpg
Executable file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
Backend/src/assets/image/background/003.jpg
Executable file
|
After Width: | Height: | Size: 789 KiB |
BIN
Backend/src/assets/image/background/004.jpg
Executable file
|
After Width: | Height: | Size: 114 KiB |
BIN
Backend/src/assets/image/background/005.png
Executable file
|
After Width: | Height: | Size: 671 KiB |
BIN
Backend/src/assets/image/chat-search-no-message.png
Executable file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
Backend/src/assets/image/chat.png
Executable file
|
After Width: | Height: | Size: 151 KiB |
BIN
Backend/src/assets/image/default-user-banner.png
Executable file
|
After Width: | Height: | Size: 192 KiB |
BIN
Backend/src/assets/image/detault-avatar.jpg
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Backend/src/assets/image/gitee-avatar.jpg
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
Backend/src/assets/image/github-avatar.jpg
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
Backend/src/assets/image/icon_face.png
Executable file
|
After Width: | Height: | Size: 758 B |
BIN
Backend/src/assets/image/icon_heart.png
Executable file
|
After Width: | Height: | Size: 882 B |
BIN
Backend/src/assets/image/no-oncall.6b776fcf.png
Executable file
|
After Width: | Height: | Size: 13 KiB |
BIN
Backend/src/assets/image/obj_w5zD.mp3
Executable file
441
Backend/src/components/editor/MeEditor.vue
Executable file
@@ -0,0 +1,441 @@
|
||||
<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>
|
||||
348
Backend/src/components/editor/MeEditorEmoticon.vue
Executable file
@@ -0,0 +1,348 @@
|
||||
<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>
|
||||
440
Backend/src/components/editor/MeEditorFileManage.vue
Executable file
@@ -0,0 +1,440 @@
|
||||
<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>
|
||||
133
Backend/src/components/editor/MeEditorImageView.vue
Executable file
@@ -0,0 +1,133 @@
|
||||
<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>
|
||||
518
Backend/src/components/editor/MeEditorRecorder.vue
Executable file
@@ -0,0 +1,518 @@
|
||||
<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>
|
||||
169
Backend/src/components/editor/MeEditorSystemEmoticon.vue
Executable file
@@ -0,0 +1,169 @@
|
||||
<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>
|
||||
49
Backend/src/components/global/Empty.vue
Executable file
@@ -0,0 +1,49 @@
|
||||
<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>
|
||||
313
Backend/src/components/global/Loading.vue
Executable file
@@ -0,0 +1,313 @@
|
||||
<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>
|
||||
448
Backend/src/components/group/GroupLaunch.vue
Executable file
@@ -0,0 +1,448 @@
|
||||
<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>
|
||||
926
Backend/src/components/group/GroupManage.vue
Executable file
@@ -0,0 +1,926 @@
|
||||
<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>
|
||||
231
Backend/src/components/group/GroupNotice.vue
Executable file
@@ -0,0 +1,231 @@
|
||||
<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>
|
||||
817
Backend/src/components/group/GroupPanel.vue
Executable file
@@ -0,0 +1,817 @@
|
||||
<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> 邀请好友</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>
|
||||
80
Backend/src/components/layout/AbsModule.vue
Executable file
@@ -0,0 +1,80 @@
|
||||
<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>
|
||||
246
Backend/src/components/layout/AvatarCropper.vue
Executable file
@@ -0,0 +1,246 @@
|
||||
<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>
|
||||
129
Backend/src/components/layout/RewardModule.vue
Executable file
@@ -0,0 +1,129 @@
|
||||
<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>
|
||||
38
Backend/src/components/layout/SkinModule.vue
Executable file
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
57
Backend/src/components/layout/WelcomeModule.vue
Executable file
@@ -0,0 +1,57 @@
|
||||
<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>
|
||||
246
Backend/src/components/note/NoteAnnexBox.vue
Executable file
@@ -0,0 +1,246 @@
|
||||
<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>
|
||||
206
Backend/src/components/note/NoteAnnexRecycle.vue
Executable file
@@ -0,0 +1,206 @@
|
||||
<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>
|
||||
220
Backend/src/components/note/NoteTagBox.vue
Executable file
@@ -0,0 +1,220 @@
|
||||
<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>
|
||||
38
Backend/src/components/svg-icon/index.vue
Executable file
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
529
Backend/src/components/user/UserBusinessCard.vue
Executable file
@@ -0,0 +1,529 @@
|
||||
<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>
|
||||
139
Backend/src/components/user/UserCard.vue
Executable file
@@ -0,0 +1,139 @@
|
||||
<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>
|
||||
223
Backend/src/components/user/UserEditEmail.vue
Executable file
@@ -0,0 +1,223 @@
|
||||
<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>
|
||||
254
Backend/src/components/user/UserEditMobile.vue
Executable file
@@ -0,0 +1,254 @@
|
||||
<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>
|
||||
161
Backend/src/components/user/UserEditPassword.vue
Executable file
@@ -0,0 +1,161 @@
|
||||
<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>
|
||||
126
Backend/src/components/user/UserSearch.vue
Executable file
@@ -0,0 +1,126 @@
|
||||
<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>
|
||||
7
Backend/src/config/config.js
Executable file
@@ -0,0 +1,7 @@
|
||||
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,
|
||||
}
|
||||
49
Backend/src/core/directives.js
Executable file
@@ -0,0 +1,49 @@
|
||||
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)
|
||||
0
Backend/src/core/filter.js
Executable file
2
Backend/src/core/global-component.js
Executable file
@@ -0,0 +1,2 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
22
Backend/src/core/icons.js
Executable file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
121
Backend/src/core/lazy-use.js
Executable file
@@ -0,0 +1,121 @@
|
||||
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.')
|
||||
47
Backend/src/directive/PreCode.js
Executable file
@@ -0,0 +1,47 @@
|
||||
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,
|
||||
}
|
||||
BIN
Backend/src/icons/avatar-default.png
Executable file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
Backend/src/icons/avatar-room.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
Backend/src/icons/check.png
Executable file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Backend/src/icons/image-default.png
Executable file
|
After Width: | Height: | Size: 7.0 KiB |
9
Backend/src/icons/index.js
Executable file
@@ -0,0 +1,9 @@
|
||||
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);
|
||||
BIN
Backend/src/icons/link.png
Executable file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Backend/src/icons/logo.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
Backend/src/icons/main-client-down.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
Backend/src/icons/main-client-total.png
Executable file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
Backend/src/icons/main-client-up.png
Executable file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
Backend/src/icons/main-room-total.png
Executable file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
Backend/src/icons/main-room-up.png
Executable file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Backend/src/icons/map.png
Executable file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
Backend/src/icons/play.png
Executable file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Backend/src/icons/ranking1.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Backend/src/icons/ranking2.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Backend/src/icons/ranking3.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
3
Backend/src/icons/svg/mention-down.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg aria-hidden="true" viewBox="0 0 1024 1024" id="icon-mention-down">
|
||||
<path d="M661.333333 298.666667v213.333333h225.834667a21.333333 21.333333 0 0 1 12.970667 4.394667l2.112 1.856a21.333333 21.333333 0 0 1 1.770666 28.16l-1.770666 2.005333-375.168 375.168a21.333333 21.333333 0 0 1-28.16 1.770667l-2.005334-1.770667L121.749333 548.416a21.333333 21.333333 0 0 1 12.586667-36.266667L136.853333 512H362.666667L362.666667 298.666667h298.666666zM618.666667 42.666667a42.666667 42.666667 0 0 1 42.666666 42.666666v128H362.666667V85.333333a42.666667 42.666667 0 0 1 42.666666-42.666666h213.333334z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 613 B |
1
Backend/src/icons/svg/not-data.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 1)" fill="none" fillRule="evenodd"><ellipse fill="#F5F5F5" cx="32" cy="33" rx="32" ry="7"></ellipse><g fillRule="nonzero" stroke="#D9D9D9"><path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"></path><path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="#FAFAFA"></path></g></g></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
309
Backend/src/icons/svg/not-fount.svg
Executable file
@@ -0,0 +1,309 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="423px" height="341px" viewBox="0 0 423 341" version="1.1">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="404" transform="translate(-484.000000, -221.000000)">
|
||||
<g id="Group-4" transform="translate(464.000000, 222.000000)">
|
||||
<g id="Group-5" transform="translate(75.417348, 0.163879)" stroke="#A3B1BF" stroke-width="1.62" opacity="0.6" stroke-linejoin="round" stroke-dasharray="12.15">
|
||||
<path d="M169.575185,288.890375 C103.647066,288.890375 50.2010204,235.455806 50.2010204,169.540425 C50.2010204,103.625045 103.647066,50.1904762 169.575185,50.1904762 C235.50394,50.1904762 288.94935,103.625045 288.94935,169.540425 C288.94935,235.455806 235.50394,288.890375 169.575185,288.890375 Z" id="Stroke-1"/>
|
||||
<path d="M169.405699,338.555288 C76.0274364,338.555288 0.329411882,262.872619 0.329411882,169.513299 C0.329411882,76.1539786 76.0274364,0.471309613 169.405699,0.471309613 C262.783961,0.471309613 338.481985,76.1539786 338.481985,169.513299 C338.481985,262.872619 262.783961,338.555288 169.405699,338.555288 Z" id="Stroke-3"/>
|
||||
</g>
|
||||
<polygon id="Fill-6" fill="#F5F5F5" transform="translate(106.914750, 199.432805) rotate(22.000000) translate(-106.914750, -199.432805) " points="97.7112298 198.803016 116.11827 190.231152 107.544667 208.634458"/>
|
||||
<polygon id="Stroke-7" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(106.914750, 199.432805) rotate(22.000000) translate(-106.914750, -199.432805) " points="97.7112298 198.803016 116.11827 190.231152 107.544667 208.634458"/>
|
||||
<polygon id="Fill-8" transform="translate(58.996118, 219.767028) rotate(22.000000) translate(-58.996118, -219.767028) " points="38.8543444 215.183828 54.4119879 199.629341 79.1378915 224.350228 63.580248 239.904716"/>
|
||||
<polygon id="Stroke-9" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(58.996118, 219.767028) rotate(22.000000) translate(-58.996118, -219.767028) " points="38.8543444 215.183828 54.4119879 199.629341 79.1378915 224.350228 63.580248 239.904716"/>
|
||||
<polygon id="Fill-10" fill="#F5F5F5" transform="translate(71.226096, 214.577614) rotate(22.000000) translate(-71.226096, -214.577614) " points="51.4642422 198.657893 55.3028278 194.820086 90.9879502 230.497335 87.1487294 234.335141"/>
|
||||
<polygon id="Stroke-11" stroke="#A3B1BF" stroke-width="1.62" fill="#F0F2F5" stroke-linejoin="round" transform="translate(71.226096, 214.577614) rotate(22.000000) translate(-71.226096, -214.577614) " points="51.4642422 198.657893 55.3028278 194.820086 90.9879502 230.497335 87.1487294 234.335141"/>
|
||||
<polygon id="Fill-12" transform="translate(84.698338, 273.903349) rotate(22.000000) translate(-84.698338, -273.903349) " points="56.0769883 259.690379 70.4821667 245.288122 113.319688 288.116319 98.9145099 302.518575"/>
|
||||
<polygon id="Stroke-13" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(84.698338, 273.903349) rotate(22.000000) translate(-84.698338, -273.903349) " points="56.0769883 259.690379 70.4821667 245.288122 113.319688 288.116319 98.9145099 302.518575"/>
|
||||
<polygon id="Fill-14" transform="translate(75.456149, 212.782520) rotate(22.000000) translate(-75.456149, -212.782520) " points="63.8911557 203.879723 66.5512281 201.22019 87.0211429 221.685318 84.3604352 224.34485"/>
|
||||
<polygon id="Stroke-15" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(75.456149, 212.782520) rotate(22.000000) translate(-75.456149, -212.782520) " points="63.8911557 203.879723 66.5512281 201.22019 87.0211429 221.685318 84.3604352 224.34485"/>
|
||||
<polygon id="Fill-16" fill="#F5F5F5" transform="translate(78.176142, 202.618192) rotate(22.000000) translate(-78.176142, -202.618192) " points="74.4026766 205.006817 80.5652518 198.845492 81.9496076 200.229567 75.7870324 206.390892"/>
|
||||
<polygon id="Stroke-17" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(78.176142, 202.618192) rotate(22.000000) translate(-78.176142, -202.618192) " points="74.4026766 205.006817 80.5652518 198.845492 81.9496076 200.229567 75.7870324 206.390892"/>
|
||||
<polygon id="Fill-18" fill="#F5F5F5" transform="translate(84.676979, 217.928886) rotate(22.000000) translate(-84.676979, -217.928886) " points="80.9035138 220.317511 87.066089 214.156186 88.4504448 215.540261 82.2878696 221.701586"/>
|
||||
<polygon id="Stroke-19" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(84.676979, 217.928886) rotate(22.000000) translate(-84.676979, -217.928886) " points="80.9035138 220.317511 87.066089 214.156186 88.4504448 215.540261 82.2878696 221.701586"/>
|
||||
<polygon id="Fill-20" transform="translate(70.238421, 242.404581) rotate(22.000000) translate(-70.238421, -242.404581) " points="67.8493111 238.631881 74.0118863 244.793206 72.6275305 246.177281 66.4649553 240.015956"/>
|
||||
<polygon id="Stroke-21" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(70.238421, 242.404581) rotate(22.000000) translate(-70.238421, -242.404581) " points="67.8493111 238.631881 74.0118863 244.793206 72.6275305 246.177281 66.4649553 240.015956"/>
|
||||
<polygon id="Fill-22" transform="translate(48.081755, 224.398789) rotate(22.000000) translate(-48.081755, -224.398789) " points="35.1127107 220.725179 36.3242602 211.432376 61.0507992 236.153899 51.756111 237.365203"/>
|
||||
<polygon id="Stroke-23" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(48.081755, 224.398789) rotate(22.000000) translate(-48.081755, -224.398789) " points="35.1127107 220.725179 36.3242602 211.432376 61.0507992 236.153899 51.756111 237.365203"/>
|
||||
<polygon id="Fill-24" transform="translate(38.612968, 228.416820) rotate(22.000000) translate(-38.612968, -228.416820) " points="32.9179858 217.470622 26.2496982 216.056058 50.9762371 240.777581 49.5613861 234.110647"/>
|
||||
<polygon id="Stroke-25" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(38.612968, 228.416820) rotate(22.000000) translate(-38.612968, -228.416820) " points="32.9179858 217.470622 26.2496982 216.056058 50.9762371 240.777581 49.5613861 234.110647"/>
|
||||
<polygon id="Fill-26" transform="translate(31.086342, 221.523909) rotate(22.000000) translate(-31.086342, -221.523909) " points="32.6469986 217.297684 26.8592604 218.37941 34.2314793 225.750133 35.3134242 219.963569"/>
|
||||
<polygon id="Stroke-27" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(31.086342, 221.523909) rotate(22.000000) translate(-31.086342, -221.523909) " points="32.6469986 217.297684 26.8592604 218.37941 34.2314793 225.750133 35.3134242 219.963569"/>
|
||||
<polygon id="Fill-28" transform="translate(38.344372, 238.617819) rotate(22.000000) translate(-38.344372, -238.617819) " points="39.9050284 234.391595 34.1172902 235.47332 41.4895091 242.844044 42.571454 237.05748"/>
|
||||
<polygon id="Stroke-29" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(38.344372, 238.617819) rotate(22.000000) translate(-38.344372, -238.617819) " points="39.9050284 234.391595 34.1172902 235.47332 41.4895091 242.844044 42.571454 237.05748"/>
|
||||
<polygon id="Fill-30" fill="#F0F2F5" transform="translate(75.352798, 203.766862) rotate(22.000000) translate(-75.352798, -203.766862) " points="71.4417859 201.708217 73.2937351 199.856644 79.2638092 205.824871 77.4118601 207.67708"/>
|
||||
<polygon id="Stroke-31" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(75.352798, 203.766862) rotate(22.000000) translate(-75.352798, -203.766862) " points="71.4417859 201.708217 73.2937351 199.856644 79.2638092 205.824871 77.4118601 207.67708"/>
|
||||
<polygon id="Fill-32" fill="#F0F2F5" transform="translate(81.871541, 219.119730) rotate(22.000000) translate(-81.871541, -219.119730) " points="77.9605297 217.061085 79.8124789 215.209512 85.7825531 221.177739 83.9306039 223.029948"/>
|
||||
<polygon id="Stroke-33" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(81.871541, 219.119730) rotate(22.000000) translate(-81.871541, -219.119730) " points="77.9605297 217.061085 79.8124789 215.209512 85.7825531 221.177739 83.9306039 223.029948"/>
|
||||
<polygon id="Fill-34" transform="translate(68.034935, 237.215417) rotate(22.000000) translate(-68.034935, -237.215417) " points="65.1594898 237.106164 67.9256601 234.340555 70.9103796 237.324669 68.1442092 240.090278"/>
|
||||
<polygon id="Stroke-35" stroke="#A3B1BF" stroke-width="1.62" fill="#F5F5F5" stroke-linejoin="round" transform="translate(68.034935, 237.215417) rotate(22.000000) translate(-68.034935, -237.215417) " points="65.1594898 237.106164 67.9256601 234.340555 70.9103796 237.324669 68.1442092 240.090278"/>
|
||||
<path d="M90.2556147,213.065319 C79.2271463,202.039088 74.6433339,188.153875 78.4889079,179.512775 C79.1248602,178.084237 80.8319571,175.429786 84.5339495,179.131027 C88.2353065,182.832268 120.560237,215.150007 123.931865,218.52095 C127.303492,221.891894 125.245065,224.192545 123.817508,224.827733 C115.174655,228.675068 101.284718,224.092186 90.2556147,213.065319 Z" id="Fill-36" fill="#F5F5F5" transform="translate(101.453924, 201.708917) rotate(22.000000) translate(-101.453924, -201.708917) "/>
|
||||
<path d="M90.2556147,213.065319 C79.2271463,202.039088 74.6433339,188.153875 78.4889079,179.512775 C79.1248602,178.084237 80.8319571,175.429786 84.5339495,179.131027 C88.2353065,182.832268 120.560237,215.150007 123.931865,218.52095 C127.303492,221.891894 125.245065,224.192545 123.817508,224.827733 C115.174655,228.675068 101.284718,224.092186 90.2556147,213.065319 Z" id="Stroke-38" stroke="#A3B1BF" stroke-width="1.62" fill="#F0F2F5" stroke-linejoin="round" transform="translate(101.453924, 201.708917) rotate(22.000000) translate(-101.453924, -201.708917) "/>
|
||||
<polygon id="Fill-40" fill="#F0F2F5" transform="translate(86.633853, 208.038961) rotate(22.000000) translate(-86.633853, -208.038961) " points="77.9716233 201.230061 79.8235724 199.378488 95.2960835 214.84786 93.4441343 216.699433"/>
|
||||
<polygon id="Stroke-42" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(86.633853, 208.038961) rotate(22.000000) translate(-86.633853, -208.038961) " points="77.9716233 201.230061 79.8235724 199.378488 95.2960835 214.84786 93.4441343 216.699433"/>
|
||||
<path d="M121.635514,196.068815 C120.517356,197.186746 118.704797,197.186746 117.586639,196.068815 C116.469116,194.950884 116.469116,193.138692 117.586639,192.020761 C118.704797,190.903465 120.517356,190.903465 121.635514,192.020761 C122.753672,193.138692 122.753672,194.950884 121.635514,196.068815" id="Fill-44" fill="#A3B1BF" transform="translate(119.611315, 194.045026) rotate(22.000000) translate(-119.611315, -194.045026) "/>
|
||||
<polygon id="Fill-46" fill-opacity="0.6" fill="#A3B1BF" transform="translate(81.250567, 248.056422) rotate(22.000000) translate(-81.250567, -248.056422) " points="81.2505672 249.672341 79.6343206 248.056422 81.2505672 246.440503 82.8668137 248.056422"/>
|
||||
<polygon id="Stroke-48" transform="translate(81.250567, 248.056422) rotate(22.000000) translate(-81.250567, -248.056422) " points="81.2505672 249.672341 79.6343206 248.056422 81.2505672 246.440503 82.8668137 248.056422"/>
|
||||
<polygon id="Fill-50" fill-opacity="0.6" fill="#A3B1BF" transform="translate(82.702196, 251.475282) rotate(22.000000) translate(-82.702196, -251.475282) " points="82.7021965 253.091201 81.0859499 251.475282 82.7021965 249.859363 84.318443 251.475282"/>
|
||||
<polygon id="Stroke-52" transform="translate(82.702196, 251.475282) rotate(22.000000) translate(-82.702196, -251.475282) " points="82.7021965 253.091201 81.0859499 251.475282 82.7021965 249.859363 84.318443 251.475282"/>
|
||||
<polygon id="Fill-54" fill-opacity="0.6" fill="#A3B1BF" transform="translate(84.153791, 254.894059) rotate(22.000000) translate(-84.153791, -254.894059) " points="84.1537906 256.509978 82.5375441 254.894059 84.1537906 253.278141 85.7700372 254.894059"/>
|
||||
<polygon id="Stroke-56" transform="translate(84.153791, 254.894059) rotate(22.000000) translate(-84.153791, -254.894059) " points="84.1537906 256.509978 82.5375441 254.894059 84.1537906 253.278141 85.7700372 254.894059"/>
|
||||
<polygon id="Fill-58" fill-opacity="0.6" fill="#A3B1BF" transform="translate(85.605385, 258.312837) rotate(22.000000) translate(-85.605385, -258.312837) " points="85.6053848 259.928756 83.9891383 258.312837 85.6053848 256.696918 87.2216313 258.312837"/>
|
||||
<polygon id="Stroke-60" transform="translate(85.605385, 258.312837) rotate(22.000000) translate(-85.605385, -258.312837) " points="85.6053848 259.928756 83.9891383 258.312837 85.6053848 256.696918 87.2216313 258.312837"/>
|
||||
<polygon id="Fill-62" fill-opacity="0.6" fill="#A3B1BF" transform="translate(78.094482, 249.395723) rotate(22.000000) translate(-78.094482, -249.395723) " points="78.0944819 251.011642 76.4782353 249.395723 78.0944819 247.779804 79.7107284 249.395723"/>
|
||||
<polygon id="Stroke-64" transform="translate(78.094482, 249.395723) rotate(22.000000) translate(-78.094482, -249.395723) " points="78.0944819 251.011642 76.4782353 249.395723 78.0944819 247.779804 79.7107284 249.395723"/>
|
||||
<polygon id="Fill-66" fill-opacity="0.6" fill="#A3B1BF" transform="translate(79.546052, 252.814559) rotate(22.000000) translate(-79.546052, -252.814559) " points="79.5460522 254.430478 77.9298057 252.814559 79.5460522 251.198641 81.1622988 252.814559"/>
|
||||
<polygon id="Stroke-68" transform="translate(79.546052, 252.814559) rotate(22.000000) translate(-79.546052, -252.814559) " points="79.5460522 254.430478 77.9298057 252.814559 79.5460522 251.198641 81.1622988 252.814559"/>
|
||||
<polygon id="Fill-70" fill-opacity="0.6" fill="#A3B1BF" transform="translate(80.997705, 256.233360) rotate(22.000000) translate(-80.997705, -256.233360) " points="80.9977053 257.849279 79.3814588 256.23336 80.9977053 254.617442 82.6139518 256.23336"/>
|
||||
<polygon id="Stroke-72" transform="translate(80.997705, 256.233360) rotate(22.000000) translate(-80.997705, -256.233360) " points="79.3814588 256.23336 80.9977053 254.617442 82.6139518 256.23336 80.9977053 257.849279"/>
|
||||
<polygon id="Fill-74" fill-opacity="0.6" fill="#A3B1BF" transform="translate(82.449276, 259.652197) rotate(22.000000) translate(-82.449276, -259.652197) " points="82.4492757 261.268115 80.8330292 259.652197 82.4492757 258.036278 84.0655222 259.652197"/>
|
||||
<polygon id="Stroke-76" transform="translate(82.449276, 259.652197) rotate(22.000000) translate(-82.449276, -259.652197) " points="82.4492757 261.268115 80.8330292 259.652197 82.4492757 258.036278 84.0655222 259.652197"/>
|
||||
<polygon id="Fill-78" fill-opacity="0.6" fill="#A3B1BF" transform="translate(74.938373, 250.735083) rotate(22.000000) translate(-74.938373, -250.735083) " points="74.9383727 252.351002 73.3221262 250.735083 74.9383727 249.119164 76.5546193 250.735083"/>
|
||||
<polygon id="Stroke-80" transform="translate(74.938373, 250.735083) rotate(22.000000) translate(-74.938373, -250.735083) " points="74.9383727 252.351002 73.3221262 250.735083 74.9383727 249.119164 76.5546193 250.735083"/>
|
||||
<polygon id="Fill-82" fill-opacity="0.6" fill="#A3B1BF" transform="translate(76.389967, 254.153860) rotate(22.000000) translate(-76.389967, -254.153860) " points="76.3899669 255.769779 74.7737204 254.15386 76.3899669 252.537942 78.0062134 254.15386"/>
|
||||
<polygon id="Stroke-84" transform="translate(76.389967, 254.153860) rotate(22.000000) translate(-76.389967, -254.153860) " points="76.3899669 255.769779 74.7737204 254.15386 76.3899669 252.537942 78.0062134 254.15386"/>
|
||||
<polygon id="Fill-86" fill-opacity="0.6" fill="#A3B1BF" transform="translate(77.841620, 257.572661) rotate(22.000000) translate(-77.841620, -257.572661) " points="77.84162 259.18858 76.2253735 257.572661 77.84162 255.956743 79.4578665 257.572661"/>
|
||||
<polygon id="Stroke-88" transform="translate(77.841620, 257.572661) rotate(22.000000) translate(-77.841620, -257.572661) " points="77.84162 259.18858 76.2253735 257.572661 77.84162 255.956743 79.4578665 257.572661"/>
|
||||
<polygon id="Fill-90" fill-opacity="0.6" fill="#A3B1BF" transform="translate(79.293190, 260.991498) rotate(22.000000) translate(-79.293190, -260.991498) " points="79.2931904 262.607416 77.6769438 260.991498 79.2931904 259.375579 80.9094369 260.991498"/>
|
||||
<polygon id="Stroke-92" transform="translate(79.293190, 260.991498) rotate(22.000000) translate(-79.293190, -260.991498) " points="79.2931904 262.607416 77.6769438 260.991498 79.2931904 259.375579 80.9094369 260.991498"/>
|
||||
<polygon id="Fill-94" fill-opacity="0.6" fill="#A3B1BF" transform="translate(71.782287, 252.074384) rotate(22.000000) translate(-71.782287, -252.074384) " points="71.7822874 253.690302 70.1660409 252.074384 71.7822874 250.458465 73.3985339 252.074384"/>
|
||||
<polygon id="Stroke-96" transform="translate(71.782287, 252.074384) rotate(22.000000) translate(-71.782287, -252.074384) " points="71.7822874 253.690302 70.1660409 252.074384 71.7822874 250.458465 73.3985339 252.074384"/>
|
||||
<polygon id="Fill-98" fill-opacity="0.6" fill="#A3B1BF" transform="translate(73.233882, 255.493161) rotate(22.000000) translate(-73.233882, -255.493161) " points="73.2338816 257.10908 71.6176351 255.493161 73.2338816 253.877242 74.8501281 255.493161"/>
|
||||
<polygon id="Stroke-100" transform="translate(73.233882, 255.493161) rotate(22.000000) translate(-73.233882, -255.493161) " points="73.2338816 257.10908 71.6176351 255.493161 73.2338816 253.877242 74.8501281 255.493161"/>
|
||||
<polygon id="Fill-102" fill-opacity="0.6" fill="#A3B1BF" transform="translate(74.685535, 258.911962) rotate(22.000000) translate(-74.685535, -258.911962) " points="74.6855347 260.527881 73.0692881 258.911962 74.6855347 257.296044 76.3017812 258.911962"/>
|
||||
<polygon id="Stroke-104" transform="translate(74.685535, 258.911962) rotate(22.000000) translate(-74.685535, -258.911962) " points="74.6855347 260.527881 73.0692881 258.911962 74.6855347 257.296044 76.3017812 258.911962"/>
|
||||
<polygon id="Fill-106" fill-opacity="0.6" fill="#A3B1BF" transform="translate(76.137105, 262.330799) rotate(22.000000) translate(-76.137105, -262.330799) " points="76.137105 263.946717 74.5208585 262.330799 76.137105 260.71488 77.7533516 262.330799"/>
|
||||
<polygon id="Stroke-108" transform="translate(76.137105, 262.330799) rotate(22.000000) translate(-76.137105, -262.330799) " points="76.137105 263.946717 74.5208585 262.330799 76.137105 260.71488 77.7533516 262.330799"/>
|
||||
<polygon id="Fill-110" fill-opacity="0.6" fill="#A3B1BF" transform="translate(68.626202, 253.413685) rotate(22.000000) translate(-68.626202, -253.413685) " points="68.6262021 255.029603 67.0099556 253.413685 68.6262021 251.797766 70.2424486 253.413685"/>
|
||||
<polygon id="Stroke-112" transform="translate(68.626202, 253.413685) rotate(22.000000) translate(-68.626202, -253.413685) " points="68.6262021 255.029603 67.0099556 253.413685 68.6262021 251.797766 70.2424486 253.413685"/>
|
||||
<polygon id="Fill-114" fill-opacity="0.6" fill="#A3B1BF" transform="translate(70.077796, 256.832462) rotate(22.000000) translate(-70.077796, -256.832462) " points="70.0777963 258.448381 68.4615497 256.832462 70.0777963 255.216543 71.6940428 256.832462"/>
|
||||
<polygon id="Stroke-116" transform="translate(70.077796, 256.832462) rotate(22.000000) translate(-70.077796, -256.832462) " points="70.0777963 258.448381 68.4615497 256.832462 70.0777963 255.216543 71.6940428 256.832462"/>
|
||||
<polygon id="Fill-118" fill-opacity="0.6" fill="#A3B1BF" transform="translate(71.529426, 260.251322) rotate(22.000000) translate(-71.529426, -260.251322) " points="71.5294256 261.867241 69.913179 260.251322 71.5294256 258.635403 73.1456721 260.251322"/>
|
||||
<polygon id="Stroke-120" transform="translate(71.529426, 260.251322) rotate(22.000000) translate(-71.529426, -260.251322) " points="71.5294256 261.867241 69.913179 260.251322 71.5294256 258.635403 73.1456721 260.251322"/>
|
||||
<polygon id="Fill-122" fill-opacity="0.6" fill="#A3B1BF" transform="translate(72.981020, 263.670099) rotate(22.000000) translate(-72.981020, -263.670099) " points="72.9810197 265.286018 71.3647732 263.670099 72.9810197 262.054181 74.5972663 263.670099"/>
|
||||
<polygon id="Stroke-124" transform="translate(72.981020, 263.670099) rotate(22.000000) translate(-72.981020, -263.670099) " points="72.9810197 265.286018 71.3647732 263.670099 72.9810197 262.054181 74.5972663 263.670099"/>
|
||||
<polygon id="Fill-126" fill-opacity="0.6" fill="#A3B1BF" transform="translate(88.852907, 265.961362) rotate(22.000000) translate(-88.852907, -265.961362) " points="88.8529067 267.577281 87.2366602 265.961362 88.8529067 264.345443 90.4691533 265.961362"/>
|
||||
<polygon id="Stroke-128" transform="translate(88.852907, 265.961362) rotate(22.000000) translate(-88.852907, -265.961362) " points="88.8529067 267.577281 87.2366602 265.961362 88.8529067 264.345443 90.4691533 265.961362"/>
|
||||
<polygon id="Fill-130" fill-opacity="0.6" fill="#A3B1BF" transform="translate(90.304501, 269.380139) rotate(22.000000) translate(-90.304501, -269.380139) " points="90.3045009 270.996058 88.6882544 269.380139 90.3045009 267.764221 91.9207474 269.380139"/>
|
||||
<polygon id="Stroke-132" transform="translate(90.304501, 269.380139) rotate(22.000000) translate(-90.304501, -269.380139) " points="90.3045009 270.996058 88.6882544 269.380139 90.3045009 267.764221 91.9207474 269.380139"/>
|
||||
<polygon id="Fill-134" fill-opacity="0.6" fill="#A3B1BF" transform="translate(91.756130, 272.798999) rotate(22.000000) translate(-91.756130, -272.798999) " points="91.7561302 274.414918 90.1398837 272.798999 91.7561302 271.183081 93.3723767 272.798999"/>
|
||||
<polygon id="Stroke-136" transform="translate(91.756130, 272.798999) rotate(22.000000) translate(-91.756130, -272.798999) " points="91.7561302 274.414918 90.1398837 272.798999 91.7561302 271.183081 93.3723767 272.798999"/>
|
||||
<polygon id="Fill-138" fill-opacity="0.6" fill="#A3B1BF" transform="translate(93.207724, 276.217777) rotate(22.000000) translate(-93.207724, -276.217777) " points="93.2077244 277.833695 91.5914778 276.217777 93.2077244 274.601858 94.8239709 276.217777"/>
|
||||
<polygon id="Stroke-140" transform="translate(93.207724, 276.217777) rotate(22.000000) translate(-93.207724, -276.217777) " points="93.2077244 277.833695 91.5914778 276.217777 93.2077244 274.601858 94.8239709 276.217777"/>
|
||||
<polygon id="Fill-142" fill-opacity="0.6" fill="#A3B1BF" transform="translate(85.696821, 267.300663) rotate(22.000000) translate(-85.696821, -267.300663) " points="85.6968214 268.916582 84.0805749 267.300663 85.6968214 265.684744 87.313068 267.300663"/>
|
||||
<polygon id="Stroke-144" transform="translate(85.696821, 267.300663) rotate(22.000000) translate(-85.696821, -267.300663) " points="85.6968214 268.916582 84.0805749 267.300663 85.6968214 265.684744 87.313068 267.300663"/>
|
||||
<polygon id="Fill-146" fill-opacity="0.6" fill="#A3B1BF" transform="translate(87.148416, 270.719440) rotate(22.000000) translate(-87.148416, -270.719440) " points="87.1484156 272.335359 85.5321691 270.71944 87.1484156 269.103521 88.7646621 270.71944"/>
|
||||
<polygon id="Stroke-148" transform="translate(87.148416, 270.719440) rotate(22.000000) translate(-87.148416, -270.719440) " points="87.1484156 272.335359 85.5321691 270.71944 87.1484156 269.103521 88.7646621 270.71944"/>
|
||||
<polygon id="Fill-150" fill-opacity="0.6" fill="#A3B1BF" transform="translate(88.599986, 274.138276) rotate(22.000000) translate(-88.599986, -274.138276) " points="88.599986 275.754195 86.9837394 274.138276 88.599986 272.522358 90.2162325 274.138276"/>
|
||||
<polygon id="Stroke-152" transform="translate(88.599986, 274.138276) rotate(22.000000) translate(-88.599986, -274.138276) " points="88.599986 275.754195 86.9837394 274.138276 88.599986 272.522358 90.2162325 274.138276"/>
|
||||
<polygon id="Fill-154" fill-opacity="0.6" fill="#A3B1BF" transform="translate(90.051639, 277.557078) rotate(22.000000) translate(-90.051639, -277.557078) " points="90.0516391 279.172996 88.4353925 277.557078 90.0516391 275.941159 91.6678856 277.557078"/>
|
||||
<polygon id="Stroke-156" transform="translate(90.051639, 277.557078) rotate(22.000000) translate(-90.051639, -277.557078) " points="90.0516391 279.172996 88.4353925 277.557078 90.0516391 275.941159 91.6678856 277.557078"/>
|
||||
<polygon id="Fill-158" fill-opacity="0.6" fill="#A3B1BF" transform="translate(82.540736, 268.639964) rotate(22.000000) translate(-82.540736, -268.639964) " points="82.5407361 270.255882 80.9244896 268.639964 82.5407361 267.024045 84.1569826 268.639964"/>
|
||||
<polygon id="Stroke-160" transform="translate(82.540736, 268.639964) rotate(22.000000) translate(-82.540736, -268.639964) " points="82.5407361 270.255882 80.9244896 268.639964 82.5407361 267.024045 84.1569826 268.639964"/>
|
||||
<polygon id="Fill-162" fill-opacity="0.6" fill="#A3B1BF" transform="translate(83.992306, 272.058800) rotate(22.000000) translate(-83.992306, -272.058800) " points="83.9923065 273.674719 82.3760599 272.0588 83.9923065 270.442881 85.608553 272.0588"/>
|
||||
<polygon id="Stroke-164" transform="translate(83.992306, 272.058800) rotate(22.000000) translate(-83.992306, -272.058800) " points="83.9923065 273.674719 82.3760599 272.0588 83.9923065 270.442881 85.608553 272.0588"/>
|
||||
<polygon id="Fill-166" fill-opacity="0.6" fill="#A3B1BF" transform="translate(85.443901, 275.477577) rotate(22.000000) translate(-85.443901, -275.477577) " points="85.4439007 277.093496 83.8276541 275.477577 85.4439007 273.861659 87.0601472 275.477577"/>
|
||||
<polygon id="Stroke-168" transform="translate(85.443901, 275.477577) rotate(22.000000) translate(-85.443901, -275.477577) " points="85.4439007 277.093496 83.8276541 275.477577 85.4439007 273.861659 87.0601472 275.477577"/>
|
||||
<polygon id="Fill-170" fill-opacity="0.6" fill="#A3B1BF" transform="translate(86.895554, 278.896378) rotate(22.000000) translate(-86.895554, -278.896378) " points="86.8955537 280.512297 85.2793072 278.896378 86.8955537 277.28046 88.5118003 278.896378"/>
|
||||
<polygon id="Stroke-172" transform="translate(86.895554, 278.896378) rotate(22.000000) translate(-86.895554, -278.896378) " points="86.8955537 280.512297 85.2793072 278.896378 86.8955537 277.28046 88.5118003 278.896378"/>
|
||||
<polygon id="Fill-174" fill-opacity="0.6" fill="#A3B1BF" transform="translate(79.384651, 269.979265) rotate(22.000000) translate(-79.384651, -269.979265) " points="79.3846508 271.595183 77.7684043 269.979265 79.3846508 268.363346 81.0008973 269.979265"/>
|
||||
<polygon id="Stroke-176" transform="translate(79.384651, 269.979265) rotate(22.000000) translate(-79.384651, -269.979265) " points="79.3846508 271.595183 77.7684043 269.979265 79.3846508 268.363346 81.0008973 269.979265"/>
|
||||
<polygon id="Fill-178" fill-opacity="0.6" fill="#A3B1BF" transform="translate(80.836221, 273.398101) rotate(22.000000) translate(-80.836221, -273.398101) " points="80.8362212 275.01402 79.2199746 273.398101 80.8362212 271.782182 82.4524677 273.398101"/>
|
||||
<polygon id="Stroke-180" transform="translate(80.836221, 273.398101) rotate(22.000000) translate(-80.836221, -273.398101) " points="80.8362212 275.01402 79.2199746 273.398101 80.8362212 271.782182 82.4524677 273.398101"/>
|
||||
<polygon id="Fill-182" fill-opacity="0.6" fill="#A3B1BF" transform="translate(82.287815, 276.816878) rotate(22.000000) translate(-82.287815, -276.816878) " points="82.2878153 278.432797 80.6715688 276.816878 82.2878153 275.20096 83.9040619 276.816878"/>
|
||||
<polygon id="Stroke-184" transform="translate(82.287815, 276.816878) rotate(22.000000) translate(-82.287815, -276.816878) " points="82.2878153 278.432797 80.6715688 276.816878 82.2878153 275.20096 83.9040619 276.816878"/>
|
||||
<polygon id="Fill-186" fill-opacity="0.6" fill="#A3B1BF" transform="translate(83.739445, 280.235738) rotate(22.000000) translate(-83.739445, -280.235738) " points="83.7394446 281.851657 82.1231981 280.235738 83.7394446 278.61982 85.3556912 280.235738"/>
|
||||
<polygon id="Stroke-188" transform="translate(83.739445, 280.235738) rotate(22.000000) translate(-83.739445, -280.235738) " points="83.7394446 281.851657 82.1231981 280.235738 83.7394446 278.61982 85.3556912 280.235738"/>
|
||||
<polygon id="Fill-190" fill-opacity="0.6" fill="#A3B1BF" transform="translate(76.228483, 271.318601) rotate(22.000000) translate(-76.228483, -271.318601) " points="76.2284828 272.934519 74.6122362 271.318601 76.2284828 269.702682 77.8447293 271.318601"/>
|
||||
<polygon id="Stroke-192" transform="translate(76.228483, 271.318601) rotate(22.000000) translate(-76.228483, -271.318601) " points="76.2284828 272.934519 74.6122362 271.318601 76.2284828 269.702682 77.8447293 271.318601"/>
|
||||
<polygon id="Fill-194" fill-opacity="0.6" fill="#A3B1BF" transform="translate(77.680136, 274.737402) rotate(22.000000) translate(-77.680136, -274.737402) " points="77.6801358 276.353321 76.0638893 274.737402 77.6801358 273.121483 79.2963824 274.737402"/>
|
||||
<polygon id="Stroke-196" transform="translate(77.680136, 274.737402) rotate(22.000000) translate(-77.680136, -274.737402) " points="77.6801358 276.353321 76.0638893 274.737402 77.6801358 273.121483 79.2963824 274.737402"/>
|
||||
<polygon id="Fill-198" fill-opacity="0.6" fill="#A3B1BF" transform="translate(79.131730, 278.156179) rotate(22.000000) translate(-79.131730, -278.156179) " points="79.13173 279.772098 77.5154835 278.156179 79.13173 276.540261 80.7479765 278.156179"/>
|
||||
<polygon id="Stroke-200" transform="translate(79.131730, 278.156179) rotate(22.000000) translate(-79.131730, -278.156179) " points="79.13173 279.772098 77.5154835 278.156179 79.13173 276.540261 80.7479765 278.156179"/>
|
||||
<polygon id="Fill-202" fill-opacity="0.6" fill="#A3B1BF" transform="translate(80.583359, 281.575039) rotate(22.000000) translate(-80.583359, -281.575039) " points="80.5833593 283.190958 78.9671128 281.575039 80.5833593 279.959121 82.1996058 281.575039"/>
|
||||
<polygon id="Stroke-204" transform="translate(80.583359, 281.575039) rotate(22.000000) translate(-80.583359, -281.575039) " points="80.5833593 283.190958 78.9671128 281.575039 80.5833593 279.959121 82.1996058 281.575039"/>
|
||||
<polygon id="Fill-206" fill-opacity="0.6" fill="#A3B1BF" transform="translate(96.455246, 283.866302) rotate(22.000000) translate(-96.455246, -283.866302) " points="96.4552463 285.48222 94.8389998 283.866302 96.4552463 282.250383 98.0714929 283.866302"/>
|
||||
<polygon id="Stroke-208" transform="translate(96.455246, 283.866302) rotate(22.000000) translate(-96.455246, -283.866302) " points="96.4552463 285.48222 94.8389998 283.866302 96.4552463 282.250383 98.0714929 283.866302"/>
|
||||
<polygon id="Fill-210" fill-opacity="0.6" fill="#A3B1BF" transform="translate(97.906840, 287.285079) rotate(22.000000) translate(-97.906840, -287.285079) " points="97.9068405 288.900998 96.290594 287.285079 97.9068405 285.66916 99.523087 287.285079"/>
|
||||
<polygon id="Stroke-212" transform="translate(97.906840, 287.285079) rotate(22.000000) translate(-97.906840, -287.285079) " points="97.9068405 288.900998 96.290594 287.285079 97.9068405 285.66916 99.523087 287.285079"/>
|
||||
<polygon id="Fill-214" fill-opacity="0.6" fill="#A3B1BF" transform="translate(99.358435, 290.703856) rotate(22.000000) translate(-99.358435, -290.703856) " points="99.3584347 292.319775 97.7421881 290.703856 99.3584347 289.087938 100.974681 290.703856"/>
|
||||
<polygon id="Stroke-216" transform="translate(99.358435, 290.703856) rotate(22.000000) translate(-99.358435, -290.703856) " points="99.3584347 292.319775 97.7421881 290.703856 99.3584347 289.087938 100.974681 290.703856"/>
|
||||
<polygon id="Fill-218" fill-opacity="0.6" fill="#A3B1BF" transform="translate(100.810064, 294.122716) rotate(22.000000) translate(-100.810064, -294.122716) " points="100.810064 295.738635 99.1938174 294.122716 100.810064 292.506798 102.42631 294.122716"/>
|
||||
<polygon id="Stroke-220" transform="translate(100.810064, 294.122716) rotate(22.000000) translate(-100.810064, -294.122716) " points="100.810064 295.738635 99.1938174 294.122716 100.810064 292.506798 102.42631 294.122716"/>
|
||||
<polygon id="Fill-222" fill-opacity="0.6" fill="#A3B1BF" transform="translate(93.299102, 285.205579) rotate(22.000000) translate(-93.299102, -285.205579) " points="93.2991021 286.821497 91.6828556 285.205579 93.2991021 283.58966 94.9153486 285.205579"/>
|
||||
<polygon id="Stroke-224" transform="translate(93.299102, 285.205579) rotate(22.000000) translate(-93.299102, -285.205579) " points="93.2991021 286.821497 91.6828556 285.205579 93.2991021 283.58966 94.9153486 285.205579"/>
|
||||
<polygon id="Fill-226" fill-opacity="0.6" fill="#A3B1BF" transform="translate(94.750755, 288.624380) rotate(22.000000) translate(-94.750755, -288.624380) " points="94.7507552 290.240299 93.1345086 288.62438 94.7507552 287.008461 96.3670017 288.62438"/>
|
||||
<polygon id="Stroke-228" transform="translate(94.750755, 288.624380) rotate(22.000000) translate(-94.750755, -288.624380) " points="94.7507552 290.240299 93.1345086 288.62438 94.7507552 287.008461 96.3670017 288.62438"/>
|
||||
<polygon id="Fill-230" fill-opacity="0.6" fill="#A3B1BF" transform="translate(96.202326, 292.043216) rotate(22.000000) translate(-96.202326, -292.043216) " points="96.2023255 293.659135 94.586079 292.043216 96.2023255 290.427297 97.8185721 292.043216"/>
|
||||
<polygon id="Stroke-232" transform="translate(96.202326, 292.043216) rotate(22.000000) translate(-96.202326, -292.043216) " points="96.2023255 293.659135 94.586079 292.043216 96.2023255 290.427297 97.8185721 292.043216"/>
|
||||
<polygon id="Fill-234" fill-opacity="0.6" fill="#A3B1BF" transform="translate(97.653979, 295.462017) rotate(22.000000) translate(-97.653979, -295.462017) " points="97.6539786 297.077936 96.0377321 295.462017 97.6539786 293.846099 99.2702252 295.462017"/>
|
||||
<polygon id="Stroke-236" transform="translate(97.653979, 295.462017) rotate(22.000000) translate(-97.653979, -295.462017) " points="97.6539786 297.077936 96.0377321 295.462017 97.6539786 293.846099 99.2702252 295.462017"/>
|
||||
<polygon id="Fill-238" fill-opacity="0.6" fill="#A3B1BF" transform="translate(90.143017, 286.544880) rotate(22.000000) translate(-90.143017, -286.544880) " points="90.1430168 288.160798 88.5267702 286.54488 90.1430168 284.928961 91.7592633 286.54488"/>
|
||||
<polygon id="Stroke-240" transform="translate(90.143017, 286.544880) rotate(22.000000) translate(-90.143017, -286.544880) " points="90.1430168 288.160798 88.5267702 286.54488 90.1430168 284.928961 91.7592633 286.54488"/>
|
||||
<polygon id="Fill-242" fill-opacity="0.6" fill="#A3B1BF" transform="translate(91.594670, 289.963681) rotate(22.000000) translate(-91.594670, -289.963681) " points="91.5946698 291.5796 89.9784233 289.963681 91.5946698 288.347762 93.2109164 289.963681"/>
|
||||
<polygon id="Stroke-244" transform="translate(91.594670, 289.963681) rotate(22.000000) translate(-91.594670, -289.963681) " points="91.5946698 291.5796 89.9784233 289.963681 91.5946698 288.347762 93.2109164 289.963681"/>
|
||||
<polygon id="Fill-246" fill-opacity="0.6" fill="#A3B1BF" transform="translate(93.046240, 293.382517) rotate(22.000000) translate(-93.046240, -293.382517) " points="93.0462402 294.998436 91.4299937 293.382517 93.0462402 291.766598 94.6624868 293.382517"/>
|
||||
<polygon id="Stroke-248" transform="translate(93.046240, 293.382517) rotate(22.000000) translate(-93.046240, -293.382517) " points="93.0462402 294.998436 91.4299937 293.382517 93.0462402 291.766598 94.6624868 293.382517"/>
|
||||
<polygon id="Fill-250" fill-opacity="0.6" fill="#A3B1BF" transform="translate(94.497893, 296.801318) rotate(22.000000) translate(-94.497893, -296.801318) " points="94.4978933 298.417237 92.8816468 296.801318 94.4978933 295.1854 96.1141398 296.801318"/>
|
||||
<polygon id="Stroke-252" transform="translate(94.497893, 296.801318) rotate(22.000000) translate(-94.497893, -296.801318) " points="94.4978933 298.417237 92.8816468 296.801318 94.4978933 295.1854 96.1141398 296.801318"/>
|
||||
<polygon id="Fill-254" fill-opacity="0.6" fill="#A3B1BF" transform="translate(86.986931, 287.884181) rotate(22.000000) translate(-86.986931, -287.884181) " points="86.9869315 289.500099 85.3706849 287.884181 86.9869315 286.268262 88.603178 287.884181"/>
|
||||
<polygon id="Stroke-256" transform="translate(86.986931, 287.884181) rotate(22.000000) translate(-86.986931, -287.884181) " points="86.9869315 289.500099 85.3706849 287.884181 86.9869315 286.268262 88.603178 287.884181"/>
|
||||
<polygon id="Fill-258" fill-opacity="0.6" fill="#A3B1BF" transform="translate(88.438585, 291.302982) rotate(22.000000) translate(-88.438585, -291.302982) " points="88.4385845 292.9189 86.822338 291.302982 88.4385845 289.687063 90.0548311 291.302982"/>
|
||||
<polygon id="Stroke-260" transform="translate(88.438585, 291.302982) rotate(22.000000) translate(-88.438585, -291.302982) " points="88.4385845 292.9189 86.822338 291.302982 88.4385845 289.687063 90.0548311 291.302982"/>
|
||||
<polygon id="Fill-262" fill-opacity="0.6" fill="#A3B1BF" transform="translate(89.890155, 294.721818) rotate(22.000000) translate(-89.890155, -294.721818) " points="89.8901549 296.337737 88.2739084 294.721818 89.8901549 293.105899 91.5064014 294.721818"/>
|
||||
<polygon id="Stroke-264" transform="translate(89.890155, 294.721818) rotate(22.000000) translate(-89.890155, -294.721818) " points="89.8901549 296.337737 88.2739084 294.721818 89.8901549 293.105899 91.5064014 294.721818"/>
|
||||
<polygon id="Fill-266" fill-opacity="0.6" fill="#A3B1BF" transform="translate(91.341749, 298.140595) rotate(22.000000) translate(-91.341749, -298.140595) " points="91.3417491 299.756514 89.7255025 298.140595 91.3417491 296.524677 92.9579956 298.140595"/>
|
||||
<polygon id="Stroke-268" transform="translate(91.341749, 298.140595) rotate(22.000000) translate(-91.341749, -298.140595) " points="91.3417491 299.756514 89.7255025 298.140595 91.3417491 296.524677 92.9579956 298.140595"/>
|
||||
<polygon id="Fill-270" fill-opacity="0.6" fill="#A3B1BF" transform="translate(83.830846, 289.223482) rotate(22.000000) translate(-83.830846, -289.223482) " points="83.8308461 290.8394 82.2145996 289.223482 83.8308461 287.607563 85.4470927 289.223482"/>
|
||||
<polygon id="Stroke-272" transform="translate(83.830846, 289.223482) rotate(22.000000) translate(-83.830846, -289.223482) " points="82.2145996 289.223482 83.8308461 287.607563 85.4470927 289.223482 83.8308461 290.8394"/>
|
||||
<polygon id="Fill-274" fill-opacity="0.6" fill="#A3B1BF" transform="translate(85.282417, 292.642318) rotate(22.000000) translate(-85.282417, -292.642318) " points="85.2824165 294.258236 83.66617 292.642318 85.2824165 291.026399 86.898663 292.642318"/>
|
||||
<polygon id="Stroke-276" transform="translate(85.282417, 292.642318) rotate(22.000000) translate(-85.282417, -292.642318) " points="85.2824165 294.258236 83.66617 292.642318 85.2824165 291.026399 86.898663 292.642318"/>
|
||||
<polygon id="Fill-278" fill-opacity="0.6" fill="#A3B1BF" transform="translate(86.734070, 296.061119) rotate(22.000000) translate(-86.734070, -296.061119) " points="86.7340696 297.677038 85.1178231 296.061119 86.7340696 294.4452 88.3503161 296.061119"/>
|
||||
<polygon id="Stroke-280" transform="translate(86.734070, 296.061119) rotate(22.000000) translate(-86.734070, -296.061119) " points="86.7340696 297.677038 85.1178231 296.061119 86.7340696 294.4452 88.3503161 296.061119"/>
|
||||
<polygon id="Fill-282" fill-opacity="0.6" fill="#A3B1BF" transform="translate(88.185664, 299.479896) rotate(22.000000) translate(-88.185664, -299.479896) " points="88.1856638 301.095815 86.5694172 299.479896 88.1856638 297.863978 89.8019103 299.479896"/>
|
||||
<polygon id="Stroke-284" transform="translate(88.185664, 299.479896) rotate(22.000000) translate(-88.185664, -299.479896) " points="88.1856638 301.095815 86.5694172 299.479896 88.1856638 297.863978 89.8019103 299.479896"/>
|
||||
<polygon id="Fill-286" transform="translate(37.779995, 163.402514) rotate(22.000000) translate(-37.779995, -163.402514) " points="51.9961666 192.018058 66.401345 177.615802 23.5644587 134.78697 9.15864501 149.189862"/>
|
||||
<polygon id="Fill-290" transform="translate(50.400318, 195.682118) rotate(22.000000) translate(-50.400318, -195.682118) " points="54.1737837 198.070743 48.0112085 191.909418 46.6268527 193.293493 52.7894279 199.454818"/>
|
||||
<polygon id="Stroke-292" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(50.400318, 195.682118) rotate(22.000000) translate(-50.400318, -195.682118) " points="54.1737837 198.070743 48.0112085 191.909418 46.6268527 193.293493 52.7894279 199.454818"/>
|
||||
<polygon id="Fill-294" fill-opacity="0.8" fill="#A3B1BF" transform="translate(52.603532, 200.871447) rotate(22.000000) translate(-52.603532, -200.871447) " points="52.712807 203.746308 55.4789773 200.980699 52.4942579 197.996585 49.7280876 200.762195"/>
|
||||
<polygon id="Stroke-296" stroke="#A3B1BF" stroke-width="1.62" fill="#FFFFFF" stroke-linejoin="round" transform="translate(52.603532, 200.871447) rotate(22.000000) translate(-52.603532, -200.871447) " points="52.712807 203.746308 55.4789773 200.980699 52.4942579 197.996585 49.7280876 200.762195"/>
|
||||
<polygon id="Fill-298" fill-opacity="0.8" fill="#A3B1BF" transform="translate(53.983257, 183.836778) rotate(22.000000) translate(-53.983257, -183.836778) " points="52.3670108 183.836778 53.9832574 185.452697 55.5995039 183.836778 53.9832574 182.22086"/>
|
||||
<polygon id="Stroke-300" transform="translate(53.983257, 183.836778) rotate(22.000000) translate(-53.983257, -183.836778) " points="52.3670108 183.836778 53.9832574 185.452697 55.5995039 183.836778 53.9832574 182.22086"/>
|
||||
<polygon id="Fill-302" fill-opacity="0.8" fill="#A3B1BF" transform="translate(52.531663, 180.418001) rotate(22.000000) translate(-52.531663, -180.418001) " points="50.9154167 180.418001 52.5316632 182.03392 54.1479097 180.418001 52.5316632 178.802082"/>
|
||||
<polygon id="Stroke-304" transform="translate(52.531663, 180.418001) rotate(22.000000) translate(-52.531663, -180.418001) " points="50.9154167 180.418001 52.5316632 182.03392 54.1479097 180.418001 52.5316632 178.802082"/>
|
||||
<polygon id="Fill-306" fill-opacity="0.8" fill="#A3B1BF" transform="translate(51.080034, 176.999141) rotate(22.000000) translate(-51.080034, -176.999141) " points="49.4637874 176.999141 51.0800339 178.61506 52.6962805 176.999141 51.0800339 175.383222"/>
|
||||
<polygon id="Stroke-308" transform="translate(51.080034, 176.999141) rotate(22.000000) translate(-51.080034, -176.999141) " points="49.4637874 176.999141 51.0800339 178.61506 52.6962805 176.999141 51.0800339 175.383222"/>
|
||||
<polygon id="Fill-310" fill-opacity="0.8" fill="#A3B1BF" transform="translate(49.628440, 173.580364) rotate(22.000000) translate(-49.628440, -173.580364) " points="48.0121932 173.580364 49.6284397 175.196282 51.2446863 173.580364 49.6284397 171.964445"/>
|
||||
<polygon id="Stroke-312" transform="translate(49.628440, 173.580364) rotate(22.000000) translate(-49.628440, -173.580364) " points="48.0121932 173.580364 49.6284397 175.196282 51.2446863 173.580364 49.6284397 171.964445"/>
|
||||
<polygon id="Fill-314" fill-opacity="0.8" fill="#A3B1BF" transform="translate(50.827172, 185.176079) rotate(22.000000) translate(-50.827172, -185.176079) " points="49.2109255 185.176079 50.8271721 186.791998 52.4434186 185.176079 50.8271721 183.560161"/>
|
||||
<polygon id="Stroke-316" transform="translate(50.827172, 185.176079) rotate(22.000000) translate(-50.827172, -185.176079) " points="49.2109255 185.176079 50.8271721 186.791998 52.4434186 185.176079 50.8271721 183.560161"/>
|
||||
<polygon id="Fill-318" fill-opacity="0.8" fill="#A3B1BF" transform="translate(49.375578, 181.757302) rotate(22.000000) translate(-49.375578, -181.757302) " points="47.7593313 181.757302 49.3755779 183.373221 50.9918244 181.757302 49.3755779 180.141383"/>
|
||||
<polygon id="Stroke-320" transform="translate(49.375578, 181.757302) rotate(22.000000) translate(-49.375578, -181.757302) " points="47.7593313 181.757302 49.3755779 183.373221 50.9918244 181.757302 49.3755779 180.141383"/>
|
||||
<polygon id="Fill-322" fill-opacity="0.8" fill="#A3B1BF" transform="translate(47.923949, 178.338442) rotate(22.000000) translate(-47.923949, -178.338442) " points="46.3077021 178.338442 47.9239486 179.954361 49.5401951 178.338442 47.9239486 176.722523"/>
|
||||
<polygon id="Stroke-324" transform="translate(47.923949, 178.338442) rotate(22.000000) translate(-47.923949, -178.338442) " points="46.3077021 178.338442 47.9239486 179.954361 49.5401951 178.338442 47.9239486 176.722523"/>
|
||||
<polygon id="Fill-326" fill-opacity="0.8" fill="#A3B1BF" transform="translate(46.472354, 174.919665) rotate(22.000000) translate(-46.472354, -174.919665) " points="44.8561079 174.919665 46.4723544 176.535583 48.088601 174.919665 46.4723544 173.303746"/>
|
||||
<polygon id="Stroke-328" transform="translate(46.472354, 174.919665) rotate(22.000000) translate(-46.472354, -174.919665) " points="44.8561079 174.919665 46.4723544 176.535583 48.088601 174.919665 46.4723544 173.303746"/>
|
||||
<polygon id="Fill-330" fill-opacity="0.8" fill="#A3B1BF" transform="translate(47.671087, 186.515380) rotate(22.000000) translate(-47.671087, -186.515380) " points="46.0548402 186.51538 47.6710867 188.131299 49.2873333 186.51538 47.6710867 184.899461"/>
|
||||
<polygon id="Stroke-332" transform="translate(47.671087, 186.515380) rotate(22.000000) translate(-47.671087, -186.515380) " points="46.0548402 186.51538 47.6710867 188.131299 49.2873333 186.51538 47.6710867 184.899461"/>
|
||||
<polygon id="Fill-334" fill-opacity="0.8" fill="#A3B1BF" transform="translate(46.219493, 183.096603) rotate(22.000000) translate(-46.219493, -183.096603) " points="44.603246 183.096603 46.2194926 184.712521 47.8357391 183.096603 46.2194926 181.480684"/>
|
||||
<polygon id="Stroke-336" transform="translate(46.219493, 183.096603) rotate(22.000000) translate(-46.219493, -183.096603) " points="44.603246 183.096603 46.2194926 184.712521 47.8357391 183.096603 46.2194926 181.480684"/>
|
||||
<polygon id="Fill-338" fill-opacity="0.8" fill="#A3B1BF" transform="translate(44.767839, 179.677802) rotate(22.000000) translate(-44.767839, -179.677802) " points="43.1515929 179.677802 44.7678395 181.29372 46.384086 179.677802 44.7678395 178.061883"/>
|
||||
<polygon id="Stroke-340" transform="translate(44.767839, 179.677802) rotate(22.000000) translate(-44.767839, -179.677802) " points="43.1515929 179.677802 44.7678395 181.29372 46.384086 179.677802 44.7678395 178.061883"/>
|
||||
<polygon id="Fill-342" fill-opacity="0.8" fill="#A3B1BF" transform="translate(43.316269, 176.258965) rotate(22.000000) translate(-43.316269, -176.258965) " points="41.7000226 176.258965 43.3162691 177.874884 44.9325156 176.258965 43.3162691 174.643047"/>
|
||||
<polygon id="Stroke-344" transform="translate(43.316269, 176.258965) rotate(22.000000) translate(-43.316269, -176.258965) " points="41.7000226 176.258965 43.3162691 177.874884 44.9325156 176.258965 43.3162691 174.643047"/>
|
||||
<polygon id="Fill-346" fill-opacity="0.8" fill="#A3B1BF" transform="translate(44.514978, 187.854740) rotate(22.000000) translate(-44.514978, -187.854740) " points="42.8987311 187.85474 44.5149776 189.470659 46.1312242 187.85474 44.5149776 186.238821"/>
|
||||
<polygon id="Stroke-348" transform="translate(44.514978, 187.854740) rotate(22.000000) translate(-44.514978, -187.854740) " points="42.8987311 187.85474 44.5149776 189.470659 46.1312242 187.85474 44.5149776 186.238821"/>
|
||||
<polygon id="Fill-350" fill-opacity="0.8" fill="#A3B1BF" transform="translate(43.063348, 184.435880) rotate(22.000000) translate(-43.063348, -184.435880) " points="41.4471018 184.43588 43.0633483 186.051799 44.6795949 184.43588 43.0633483 182.819961"/>
|
||||
<polygon id="Stroke-352" transform="translate(43.063348, 184.435880) rotate(22.000000) translate(-43.063348, -184.435880) " points="41.4471018 184.43588 43.0633483 186.051799 44.6795949 184.43588 43.0633483 182.819961"/>
|
||||
<polygon id="Fill-354" fill-opacity="0.8" fill="#A3B1BF" transform="translate(41.611754, 181.017103) rotate(22.000000) translate(-41.611754, -181.017103) " points="39.9955076 181.017103 41.6117542 182.633021 43.2280007 181.017103 41.6117542 179.401184"/>
|
||||
<polygon id="Stroke-356" transform="translate(41.611754, 181.017103) rotate(22.000000) translate(-41.611754, -181.017103) " points="39.9955076 181.017103 41.6117542 182.633021 43.2280007 181.017103 41.6117542 179.401184"/>
|
||||
<polygon id="Fill-358" fill-opacity="0.8" fill="#A3B1BF" transform="translate(40.160184, 177.598266) rotate(22.000000) translate(-40.160184, -177.598266) " points="38.5439373 177.598266 40.1601838 179.214185 41.7764303 177.598266 40.1601838 175.982348"/>
|
||||
<polygon id="Stroke-360" transform="translate(40.160184, 177.598266) rotate(22.000000) translate(-40.160184, -177.598266) " points="38.5439373 177.598266 40.1601838 179.214185 41.7764303 177.598266 40.1601838 175.982348"/>
|
||||
<polygon id="Fill-362" fill-opacity="0.8" fill="#A3B1BF" transform="translate(41.358892, 189.194041) rotate(22.000000) translate(-41.358892, -189.194041) " points="39.7426458 189.194041 41.3588923 190.80996 42.9751388 189.194041 41.3588923 187.578122"/>
|
||||
<polygon id="Stroke-364" transform="translate(41.358892, 189.194041) rotate(22.000000) translate(-41.358892, -189.194041) " points="39.7426458 189.194041 41.3588923 190.80996 42.9751388 189.194041 41.3588923 187.578122"/>
|
||||
<polygon id="Fill-366" fill-opacity="0.8" fill="#A3B1BF" transform="translate(39.907263, 185.775181) rotate(22.000000) translate(-39.907263, -185.775181) " points="38.2910165 185.775181 39.907263 187.3911 41.5235095 185.775181 39.907263 184.159262"/>
|
||||
<polygon id="Stroke-368" transform="translate(39.907263, 185.775181) rotate(22.000000) translate(-39.907263, -185.775181) " points="38.2910165 185.775181 39.907263 187.3911 41.5235095 185.775181 39.907263 184.159262"/>
|
||||
<polygon id="Fill-370" fill-opacity="0.8" fill="#A3B1BF" transform="translate(38.455669, 182.356404) rotate(22.000000) translate(-38.455669, -182.356404) " points="36.8394223 182.356404 38.4556688 183.972322 40.0719154 182.356404 38.4556688 180.740485"/>
|
||||
<polygon id="Stroke-372" transform="translate(38.455669, 182.356404) rotate(22.000000) translate(-38.455669, -182.356404) " points="36.8394223 182.356404 38.4556688 183.972322 40.0719154 182.356404 38.4556688 180.740485"/>
|
||||
<polygon id="Fill-374" fill-opacity="0.8" fill="#A3B1BF" transform="translate(37.004016, 178.937602) rotate(22.000000) translate(-37.004016, -178.937602) " points="35.3877692 178.937602 37.0040158 180.553521 38.6202623 178.937602 37.0040158 177.321684"/>
|
||||
<polygon id="Stroke-376" transform="translate(37.004016, 178.937602) rotate(22.000000) translate(-37.004016, -178.937602) " points="35.3877692 178.937602 37.0040158 180.553521 38.6202623 178.937602 37.0040158 177.321684"/>
|
||||
<polygon id="Fill-378" fill-opacity="0.8" fill="#A3B1BF" transform="translate(46.380894, 165.931897) rotate(22.000000) translate(-46.380894, -165.931897) " points="44.7646475 165.931897 46.380894 167.547816 47.9971405 165.931897 46.380894 164.315979"/>
|
||||
<polygon id="Stroke-380" transform="translate(46.380894, 165.931897) rotate(22.000000) translate(-46.380894, -165.931897) " points="44.7646475 165.931897 46.380894 167.547816 47.9971405 165.931897 46.380894 164.315979"/>
|
||||
<polygon id="Fill-382" fill-opacity="0.8" fill="#A3B1BF" transform="translate(44.929324, 162.513061) rotate(22.000000) translate(-44.929324, -162.513061) " points="43.3130771 162.513061 44.9293236 164.12898 46.5455702 162.513061 44.9293236 160.897143"/>
|
||||
<polygon id="Stroke-384" transform="translate(44.929324, 162.513061) rotate(22.000000) translate(-44.929324, -162.513061) " points="43.3130771 162.513061 44.9293236 164.12898 46.5455702 162.513061 44.9293236 160.897143"/>
|
||||
<polygon id="Fill-386" fill-opacity="0.8" fill="#A3B1BF" transform="translate(43.477729, 159.094284) rotate(22.000000) translate(-43.477729, -159.094284) " points="41.8614829 159.094284 43.4777295 160.710203 45.093976 159.094284 43.4777295 157.478365"/>
|
||||
<polygon id="Stroke-388" transform="translate(43.477729, 159.094284) rotate(22.000000) translate(-43.477729, -159.094284) " points="41.8614829 159.094284 43.4777295 160.710203 45.093976 159.094284 43.4777295 157.478365"/>
|
||||
<polygon id="Fill-390" fill-opacity="0.8" fill="#A3B1BF" transform="translate(42.026100, 155.675424) rotate(22.000000) translate(-42.026100, -155.675424) " points="40.4098536 155.675424 42.0261002 157.291342 43.6423467 155.675424 42.0261002 154.059505"/>
|
||||
<polygon id="Stroke-392" transform="translate(42.026100, 155.675424) rotate(22.000000) translate(-42.026100, -155.675424) " points="40.4098536 155.675424 42.0261002 157.291342 43.6423467 155.675424 42.0261002 154.059505"/>
|
||||
<polygon id="Fill-394" fill-opacity="0.8" fill="#A3B1BF" transform="translate(43.224809, 167.271198) rotate(22.000000) translate(-43.224809, -167.271198) " points="41.6085622 167.271198 43.2248087 168.887117 44.8410552 167.271198 43.2248087 165.65528"/>
|
||||
<polygon id="Stroke-396" transform="translate(43.224809, 167.271198) rotate(22.000000) translate(-43.224809, -167.271198) " points="41.6085622 167.271198 43.2248087 168.887117 44.8410552 167.271198 43.2248087 165.65528"/>
|
||||
<polygon id="Fill-398" fill-opacity="0.8" fill="#A3B1BF" transform="translate(41.773238, 163.852362) rotate(22.000000) translate(-41.773238, -163.852362) " points="40.1569918 163.852362 41.7732383 165.468281 43.3894848 163.852362 41.7732383 162.236443"/>
|
||||
<polygon id="Stroke-400" transform="translate(41.773238, 163.852362) rotate(22.000000) translate(-41.773238, -163.852362) " points="40.1569918 163.852362 41.7732383 165.468281 43.3894848 163.852362 41.7732383 162.236443"/>
|
||||
<polygon id="Fill-402" fill-opacity="0.8" fill="#A3B1BF" transform="translate(40.321585, 160.433561) rotate(22.000000) translate(-40.321585, -160.433561) " points="38.7053387 160.433561 40.3215852 162.04948 41.9378318 160.433561 40.3215852 158.817642"/>
|
||||
<polygon id="Stroke-404" transform="translate(40.321585, 160.433561) rotate(22.000000) translate(-40.321585, -160.433561) " points="38.7053387 160.433561 40.3215852 162.04948 41.9378318 160.433561 40.3215852 158.817642"/>
|
||||
<polygon id="Fill-406" fill-opacity="0.8" fill="#A3B1BF" transform="translate(38.869991, 157.014784) rotate(22.000000) translate(-38.869991, -157.014784) " points="37.2537445 157.014784 38.8699911 158.630702 40.4862376 157.014784 38.8699911 155.398865"/>
|
||||
<polygon id="Stroke-408" transform="translate(38.869991, 157.014784) rotate(22.000000) translate(-38.869991, -157.014784) " points="37.2537445 157.014784 38.8699911 158.630702 40.4862376 157.014784 38.8699911 155.398865"/>
|
||||
<polygon id="Fill-410" fill-opacity="0.8" fill="#A3B1BF" transform="translate(40.068723, 168.610499) rotate(22.000000) translate(-40.068723, -168.610499) " points="38.4524768 168.610499 40.0687234 170.226418 41.6849699 168.610499 40.0687234 166.994581"/>
|
||||
<polygon id="Stroke-412" transform="translate(40.068723, 168.610499) rotate(22.000000) translate(-40.068723, -168.610499) " points="38.4524768 168.610499 40.0687234 170.226418 41.6849699 168.610499 40.0687234 166.994581"/>
|
||||
<polygon id="Fill-414" fill-opacity="0.8" fill="#A3B1BF" transform="translate(38.617129, 165.191722) rotate(22.000000) translate(-38.617129, -165.191722) " points="37.0008827 165.191722 38.6171292 166.807641 40.2333757 165.191722 38.6171292 163.575803"/>
|
||||
<polygon id="Stroke-416" transform="translate(38.617129, 165.191722) rotate(22.000000) translate(-38.617129, -165.191722) " points="37.0008827 165.191722 38.6171292 166.807641 40.2333757 165.191722 38.6171292 163.575803"/>
|
||||
<polygon id="Fill-418" fill-opacity="0.8" fill="#A3B1BF" transform="translate(37.165500, 161.772862) rotate(22.000000) translate(-37.165500, -161.772862) " points="35.5492534 161.772862 37.1654999 163.388781 38.7817464 161.772862 37.1654999 160.156943"/>
|
||||
<polygon id="Stroke-420" transform="translate(37.165500, 161.772862) rotate(22.000000) translate(-37.165500, -161.772862) " points="35.5492534 161.772862 37.1654999 163.388781 38.7817464 161.772862 37.1654999 160.156943"/>
|
||||
<polygon id="Fill-422" fill-opacity="0.8" fill="#A3B1BF" transform="translate(35.713906, 158.354085) rotate(22.000000) translate(-35.713906, -158.354085) " points="34.0976592 158.354085 35.7139057 159.970003 37.3301523 158.354085 35.7139057 156.738166"/>
|
||||
<polygon id="Stroke-424" transform="translate(35.713906, 158.354085) rotate(22.000000) translate(-35.713906, -158.354085) " points="34.0976592 158.354085 35.7139057 159.970003 37.3301523 158.354085 35.7139057 156.738166"/>
|
||||
<polygon id="Fill-426" fill-opacity="0.8" fill="#A3B1BF" transform="translate(36.912638, 169.949800) rotate(22.000000) translate(-36.912638, -169.949800) " points="35.2963915 169.9498 36.912638 171.565719 38.5288846 169.9498 36.912638 168.333882"/>
|
||||
<polygon id="Stroke-428" transform="translate(36.912638, 169.949800) rotate(22.000000) translate(-36.912638, -169.949800) " points="35.2963915 169.9498 36.912638 171.565719 38.5288846 169.9498 36.912638 168.333882"/>
|
||||
<polygon id="Fill-430" fill-opacity="0.8" fill="#A3B1BF" transform="translate(35.461068, 166.530964) rotate(22.000000) translate(-35.461068, -166.530964) " points="33.8448211 166.530964 35.4610677 168.146883 37.0773142 166.530964 35.4610677 164.915045"/>
|
||||
<polygon id="Stroke-432" transform="translate(35.461068, 166.530964) rotate(22.000000) translate(-35.461068, -166.530964) " points="33.8448211 166.530964 35.4610677 168.146883 37.0773142 166.530964 35.4610677 164.915045"/>
|
||||
<polygon id="Fill-434" fill-opacity="0.8" fill="#A3B1BF" transform="translate(34.009415, 163.112163) rotate(22.000000) translate(-34.009415, -163.112163) " points="32.3931681 163.112163 34.0094146 164.728081 35.6256611 163.112163 34.0094146 161.496244"/>
|
||||
<polygon id="Stroke-436" transform="translate(34.009415, 163.112163) rotate(22.000000) translate(-34.009415, -163.112163) " points="32.3931681 163.112163 34.0094146 164.728081 35.6256611 163.112163 34.0094146 161.496244"/>
|
||||
<polygon id="Fill-438" fill-opacity="0.8" fill="#A3B1BF" transform="translate(32.557820, 159.693385) rotate(22.000000) translate(-32.557820, -159.693385) " points="30.9415739 159.693385 32.5578204 161.309304 34.174067 159.693385 32.5578204 158.077467"/>
|
||||
<polygon id="Stroke-440" transform="translate(32.557820, 159.693385) rotate(22.000000) translate(-32.557820, -159.693385) " points="30.9415739 159.693385 32.5578204 161.309304 34.174067 159.693385 32.5578204 158.077467"/>
|
||||
<polygon id="Fill-442" fill-opacity="0.8" fill="#A3B1BF" transform="translate(33.756553, 171.289101) rotate(22.000000) translate(-33.756553, -171.289101) " points="32.1403062 171.289101 33.7565527 172.90502 35.3727993 171.289101 33.7565527 169.673182"/>
|
||||
<polygon id="Stroke-444" transform="translate(33.756553, 171.289101) rotate(22.000000) translate(-33.756553, -171.289101) " points="32.1403062 171.289101 33.7565527 172.90502 35.3727993 171.289101 33.7565527 169.673182"/>
|
||||
<polygon id="Fill-446" fill-opacity="0.8" fill="#A3B1BF" transform="translate(32.304959, 167.870324) rotate(22.000000) translate(-32.304959, -167.870324) " points="30.688712 167.870324 32.3049586 169.486242 33.9212051 167.870324 32.3049586 166.254405"/>
|
||||
<polygon id="Stroke-448" transform="translate(32.304959, 167.870324) rotate(22.000000) translate(-32.304959, -167.870324) " points="30.688712 167.870324 32.3049586 169.486242 33.9212051 167.870324 32.3049586 166.254405"/>
|
||||
<polygon id="Fill-450" fill-opacity="0.8" fill="#A3B1BF" transform="translate(30.853329, 164.451464) rotate(22.000000) translate(-30.853329, -164.451464) " points="29.2370827 164.451464 30.8533293 166.067382 32.4695758 164.451464 30.8533293 162.835545"/>
|
||||
<polygon id="Stroke-452" transform="translate(30.853329, 164.451464) rotate(22.000000) translate(-30.853329, -164.451464) " points="29.2370827 164.451464 30.8533293 166.067382 32.4695758 164.451464 30.8533293 162.835545"/>
|
||||
<polygon id="Fill-454" fill-opacity="0.8" fill="#A3B1BF" transform="translate(29.401735, 161.032686) rotate(22.000000) translate(-29.401735, -161.032686) " points="27.7854886 161.032686 29.4017351 162.648605 31.0179816 161.032686 29.4017351 159.416768"/>
|
||||
<polygon id="Stroke-456" transform="translate(29.401735, 161.032686) rotate(22.000000) translate(-29.401735, -161.032686) " points="27.7854886 161.032686 29.4017351 162.648605 31.0179816 161.032686 29.4017351 159.416768"/>
|
||||
<polygon id="Fill-458" fill-opacity="0.8" fill="#A3B1BF" transform="translate(38.778613, 148.026981) rotate(22.000000) translate(-38.778613, -148.026981) " points="37.1623668 148.026981 38.7786133 149.6429 40.3948599 148.026981 38.7786133 146.411063"/>
|
||||
<polygon id="Stroke-460" transform="translate(38.778613, 148.026981) rotate(22.000000) translate(-38.778613, -148.026981) " points="37.1623668 148.026981 38.7786133 149.6429 40.3948599 148.026981 38.7786133 146.411063"/>
|
||||
<polygon id="Fill-462" fill-opacity="0.8" fill="#A3B1BF" transform="translate(37.326984, 144.608121) rotate(22.000000) translate(-37.326984, -144.608121) " points="35.7107375 144.608121 37.3269841 146.22404 38.9432306 144.608121 37.3269841 142.992203"/>
|
||||
<polygon id="Stroke-464" transform="translate(37.326984, 144.608121) rotate(22.000000) translate(-37.326984, -144.608121) " points="35.7107375 144.608121 37.3269841 146.22404 38.9432306 144.608121 37.3269841 142.992203"/>
|
||||
<polygon id="Fill-466" fill-opacity="0.8" fill="#A3B1BF" transform="translate(35.875390, 141.189344) rotate(22.000000) translate(-35.875390, -141.189344) " points="34.2591433 141.189344 35.8753899 142.805263 37.4916364 141.189344 35.8753899 139.573425"/>
|
||||
<polygon id="Stroke-468" transform="translate(35.875390, 141.189344) rotate(22.000000) translate(-35.875390, -141.189344) " points="34.2591433 141.189344 35.8753899 142.805263 37.4916364 141.189344 35.8753899 139.573425"/>
|
||||
<polygon id="Fill-470" fill-opacity="0.8" fill="#A3B1BF" transform="translate(34.423796, 137.770567) rotate(22.000000) translate(-34.423796, -137.770567) " points="32.8075492 137.770567 34.4237957 139.386485 36.0400422 137.770567 34.4237957 136.154648"/>
|
||||
<polygon id="Stroke-472" fill="#B2BECA" transform="translate(34.423796, 137.770567) rotate(22.000000) translate(-34.423796, -137.770567) " points="34.4237957 139.386485 36.0400422 137.770567 34.4237957 136.154648 32.8075492 137.770567"/>
|
||||
<polygon id="Fill-474" fill-opacity="0.8" fill="#A3B1BF" transform="translate(35.622528, 149.366282) rotate(22.000000) translate(-35.622528, -149.366282) " points="34.0062815 149.366282 35.622528 150.982201 37.2387746 149.366282 35.622528 147.750364"/>
|
||||
<polygon id="Stroke-476" transform="translate(35.622528, 149.366282) rotate(22.000000) translate(-35.622528, -149.366282) " points="34.0062815 149.366282 35.622528 150.982201 37.2387746 149.366282 35.622528 147.750364"/>
|
||||
<polygon id="Fill-478" fill-opacity="0.8" fill="#A3B1BF" transform="translate(34.170875, 145.947481) rotate(22.000000) translate(-34.170875, -145.947481) " points="32.5546284 145.947481 34.1708749 147.5634 35.7871215 145.947481 34.1708749 144.331563"/>
|
||||
<polygon id="Stroke-480" transform="translate(34.170875, 145.947481) rotate(22.000000) translate(-34.170875, -145.947481) " points="32.5546284 145.947481 34.1708749 147.5634 35.7871215 145.947481 34.1708749 144.331563"/>
|
||||
<polygon id="Fill-482" fill-opacity="0.8" fill="#A3B1BF" transform="translate(32.719305, 142.528645) rotate(22.000000) translate(-32.719305, -142.528645) " points="31.103058 142.528645 32.7193046 144.144564 34.3355511 142.528645 32.7193046 140.912726"/>
|
||||
<polygon id="Stroke-484" transform="translate(32.719305, 142.528645) rotate(22.000000) translate(-32.719305, -142.528645) " points="31.103058 142.528645 32.7193046 144.144564 34.3355511 142.528645 32.7193046 140.912726"/>
|
||||
<polygon id="Fill-486" fill-opacity="0.8" fill="#A3B1BF" transform="translate(31.267651, 139.109844) rotate(22.000000) translate(-31.267651, -139.109844) " points="29.651405 139.109844 31.2676515 140.725763 32.883898 139.109844 31.2676515 137.493925"/>
|
||||
<polygon id="Stroke-488" transform="translate(31.267651, 139.109844) rotate(22.000000) translate(-31.267651, -139.109844) " points="29.651405 139.109844 31.2676515 140.725763 32.883898 139.109844 31.2676515 137.493925"/>
|
||||
<polygon id="Fill-490" fill-opacity="0.8" fill="#A3B1BF" transform="translate(32.466384, 150.705559) rotate(22.000000) translate(-32.466384, -150.705559) " points="30.8501373 150.705559 32.4663838 152.321478 34.0826303 150.705559 32.4663838 149.089641"/>
|
||||
<polygon id="Stroke-492" transform="translate(32.466384, 150.705559) rotate(22.000000) translate(-32.466384, -150.705559) " points="30.8501373 150.705559 32.4663838 152.321478 34.0826303 150.705559 32.4663838 149.089641"/>
|
||||
<polygon id="Fill-494" fill-opacity="0.8" fill="#A3B1BF" transform="translate(31.014790, 147.286782) rotate(22.000000) translate(-31.014790, -147.286782) " points="29.3985431 147.286782 31.0147896 148.902701 32.6310362 147.286782 31.0147896 145.670863"/>
|
||||
<polygon id="Stroke-496" transform="translate(31.014790, 147.286782) rotate(22.000000) translate(-31.014790, -147.286782) " points="29.3985431 147.286782 31.0147896 148.902701 32.6310362 147.286782 31.0147896 145.670863"/>
|
||||
<polygon id="Fill-498" fill-opacity="0.8" fill="#A3B1BF" transform="translate(29.563219, 143.867946) rotate(22.000000) translate(-29.563219, -143.867946) " points="27.9469727 143.867946 29.5632192 145.483865 31.1794658 143.867946 29.5632192 142.252027"/>
|
||||
<polygon id="Stroke-500" transform="translate(29.563219, 143.867946) rotate(22.000000) translate(-29.563219, -143.867946) " points="27.9469727 143.867946 29.5632192 145.483865 31.1794658 143.867946 29.5632192 142.252027"/>
|
||||
<polygon id="Fill-502" fill-opacity="0.8" fill="#A3B1BF" transform="translate(28.111566, 140.449145) rotate(22.000000) translate(-28.111566, -140.449145) " points="26.4953196 140.449145 28.1115662 142.065063 29.7278127 140.449145 28.1115662 138.833226"/>
|
||||
<polygon id="Stroke-504" transform="translate(28.111566, 140.449145) rotate(22.000000) translate(-28.111566, -140.449145) " points="26.4953196 140.449145 28.1115662 142.065063 29.7278127 140.449145 28.1115662 138.833226"/>
|
||||
<polygon id="Fill-506" fill-opacity="0.8" fill="#A3B1BF" transform="translate(29.310298, 152.044860) rotate(22.000000) translate(-29.310298, -152.044860) " points="27.6940519 152.04486 29.3102985 153.660779 30.926545 152.04486 29.3102985 150.428942"/>
|
||||
<polygon id="Stroke-508" transform="translate(29.310298, 152.044860) rotate(22.000000) translate(-29.310298, -152.044860) " points="27.6940519 152.04486 29.3102985 153.660779 30.926545 152.04486 29.3102985 150.428942"/>
|
||||
<polygon id="Fill-510" fill-opacity="0.8" fill="#A3B1BF" transform="translate(27.858704, 148.626083) rotate(22.000000) translate(-27.858704, -148.626083) " points="26.2424578 148.626083 27.8587043 150.242002 29.4749508 148.626083 27.8587043 147.010164"/>
|
||||
<polygon id="Stroke-512" transform="translate(27.858704, 148.626083) rotate(22.000000) translate(-27.858704, -148.626083) " points="26.2424578 148.626083 27.8587043 150.242002 29.4749508 148.626083 27.8587043 147.010164"/>
|
||||
<polygon id="Fill-514" fill-opacity="0.8" fill="#A3B1BF" transform="translate(26.407134, 145.207247) rotate(22.000000) translate(-26.407134, -145.207247) " points="24.7908874 145.207247 26.4071339 146.823166 28.0233805 145.207247 26.4071339 143.591328"/>
|
||||
<polygon id="Stroke-516" transform="translate(26.407134, 145.207247) rotate(22.000000) translate(-26.407134, -145.207247) " points="24.7908874 145.207247 26.4071339 146.823166 28.0233805 145.207247 26.4071339 143.591328"/>
|
||||
<polygon id="Fill-518" fill-opacity="0.8" fill="#A3B1BF" transform="translate(24.955481, 141.788446) rotate(22.000000) translate(-24.955481, -141.788446) " points="23.3392343 141.788446 24.9554808 143.404364 26.5717274 141.788446 24.9554808 140.172527"/>
|
||||
<polygon id="Stroke-520" transform="translate(24.955481, 141.788446) rotate(22.000000) translate(-24.955481, -141.788446) " points="23.3392343 141.788446 24.9554808 143.404364 26.5717274 141.788446 24.9554808 140.172527"/>
|
||||
<polygon id="Fill-522" fill-opacity="0.8" fill="#A3B1BF" transform="translate(26.154213, 153.384161) rotate(22.000000) translate(-26.154213, -153.384161) " points="24.5379666 153.384161 26.1542132 155.00008 27.7704597 153.384161 26.1542132 151.768243"/>
|
||||
<polygon id="Stroke-524" transform="translate(26.154213, 153.384161) rotate(22.000000) translate(-26.154213, -153.384161) " points="24.5379666 153.384161 26.1542132 155.00008 27.7704597 153.384161 26.1542132 151.768243"/>
|
||||
<polygon id="Fill-526" fill-opacity="0.8" fill="#A3B1BF" transform="translate(24.702619, 149.965384) rotate(22.000000) translate(-24.702619, -149.965384) " points="23.0863724 149.965384 24.702619 151.581303 26.3188655 149.965384 24.702619 148.349465"/>
|
||||
<polygon id="Stroke-528" transform="translate(24.702619, 149.965384) rotate(22.000000) translate(-24.702619, -149.965384) " points="23.0863724 149.965384 24.702619 151.581303 26.3188655 149.965384 24.702619 148.349465"/>
|
||||
<polygon id="Fill-530" fill-opacity="0.8" fill="#A3B1BF" transform="translate(23.250966, 146.546583) rotate(22.000000) translate(-23.250966, -146.546583) " points="21.6347194 146.546583 23.2509659 148.162502 24.8672124 146.546583 23.2509659 144.930664"/>
|
||||
<polygon id="Stroke-532" transform="translate(23.250966, 146.546583) rotate(22.000000) translate(-23.250966, -146.546583) " points="21.6347194 146.546583 23.2509659 148.162502 24.8672124 146.546583 23.2509659 144.930664"/>
|
||||
<polygon id="Fill-534" fill-opacity="0.8" fill="#A3B1BF" transform="translate(21.799396, 143.127747) rotate(22.000000) translate(-21.799396, -143.127747) " points="20.183149 143.127747 21.7993955 144.743665 23.4156421 143.127747 21.7993955 141.511828"/>
|
||||
<polygon id="Stroke-536" transform="translate(21.799396, 143.127747) rotate(22.000000) translate(-21.799396, -143.127747) " points="20.183149 143.127747 21.7993955 144.743665 23.4156421 143.127747 21.7993955 141.511828"/>
|
||||
<path d="M129.314819,189.942425 C126.372666,187.000233 124.490222,184.112668 125.110927,183.492089 C125.731631,182.87151 128.619783,184.753573 131.562571,187.695129 C134.504724,190.637321 136.387169,193.524886 135.766464,194.145465 C135.145759,194.766044 132.257608,192.883981 129.314819,189.942425 Z" id="Fill-538" fill="#F5F5F5" transform="translate(130.438695, 188.818777) rotate(22.000000) translate(-130.438695, -188.818777) "/>
|
||||
<path d="M129.314819,189.942425 C126.372666,187.000233 124.490222,184.112668 125.110927,183.492089 C125.731631,182.87151 128.619783,184.753573 131.562571,187.695129 C134.504724,190.637321 136.387169,193.524886 135.766464,194.145465 C135.145759,194.766044 132.257608,192.883981 129.314819,189.942425 Z" id="Stroke-540" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(130.438695, 188.818777) rotate(22.000000) translate(-130.438695, -188.818777) "/>
|
||||
<path d="M136.083433,187.715086 C130.20421,181.837055 126.443133,176.067006 127.683272,174.827118 C128.92341,173.587231 134.69463,177.347545 140.573854,183.225576 C146.453713,189.104243 150.21479,194.874292 148.974651,196.114179 C147.734513,197.354066 141.963293,193.593752 136.083433,187.715086 Z" id="Fill-542" fill="#F5F5F5" transform="translate(138.328961, 185.470648) rotate(22.000000) translate(-138.328961, -185.470648) "/>
|
||||
<path d="M136.083433,187.715086 C130.20421,181.837055 126.443133,176.067006 127.683272,174.827118 C128.92341,173.587231 134.69463,177.347545 140.573854,183.225576 C146.453713,189.104243 150.21479,194.874292 148.974651,196.114179 C147.734513,197.354066 141.963293,193.593752 136.083433,187.715086 Z" id="Stroke-544" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(138.328961, 185.470648) rotate(22.000000) translate(-138.328961, -185.470648) "/>
|
||||
<path d="M146.697288,183.650834 C138.818086,175.773231 133.777481,168.04045 135.43947,166.378798 C137.101459,164.717146 144.835809,169.756729 152.715011,177.634332 C160.594213,185.511936 165.634818,193.244716 163.972829,194.906369 C162.310839,196.568021 154.57649,191.528438 146.697288,183.650834 Z" id="Fill-546" fill-opacity="0.35" fill="#F5F5F5" transform="translate(149.706149, 180.642583) rotate(22.000000) translate(-149.706149, -180.642583) "/>
|
||||
<path d="M146.697288,183.650834 C138.818086,175.773231 133.777481,168.04045 135.43947,166.378798 C137.101459,164.717146 144.835809,169.756729 152.715011,177.634332 C160.594213,185.511936 165.634818,193.244716 163.972829,194.906369 C162.310839,196.568021 154.57649,191.528438 146.697288,183.650834 Z" id="Stroke-548" stroke="#A3B1BF" stroke-width="1.62" stroke-linejoin="round" transform="translate(149.706149, 180.642583) rotate(22.000000) translate(-149.706149, -180.642583) "/>
|
||||
<path d="M65.2279553,90.2345285 C62.4548336,90.0320836 60.4105231,89.3345405 60.4105231,88.5051511 C60.4105231,87.676673 62.4503446,86.9797514 65.2188187,86.7764428 C65.4149139,84.0068366 66.0879499,81.9659221 66.8880741,81.9659221 C67.6881984,81.9659221 68.3612344,84.0068366 68.5573295,86.7764428 C71.3258036,86.9797514 73.3656251,87.676673 73.3656251,88.5051511 C73.3656251,89.3345405 71.3213146,90.0320836 68.548193,90.2345285 C68.3410329,92.9408238 67.6762073,94.9210242 66.8880741,94.9210242 C66.0999409,94.9210242 65.4351154,92.9408238 65.2279553,90.2345285 Z" id="Oval-80-Copy-3" fill-opacity="0.4" fill="#A3B1BF" opacity="0.85" transform="translate(66.888074, 88.443473) rotate(-340.000000) translate(-66.888074, -88.443473) "/>
|
||||
<path d="M118.775569,329.140492 L118.424988,329.526146 C117.522244,330.5192 115.985396,330.592411 114.992341,329.689667 C114.596934,329.330218 114.331035,328.850456 114.235803,328.324641 L114.143075,327.812647 L113.669257,327.599091 C112.445737,327.047635 111.900921,325.608734 112.452377,324.385214 C112.672015,323.8979 113.046204,323.496601 113.516991,323.243469 L113.97521,322.997095 L114.032158,322.478985 C114.178786,321.144967 115.379087,320.182398 116.713105,320.329026 C117.24418,320.387399 117.741214,320.61917 118.127299,320.988476 L118.503957,321.348765 L119.01334,321.242962 C120.327346,320.970031 121.613813,321.813989 121.886744,323.127995 C121.995449,323.651349 121.928561,324.195943 121.696437,324.677436 L121.470742,325.145594 L121.728812,325.597409 C122.394441,326.762759 121.989337,328.247061 120.823987,328.91269 C120.359975,329.177726 119.82154,329.282421 119.292023,329.21057 L118.775569,329.140492 Z" id="Star-1-Copy-16" fill-opacity="0.4" fill="#A3B1BF"/>
|
||||
<path d="M432.83752,120.004816 L432.837087,120.005044 L432.837087,120.005044 C432.092472,120.396429 431.171561,120.110079 430.780176,119.365464 C430.624337,119.068978 430.570573,118.729391 430.627206,118.399266 L430.627206,118.399266 L430.627206,118.399266 L430.627206,118.399266 C430.024845,117.812233 430.01242,116.848039 430.599453,116.245678 C430.833262,116.005765 431.139652,115.849637 431.471171,115.801474 L431.471471,115.801431 L431.471471,115.801431 L431.471471,115.801431 C431.843891,115.046982 432.757399,114.737288 433.511848,115.109709 C433.812195,115.25797 434.055308,115.501083 434.20357,115.801431 L434.20357,115.801431 L434.203869,115.801474 L434.203869,115.801474 C435.036229,115.922398 435.612963,116.695187 435.492039,117.527547 C435.443876,117.859067 435.287748,118.165457 435.047835,118.399266 L435.047835,118.399266 L435.047835,118.399266 L435.047835,118.399266 C435.190066,119.228364 434.63325,120.015783 433.804152,120.158014 C433.474027,120.214647 433.134439,120.160883 432.837954,120.005044 L432.83752,120.004816 Z" id="Star-1-Copy-18" fill-opacity="0.4" fill="#A3B1BF" transform="translate(432.837619, 117.566125) rotate(-3.000000) translate(-432.837619, -117.566125) "/>
|
||||
<path d="M99.7727703,38.762372 L99.0266308,39.1545584 L99.0266308,39.1545584 C97.8386845,39.7789675 96.36948,39.3221304 95.7450708,38.134184 C95.4964477,37.6611755 95.4106732,37.1194035 95.5010241,36.5927278 L95.6433921,35.7628333 L95.0407812,35.1755563 L95.0407812,35.1755563 C94.0796578,34.2388893 94.059832,32.7004261 94.996499,31.7393027 C95.369563,31.3564979 95.8584376,31.1073808 96.3874089,31.0305329 L97.2206742,30.9094778 L97.5937913,30.1536188 L97.5937913,30.1536188 C98.1878378,28.950202 99.6449697,28.4562097 100.848387,29.0502562 C101.327469,29.2867472 101.715258,29.6745363 101.951749,30.1536188 L102.324866,30.9094778 L103.158132,31.0305329 L103.158132,31.0305329 C104.486241,31.223478 105.406474,32.4565366 105.213529,33.7846464 C105.136681,34.3136177 104.887564,34.8024923 104.504759,35.1755563 L103.902148,35.7628333 L104.044516,36.5927278 L104.044516,36.5927278 C104.27143,37.9154575 103.383095,39.1716915 102.060366,39.398605 C101.53369,39.4889559 100.991918,39.4031814 100.51891,39.1545584 L99.7727703,38.762372 Z" id="Star-1-Copy-19" fill-opacity="0.4" fill="#A3B1BF" transform="translate(99.772928, 34.116388) rotate(19.000000) translate(-99.772928, -34.116388) "/>
|
||||
<path d="M396.631073,305.744172 L396.098125,305.608596 C394.797498,305.277731 394.01135,303.955146 394.342215,302.654518 C394.473956,302.136643 394.772729,301.676633 395.192288,301.345694 L395.623376,301.00566 L395.587617,300.458372 C395.500116,299.119176 396.514815,297.962609 397.854012,297.875108 C398.387399,297.840257 398.917395,297.982247 399.361919,298.279089 L399.818473,298.583965 L400.329362,298.380373 C401.576068,297.883555 402.989473,298.491459 403.486291,299.738164 C403.684076,300.23448 403.712777,300.782145 403.567956,301.296416 L403.418882,301.825788 L403.76981,302.247972 C404.627682,303.280038 404.486471,304.812135 403.454406,305.670007 C403.043346,306.011688 402.531095,306.208301 401.996987,306.229395 L401.44896,306.251039 L401.15585,306.715309 C400.439401,307.850123 398.938655,308.189275 397.80384,307.472826 C397.351988,307.187555 397.006767,306.761296 396.821614,306.260029 L396.631073,305.744172 Z" id="Star-1-Copy-20" fill-opacity="0.4" fill="#A3B1BF"/>
|
||||
<path d="M439.755536,146.347757 L439.755103,146.347984 L439.755103,146.347984 C439.010488,146.739369 438.089577,146.45302 437.698192,145.708405 C437.542353,145.411919 437.488589,145.072331 437.545222,144.742206 L437.545222,144.742206 L437.545222,144.742206 L437.545222,144.742206 C436.942861,144.155173 436.930436,143.190979 437.517469,142.588619 C437.751277,142.348705 438.057668,142.192577 438.389187,142.144415 L438.389487,142.144371 L438.389487,142.144371 L438.389487,142.144371 C438.761907,141.389923 439.675415,141.080228 440.429864,141.452649 C440.730211,141.600911 440.973324,141.844024 441.121586,142.144371 L441.121586,142.144371 L441.121885,142.144415 L441.121885,142.144415 C441.954245,142.265338 442.530978,143.038128 442.410055,143.870488 C442.361892,144.202007 442.205764,144.508398 441.965851,144.742206 L441.965851,144.742206 L441.965851,144.742206 L441.965851,144.742206 C442.108082,145.571305 441.551266,146.358723 440.722168,146.500955 C440.392043,146.557588 440.052455,146.503823 439.755969,146.347984 L439.755536,146.347757 Z" id="Star-1-Copy-21" fill-opacity="0.4" fill="#A3B1BF" transform="translate(439.755635, 143.909066) rotate(-12.000000) translate(-439.755635, -143.909066) "/>
|
||||
<path d="M244.621971,220.61341 C277.15945,220.61341 303.536288,194.242112 303.536288,161.711467 C303.536288,129.180822 277.15945,102.809524 244.621971,102.809524 C212.084491,102.809524 185.707653,129.180822 185.707653,161.711467 C185.707653,194.242112 212.084491,220.61341 244.621971,220.61341 Z" id="Oval-8-Copy-2" fill-opacity="0.66" fill="#A3B1BF"/>
|
||||
<path d="M245.345696,141.060063 C241.146656,141.060063 237.880736,142.342349 235.547936,144.906921 C233.215136,147.354921 232.107056,150.618921 232.107056,154.698921 L236.714336,154.698921 C236.714336,151.842921 237.355856,149.628063 238.638896,147.996063 C240.038576,146.072635 242.196416,145.140063 245.112416,145.140063 C247.561856,145.140063 249.486416,145.781206 250.827776,147.180063 C252.110816,148.462349 252.810656,150.269206 252.810656,152.600635 C252.810656,154.232635 252.227456,155.748063 251.061056,157.205206 C250.711136,157.671492 250.011296,158.370921 249.078176,159.303492 C245.928896,162.101206 244.004336,164.316063 243.187856,166.064635 C242.488016,167.521778 242.138096,169.212063 242.138096,171.135492 L242.138096,172.476063 L246.803696,172.476063 L246.803696,171.135492 C246.803696,169.561778 247.153616,168.162921 247.911776,166.880635 C248.494976,165.831492 249.369776,164.782349 250.652816,163.674921 C253.218896,161.401778 254.793536,159.886349 255.376736,159.186921 C256.834736,157.263492 257.592896,154.990349 257.592896,152.367492 C257.592896,148.870349 256.484816,146.130921 254.326976,144.149206 C252.052496,142.050921 249.078176,141.060063 245.345696,141.060063 Z M244.470896,176.556063 C243.479456,176.556063 242.662976,176.847492 241.963136,177.546921 C241.263296,178.188063 240.971696,179.004063 240.971696,179.994921 C240.971696,180.985778 241.263296,181.801778 241.963136,182.501206 C242.662976,183.142349 243.479456,183.492063 244.470896,183.492063 C245.462336,183.492063 246.278816,183.142349 246.978656,182.501206 C247.678496,181.860063 248.028416,181.044063 248.028416,179.994921 C248.028416,179.004063 247.678496,178.188063 247.036976,177.546921 C246.337136,176.847492 245.462336,176.556063 244.470896,176.556063 Z" id="?" fill="#FFFFFF"/>
|
||||
<path d="M223.261662,133.139924 C229.077338,129.782947 239.631808,125.828273 236.274126,120.013819 C232.916444,114.199364 220.565868,115.043746 214.750193,118.400722 C208.934517,121.757699 202.729834,131.623951 206.087516,137.438405 C209.445198,143.252859 217.445986,136.4969 223.261662,133.139924" id="Oval-8-Copy-2" fill="#FAFAFA"/>
|
||||
<path d="M249.679003,119.820589 C252.365149,119.820589 254.5427,117.643495 254.5427,114.957913 C254.5427,112.272332 252.365149,110.095238 249.679003,110.095238 C246.992857,110.095238 244.815306,112.272332 244.815306,114.957913 C244.815306,117.643495 246.992857,119.820589 249.679003,119.820589 Z" id="Oval-10-Copy" fill="#FAFAFA"/>
|
||||
<path d="M421.5,321.5 L423,323" id="Line-2" stroke="#979797" stroke-linecap="square"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 84 KiB |
3
Backend/src/icons/svg/note-book.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg focusable="false" viewBox="0 0 16 16" aria-hidden="true" role="presentation" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.196,5.907H2.588V6.34h3.608V5.907z M2.588,9.515h4.33V9.082h-4.33V9.515z M5.763,7.495H2.588v0.433 h3.175V7.495z M4.753,4.32H2.588v0.433h2.165V4.32z M9.515,2.588c0,0-0.627,0-0.866,0H8.505c-0.353,0-0.503,0.243-0.503,0.433 c0-0.17-0.154-0.433-0.508-0.433H7.206c-0.239,0-0.866,0-0.866,0H1v10.68h6.639c0.09,0,0.144,0.07,0.144,0.144h0.433 c0-0.079,0.062-0.144,0.144-0.144H15V2.588C15,2.588,9.515,2.588,9.515,2.588z M7.784,3.598c0,0.159,0,9.237,0,9.237H1.433V3.021 H6.34c0,0,0.706,0,0.866,0h0.289c0.159,0,0.289,0.129,0.289,0.289V3.598z M14.567,12.835H8.216c0,0,0-9.078,0-9.237V3.309 c0-0.159,0.129-0.289,0.289-0.289h0.289c0.159,0,0.722,0,0.722,0h5.052V12.835z M9.371,11.103h3.608V10.67H9.371V11.103z M9.371,6.34h2.021V4.32H9.371V6.34z M9.804,4.753h1.155v1.155H9.804V4.753z M9.371,9.515h4.33V9.082h-4.33V9.515z M9.371,7.928 h2.887V7.495H9.371V7.928z M2.588,11.103h2.165V10.67H2.588V11.103z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
43
Backend/src/icons/svg/note.svg
Executable file
@@ -0,0 +1,43 @@
|
||||
<svg focusable="false" viewBox="0 0 220 220" aria-hidden="true" role="presentation" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#F9FAFA" d="M187.508,86.089l-84.87-49c-4.783-2.761-10.899-1.123-13.66,3.66 c-2.761,4.783-1.123,10.899,3.66,13.66l84.87,49c4.783,2.761,10.899,1.123,13.66-3.66 C193.929,94.966,192.291,88.85,187.508,86.089z M176.56,149.05l-30.311-17.5c4.783,2.761,10.899,1.122,13.66-3.66 c2.761-4.783,1.123-10.899-3.66-13.66L116.06,91.026l38.457,22.203c4.783,2.761,10.899,1.123,13.66-3.66 c2.761-4.783,1.123-10.899-3.66-13.66l-100.459-58c-4.783-2.761-10.899-1.123-13.66,3.66c-2.761,4.783-1.123,10.899,3.66,13.66 l6.929,4c4.782,2.762,6.421,8.877,3.66,13.66c-2.761,4.783-8.877,6.422-13.66,3.66l38.971,22.5l-65.818-38 c-4.783-2.761-10.899-1.123-13.66,3.66c-2.761,4.783-1.123,10.899,3.66,13.66l25.115,14.5c4.783,2.761,6.421,8.877,3.66,13.66 c-2.761,4.783-8.877,6.422-13.66,3.66l25.548,14.75l-26.414-15.25c-4.783-2.761-10.899-1.123-13.66,3.66 c-2.761,4.783-1.123,10.899,3.66,13.66l105.655,61c4.783,2.761,10.899,1.123,13.66-3.66c2.761-4.783,1.123-10.899-3.66-13.66 l-2.598-1.5c-4.783-2.761-6.422-8.877-3.66-13.66c2.761-4.783,8.877-6.422,13.66-3.66l25.115,14.5 c4.783,2.761,10.899,1.123,13.66-3.66C182.982,157.927,181.343,151.811,176.56,149.05z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M56.375,71h91v68h-91V71z"/>
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#D1D1D1" d="M201.75,85.25c-2.761,0-5,2.239-5,5s2.239,5,5,5s5-2.239,5-5 S204.511,85.25,201.75,85.25z M201.75,94.25c-2.209,0-4-1.791-4-4s1.791-4,4-4s4,1.791,4,4S203.959,94.25,201.75,94.25z M24.75,64.25c-1.657,0-3,1.343-3,3c0,1.657,1.343,3,3,3s3-1.343,3-3C27.75,65.593,26.407,64.25,24.75,64.25z M24.75,69.25 c-1.105,0-2-0.895-2-2s0.895-2,2-2s2,0.895,2,2S25.855,69.25,24.75,69.25z M196.75,144.25c-1.657,0-3,1.343-3,3 c0,1.657,1.343,3,3,3s3-1.343,3-3C199.75,145.593,198.407,144.25,196.75,144.25z M196.75,149.25c-1.105,0-2-0.895-2-2s0.895-2,2-2 s2,0.895,2,2S197.855,149.25,196.75,149.25z M186.75,173.25c-2.761,0-5,2.239-5,5s2.239,5,5,5s5-2.239,5-5 S189.511,173.25,186.75,173.25z M186.75,182.25c-2.209,0-4-1.791-4-4s1.791-4,4-4s4,1.791,4,4S188.959,182.25,186.75,182.25z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g opacity="0.6">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#CBCBCB" d="M207.85,164.1c-0.331,0-0.6,0.269-0.6,0.6v3.8 c0,0.331,0.269,0.6,0.6,0.6c0.331,0,0.6-0.269,0.6-0.6v-3.8C208.45,164.369,208.181,164.1,207.85,164.1z M83.85,29.1 c-0.331,0-0.6,0.269-0.6,0.6v3.8c0,0.331,0.269,0.6,0.6,0.6c0.331,0,0.6-0.269,0.6-0.6v-3.8C84.45,29.369,84.181,29.1,83.85,29.1z M158.85,169.1c-0.331,0-0.6,0.269-0.6,0.6v5.8c0,0.331,0.269,0.6,0.6,0.6c0.331,0,0.6-0.269,0.6-0.6v-5.8 C159.45,169.369,159.181,169.1,158.85,169.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g opacity="0.8">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#CCCCCC" d="M209.45,166.095h-3.2c-0.221,0-0.4,0.226-0.4,0.505 s0.179,0.505,0.4,0.505h3.2c0.221,0,0.4-0.226,0.4-0.505S209.671,166.095,209.45,166.095z M85.45,31.095h-3.2 c-0.221,0-0.4,0.226-0.4,0.505s0.179,0.505,0.4,0.505h3.2c0.221,0,0.4-0.226,0.4-0.505S85.671,31.095,85.45,31.095z M161.45,172.095h-5.2c-0.221,0-0.4,0.226-0.4,0.505s0.179,0.505,0.4,0.505h5.2c0.221,0,0.4-0.226,0.4-0.505 S161.671,172.095,161.45,172.095z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#CBCBCB" d="M51.583,50.917h-8c-0.552,0-1,0.448-1,1c0,0.552,0.448,1,1,1h8 c0.552,0,1-0.448,1-1C52.583,51.364,52.136,50.917,51.583,50.917z M192.583,102.917h-8c-0.552,0-1,0.448-1,1c0,0.552,0.448,1,1,1 h8c0.552,0,1-0.448,1-1C193.583,103.364,193.136,102.917,192.583,102.917z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#CBCBCB" d="M47.583,46.917c-0.552,0-1,0.448-1,1v8c0,0.552,0.448,1,1,1 s1-0.448,1-1v-8C48.583,47.364,48.136,46.917,47.583,46.917z M188.583,98.917c-0.552,0-1,0.448-1,1v8c0,0.552,0.448,1,1,1 s1-0.448,1-1v-8C189.583,99.364,189.136,98.917,188.583,98.917z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#CCCCCC" d="M89.25,91h-25v3h25V91z M64.25,116h30v-3h-30V116z M86.25,102 h-22v3h22V102z M79.25,80h-15v3h15V80z M112.25,68c0,0-4.343,0-6,0h-1c-2.447,0-3.482,1.687-3.482,3c0-1.181-1.064-3-3.518-3h-2 c-1.657,0-6,0-6,0h-37v74h46c0.625,0,1,0.483,1,1h3c0-0.544,0.428-1,1-1h46V68H112.25z M100.25,75c0,1.105,0,64,0,64h-44V71h34 c0,0,4.895,0,6,0h2c1.105,0,2,0.895,2,2V75z M147.25,139h-44c0,0,0-62.895,0-64v-2c0-1.105,0.895-2,2-2h2c1.105,0,5,0,5,0h35V139z M111.25,127h25v-3h-25V127z M111.25,94h14V80h-14V94z M114.25,83h8v8h-8V83z M111.25,116h30v-3h-30V116z M111.25,105h20v-3h-20 V105z M64.25,127h15v-3h-15V127z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g opacity="0.2">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#CCCCCC" d="M41.333,29.083c-2.209,0-4,1.791-4,4s1.791,4,4,4s4-1.791,4-4 S43.542,29.083,41.333,29.083z M41.333,36.095c-1.663,0-3.012-1.348-3.012-3.012c0-1.663,1.349-3.012,3.012-3.012 c1.663,0,3.012,1.349,3.012,3.012C44.345,34.747,42.997,36.095,41.333,36.095z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
4
Backend/src/icons/svg/zhuangfa.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<svg aria-hidden="true" viewBox="0 0 1024 1024">
|
||||
<path d="M513.28 586.624c0 48.853333 50.986667 71.36 87.125333 38.656l263.104-237.973333a60.096 60.096 0 0 0-2.88-92.650667L599.210667 86.570667c-37.077333-29.504-85.930667-6.058667-85.930667 41.493333v91.626667C172.906667 259.413333 162.56 545.642667 170.666667 565.333333c9.322667 22.741333 38.613333 42.794667 87.189333 13.248 53.290667-32.426667 131.114667-61.312 255.424-65.834666v73.877333z m85.333333-391.466667l186.24 148.266667-186.24 168.426667V426.666667h-42.666666c-135.850667 0-236.565333 27.178667-307.306667 64 4.842667-9.194667 10.133333-24.832 16.106667-33.728C319.36 375.466667 384 311.978667 561.792 298.24l36.821333-2.688V195.157333z" fill="#333333"/>
|
||||
<path d="M341.333333 704h554.666667v85.333333H341.333333zM128 874.666667h768v85.333333H128z" fill="#333333"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 846 B |
BIN
Backend/src/icons/user-avatar.png
Executable file
|
After Width: | Height: | Size: 4.9 KiB |
49
Backend/src/main.js
Executable file
@@ -0,0 +1,49 @@
|
||||
import 'core-js/stable'
|
||||
import 'regenerator-runtime/runtime'
|
||||
|
||||
import Vue from 'vue'
|
||||
import App from '@/App'
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
import MainMixin from './mixins/main-mixin'
|
||||
|
||||
import './core/lazy-use'
|
||||
import './core/global-component'
|
||||
import './core/filter'
|
||||
import './core/directives'
|
||||
import '@/permission'
|
||||
import '@/icons'
|
||||
|
||||
// 引入自定义全局css
|
||||
import '@/assets/css/global.less'
|
||||
|
||||
Vue.prototype.$userRoles = [];
|
||||
Vue.config.productionTip = false
|
||||
|
||||
const Instance = new Vue({
|
||||
router,
|
||||
store,
|
||||
mixins: [MainMixin],
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
||||
export default Instance
|
||||
|
||||
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
|
||||
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
|
||||
Date.prototype.Format = function (fmt) { // author: meizz
|
||||
var o = {
|
||||
"M+": this.getMonth() + 1, // 月份
|
||||
"d+": this.getDate(), // 日
|
||||
"h+": this.getHours(), // 小时
|
||||
"m+": this.getMinutes(), // 分
|
||||
"s+": this.getSeconds(), // 秒
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3), // 季度
|
||||
"S": this.getMilliseconds() // 毫秒
|
||||
};
|
||||
if (/(y+)/.test(fmt))
|
||||
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||
for (var k in o)
|
||||
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
|
||||
return fmt;
|
||||
}
|
||||
58
Backend/src/mixins/main-mixin.js
Executable file
@@ -0,0 +1,58 @@
|
||||
import SocketInstance from '../socket-instance'
|
||||
import { ServeGetUser } from '@/api/user'
|
||||
|
||||
export default {
|
||||
created() {
|
||||
// 判断用户是否登录
|
||||
if (this.$store.getters.loginStatus) {
|
||||
this.initialize()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 页面初始化设置
|
||||
initialize() {
|
||||
//SocketInstance.connect()
|
||||
this.loadUser()
|
||||
},
|
||||
|
||||
// 加载用户相关设置信息,更新本地缓存
|
||||
loadUser() {
|
||||
ServeGetUser().then(({ code, data }) => {
|
||||
if (code == 200) {
|
||||
const { user } = data
|
||||
|
||||
this.$store.commit('UPDATE_USER_INFO', {
|
||||
id: user.id,
|
||||
//channel_id: user.channel_id,
|
||||
//channel_name: user.channel_name,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
remark: user.remark,
|
||||
login_time: user.login_time,
|
||||
login_count: user.login_count,
|
||||
login_ip: user.login_ip,
|
||||
create_time: user.create_time,
|
||||
roles: user.roles,
|
||||
mod_game: user.mod_game,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.$root.$children[0].refreshView()
|
||||
},
|
||||
|
||||
// 跳转到指定好友对话页
|
||||
dumpTalkPage(index_name) {
|
||||
sessionStorage.setItem('send_message_index_name', index_name)
|
||||
|
||||
if (this.$route.path == '/message') {
|
||||
this.reload()
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push('/message')
|
||||
},
|
||||
},
|
||||
}
|
||||
48
Backend/src/permission.js
Executable file
@@ -0,0 +1,48 @@
|
||||
import router from '@/router'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import config from '@/config/config'
|
||||
import {getToken, setToken} from '@/utils/auth'
|
||||
import ar from "element-ui/src/locale/lang/ar";
|
||||
|
||||
NProgress.configure({
|
||||
showSpinner: false,
|
||||
})
|
||||
|
||||
const WEBSITE_NAME = config.WEBSITE_NAME
|
||||
|
||||
// 登录用户强制重定向页面
|
||||
const redirect = ['/auth/login', '/auth/register', '/auth/forget']
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
document.title = to.meta.title
|
||||
? `${WEBSITE_NAME} | ${to.meta.title}`
|
||||
: WEBSITE_NAME
|
||||
|
||||
// 链接传TOKEN
|
||||
if (/\?token=.*/i.test(location.href)) {
|
||||
let args = location.href.substr(location.href.indexOf('?') + 1).split('&')
|
||||
for (var i = 0; i < args.length; i ++) {
|
||||
var arg = args[i].split('=');
|
||||
if (arg.length >=2 && arg[0].toLowerCase() === 'token' && arg[1]) {
|
||||
setToken(arg[1], 7 * 24 * 3600)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有token说明该用户已登陆
|
||||
if (getToken()) {
|
||||
if (redirect.indexOf(to.path) >= 0) {
|
||||
next('/')
|
||||
}
|
||||
} else if (to.meta.needLogin) {
|
||||
next('/auth/login')
|
||||
}
|
||||
|
||||
NProgress.start()
|
||||
next()
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
})
|
||||