升级Vue3,iView替换ElementPlus

- 删除babel配置、更新依赖与入口初始化
- 全量替换UI组件、样式适配,新增迁移文档与标签/过滤器自动化替换脚本
This commit is contained in:
lifenlong
2026-06-05 17:49:43 +08:00
parent 615ee91511
commit 832fda813b
322 changed files with 25693 additions and 24453 deletions

View File

@@ -1,19 +1,10 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
[
"import",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
],
[
"prismjs",
{
"languages": [
languages: [
"html",
"css",
"less",
@@ -42,10 +33,10 @@ module.exports = {
"yml",
"md",
"erlang",
"ini"
"ini",
],
"theme": "okaidia"
}
]
]
}
theme: "okaidia",
},
],
],
};

View File

@@ -8,43 +8,43 @@
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"engines": {
"node": ">=16"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^0.21.4",
"babel-plugin-prismjs": "^2.0.1",
"core-js": "^3.6.5",
"element-ui": "^2.14.1",
"core-js": "^3.36.0",
"element-plus": "^2.6.3",
"js-audio-recorder": "^1.0.6",
"js-base64": "^2.5.1",
"mavon-editor": "^2.10.4",
"nprogress": "^0.2.0",
"prismjs": "^1.29.0",
"qs": "^6.9.4",
"svg-sprite-loader": "^5.0.0",
"vue": "^2.6.11",
"vue-contextmenujs": "^1.3.13",
"vue-cropper": "^0.5.5",
"vue-prism-editor": "^0.5.1",
"vue-router": "^3.4.9",
"vue-virtual-scroller": "^1.1.2",
"vuex": "^3.5.1"
"vue": "^3.4.21",
"vue-cropper": "^1.1.1",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "^4.3.0",
"vue-virtual-scroller": "^3.0.4",
"vuex": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/compiler-sfc": "^3.4.21",
"babel-eslint": "^10.1.0",
"babel-plugin-import": "^1.13.1",
"compression-webpack-plugin": "^5.0.0",
"compression-webpack-plugin": "^10.0.0",
"eslint": "^8.28.0",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"eslint-plugin-vue": "^9.22.0",
"less": "^4.2.0",
"less-loader": "^11.1.4",
"postcss": "^8.4.20",
"style-resources-loader": "^1.4.1",
"vue-cli-plugin-style-resources-loader": "^0.1.4",
"vue-svg-component-runtime": "^1.0.1",
"vue-svg-icon-loader": "^2.1.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^5.75.0"
"webpack": "^5.95.0"
},
"eslintConfig": {
"root": true,
@@ -52,7 +52,7 @@
"node": true
},
"extends": [
"plugin:vue/essential",
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {

21
im/scripts/fix-deep.js Normal file
View File

@@ -0,0 +1,21 @@
const fs = require("fs");
const path = require("path");
function walk(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walk(full, files);
else if (/\.(vue|less)$/.test(entry.name)) files.push(full);
}
return files;
}
const root = path.join(__dirname, "../src");
for (const file of walk(root)) {
let content = fs.readFileSync(file, "utf8");
const next = content.replace(/\/deep\/\s*([^\{]+)\{/g, ":deep($1) {");
if (next !== content) {
fs.writeFileSync(file, next);
console.log("fixed:", file);
}
}

View File

@@ -4,6 +4,8 @@
</div>
</template>
<script>
import { setRefreshView } from "@/utils/app-bridge";
export default {
name: 'App',
data () {
@@ -35,10 +37,11 @@ export default {
},
mounted () {
setRefreshView(this.refreshView.bind(this));
window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
window.addEventListener('unload', e => this.unloadHandler(e))
},
destroyed () {
beforeUnmount () {
window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e))
window.removeEventListener('unload', e => this.unloadHandler(e))
},

View File

@@ -173,7 +173,7 @@
}
.main {
/deep/.el-input__inner {
:deep(.el-input__inner ) {
border-radius: 1px !important;
}
@@ -238,4 +238,44 @@
.flex-10 {
flex: 10;
}
.im-contextmenu {
padding: 4px 0;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.im-contextmenu-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
font-size: 13px;
color: #606266;
cursor: pointer;
white-space: nowrap;
&:hover {
background: #f5f7fa;
color: #409eff;
}
&.is-disabled {
color: #c0c4cc;
cursor: not-allowed;
&:hover {
background: transparent;
color: #c0c4cc;
}
}
}
.im-contextmenu-divider {
height: 1px;
margin: 4px 0;
background: #ebeef5;
}

View File

@@ -16,7 +16,7 @@
flex-shrink: 0;
height: 40px;
/deep/.el-input .el-input__inner {
:deep(.el-input .el-input__inner ) {
border-radius: 20px;
width: 170px;
}

View File

@@ -1,4 +1,4 @@
/deep/.el-input__inner {
:deep(.el-input__inner ) {
border-radius: 1px !important;
}

View File

@@ -43,7 +43,7 @@
}
}
/deep/.text-message {
:deep(.text-message ) {
border-radius: 0;
}
}

View File

@@ -43,14 +43,9 @@
</div>
</template>
<script>
import PrismEditor from "vue-prism-editor";
import "vue-prism-editor/dist/VuePrismEditor.css";
import { PrismEditor } from "vue-prism-editor";
import "vue-prism-editor/dist/prismeditor.min.css";
import "prismjs/themes/prism-okaidia.css";
import Vue from "vue";
import { Select, Option } from "element-ui";
Vue.use(Select);
Vue.use(Option);
export default {
name: "TalkCodeBlock",
components: {
@@ -284,29 +279,29 @@ export default {
max-width: 100%;
}
/deep/ .el-input__inner {
:deep(.el-input__inner ) {
border-radius: 0;
width: 130px;
}
/deep/ pre {
:deep(pre ) {
border-radius: 0;
}
/deep/ .prism-editor-wrapper pre::-webkit-scrollbar {
:deep(.prism-editor-wrapper pre::-webkit-scrollbar ) {
background-color: #272822;
}
/deep/ .prism-editor-wrapper pre::-webkit-scrollbar-thumb {
:deep(.prism-editor-wrapper pre::-webkit-scrollbar-thumb ) {
background-color: #41413f;
cursor: pointer;
}
/deep/ .prism-editor-wrapper::-webkit-scrollbar {
:deep(.prism-editor-wrapper::-webkit-scrollbar ) {
background-color: #272822;
}
/deep/ .prism-editor-wrapper::-webkit-scrollbar-thumb {
:deep(.prism-editor-wrapper::-webkit-scrollbar-thumb ) {
background-color: rgb(114, 112, 112);
cursor: pointer;
}

View File

@@ -131,7 +131,7 @@ export default {
height: 600px;
}
/deep/.el-scrollbar__wrap {
:deep(.el-scrollbar__wrap ) {
overflow-x: hidden;
}

View File

@@ -397,7 +397,7 @@ export default {
};
</script>
<style lang="less" scoped>
/deep/.el-scrollbar__wrap {
:deep(.el-scrollbar__wrap ) {
overflow-x: hidden;
}

View File

@@ -125,7 +125,7 @@ export default {
justify-content: center;
box-sizing: border-box;
/deep/.el-divider {
:deep(.el-divider ) {
background: white;
}

View File

@@ -7,8 +7,12 @@
:style="getImgStyle(src)"
:preview-src-list="[src]"
>
<div slot="error" class="image-slot">图片加载失败...</div>
<div slot="placeholder" class="image-slot">图片加载...</div>
<template #error>
<div class="image-slot">图片加载失败...</div>
</template>
<template #placeholder>
<div class="image-slot">图片加载中...</div>
</template>
</el-image>
</div>
</template>
@@ -31,7 +35,7 @@ export default {
</script>
<style lang="less" scoped>
.image-message {
/deep/.el-image {
:deep(.el-image) {
border-radius: 5px;
cursor: pointer;
background: #f1efef;

View File

@@ -17,7 +17,6 @@
</template>
<script>
import { Tabs, TabPane } from 'element-ui'
import { ServeGetStoreDetail, ServeGetUserDetail, ServeGetFootPrint, ServeGetOrderPrint, ServeGetGoodsDetail, ServeStoreGetFootPrint,ServeStoreGetOrderPrint } from '@/api/user'
import StoreDetail from "@/components/chat/panel/template/storeDetail.vue";
import FootPrint from "@/components/chat/panel/template/footPrint.vue";
@@ -26,8 +25,6 @@ import SocketInstance from "@/im-server/socket-instance";
import { mapState, mapGetters } from "vuex";
export default {
components: {
"el-tabs": Tabs,
"el-tab-pane": TabPane,
StoreDetail,
FootPrint,
GoodsLink
@@ -271,27 +268,27 @@ export default {
</script>
<style scoped lang="less">
// /deep/ .el-tabs__nav.is-top {
// :deep(.el-tabs__nav.is-top ) {
// }
/deep/ .el-tabs__nav {
:deep(.el-tabs__nav ) {
height: 60px;
line-height: 60px;
}
/deep/ .el-tab-pane {
:deep(.el-tab-pane ) {
// margin-left: 12px;
}
/deep/.el-tabs__nav-scroll{
:deep(.el-tabs__nav-scroll) {
min-width: 362px;
}
/deep/ .el-tabs__item{
:deep(.el-tabs__item) {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/deep/ .el-tabs__header{
:deep(.el-tabs__header) {
margin-bottom: 0;
}
</style>

View File

@@ -89,7 +89,7 @@
</el-tooltip>
</div>
<div class="price">
<span>{{ item.text.price | unitPrice('¥') }}</span>
<span>{{ $filters.unitPrice(item.text.price, '¥') }}</span>
</div>
</div>
</div>
@@ -111,14 +111,14 @@
<div>
<span class="orderGoodsName">{{ order.name }}</span>
<div class="goods-item-price">
<span>{{ order.goodsPrice | unitPrice('¥') }}</span>
<span>{{ $filters.unitPrice(order.goodsPrice, '¥') }}</span>
</div>
</div>
</div>
<div class="shared-goods">
<div class="orderGoodsTime">{{ item.text.paymentTime }}</div>
<span class="orderFlowPrice">
订单金额<span>{{ item.text.flowPrice | unitPrice('¥') }}</span>
订单金额<span>{{ $filters.unitPrice(item.text.flowPrice, '¥') }}</span>
</span>
<div class="order-status">
<el-tag
@@ -205,7 +205,6 @@ import PanelToolbar from "./PanelToolbar";
import SocketInstance from "@/im-server/socket-instance";
import { SvgMentionDown } from "@/core/icons";
import { formatTime, parseTime, copyTextToClipboard } from "@/utils/functions";
import { unitPrice } from '@/plugins/filters';
import {
ServeTalkRecords,

View File

@@ -14,7 +14,7 @@
<a class="goods_name">{{ item.goodsName }}</a>
<div style="display: flex;">
<div style="margin-top: 20px;">
<span class='goods-price'>{{ item.price | unitPrice("¥") }}</span>
<span class='goods-price'>{{ $filters.unitPrice(item.price, "¥") }}</span>
<div class="goods_store_button">
</div>
@@ -42,7 +42,7 @@
<div class="order-items" v-for="(order,orderIndex) in item.orderItems" :key="orderIndex">
<img :src="order.image" alt="">
<span class="order-goods-name" @click="linkToOrders(item.sn)"> {{ order.name }}</span>
<span class="price">{{order.goodsPrice | unitPrice("¥")}}</span>
<span class="price">{{ $filters.unitPrice(order.goodsPrice, "¥") }}</span>
</div>
<!-- <img :src="item.groupImages" alt=""> -->
@@ -77,14 +77,9 @@
</template>
<script>
import { Tag, button, Tabs, TabPane, InfiniteScroll } from 'element-ui'
import { mapState, mapGetters } from "vuex";
import { unitPrice } from '@/plugins/filters';
export default {
directives: {
"infinite-scroll": InfiniteScroll,
},
data () {
return {
noMoreList:{
@@ -310,7 +305,7 @@ export default {
font-size:12px;
}
/deep/ .el-tabs__header {
:deep(.el-tabs__header ) {
position: absolute;
width: 362px;
height: 50px;
@@ -318,7 +313,7 @@ export default {
z-index: 2;
background: #ffffff;
}
/deep/ .el-tabs__nav.is-top {
:deep(.el-tabs__nav.is-top ) {
height: 50px;
}
@@ -328,10 +323,10 @@ export default {
align-items: center;
}
/deep/.el-tabs__content {
:deep(.el-tabs__content ) {
margin-top: 50px;
}
/deep/ .el-tabs{
:deep(.el-tabs) {
width: 100%;
}
@@ -390,7 +385,7 @@ export default {
// height: calc(100vh - 110px);
// overflow-y: auto;
// }
// /deep/.el-tab-pane {
// :deep(.el-tab-pane ) {
// height: calc(100vh - 110px);
// overflow-y: auto;
// }

View File

@@ -8,7 +8,7 @@
<div style="margin-left: 13px">
<a @click="linkToGoods(goodsDetail.goodsId, goodsDetail.id)"> {{ goodsDetail.goodsName }} </a>
<div>
<span style="color: red;">{{ goodsDetail.price | unitPrice('¥') }}</span>
<span style="color: red;">{{ $filters.unitPrice(goodsDetail.price, '¥') }}</span>
</div>
<div v-if="hide">
<el-button class="store-button" type="danger" v-if="btnHide == 1 && toUser.storeFlag" size="mini"
@@ -20,7 +20,6 @@
</template>
<script>
import { Tag, button } from 'element-ui'
import { mapState, mapGetters } from "vuex";
import SocketInstance from "@/im-server/socket-instance";
export default {
@@ -42,11 +41,6 @@ export default {
console.log(this.goodsDetail)
this.btnHide = localStorage.getItem('btnHide')
},
components: {
"el-tag": Tag,
"el-button": button,
Storage
},
methods: {
toGoods () {
alert("toGoods")

View File

@@ -35,7 +35,6 @@
</template>
<script>
import { Tag, button, rate } from 'element-ui'
export default {
data () {
return {
@@ -43,11 +42,6 @@ export default {
colors: ['#99A9BF', '#F7BA2A', '#FF9900']
}
},
components: {
"el-tag": Tag,
"el-button": button,
"el-rate": rate
},
methods: {
},
props: {

View File

@@ -3,10 +3,16 @@
<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>
<el-popover ref="popoverEmoticon" placement="top-start" trigger="click" width="300"
popper-class="no-padding el-popover-em">
<template #reference>
<li>
<i class="iconfont icon-icon_im_face" style="font-size: 15px" />
<p class="tip-title">表情符号</p>
</li>
</template>
<MeEditorEmoticon ref="editorEmoticon" @selected="selecteEmoticon" />
</el-popover>
<!-- <li @click="codeBlock.isShow = true">
<i class="iconfont icon-daima" />
<p class="tip-title">代码片段</p>
@@ -59,11 +65,6 @@
<!-- </p>-->
</ul>
<el-popover ref="popoverEmoticon" placement="top-start" trigger="click" width="300"
popper-class="no-padding el-popover-em">
<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" />
@@ -76,7 +77,7 @@
</el-container>
<!-- 图片查看器 -->
<MeEditorImageView ref="imageViewer" v-model="imageViewer.isShow" :file="imageViewer.file"
<MeEditorImageView ref="imageViewer" v-model:show="imageViewer.isShow" :file="imageViewer.file"
@confirm="confirmUploadImage" />
<MeEditorRecorder v-if="recorder" @close="recorder = false" />
@@ -86,7 +87,7 @@
@confirm="confirmCodeBlock" />
<!-- 文件上传管理器 -->
<MeEditorFileManage ref="filesManager" v-model="filesManager.isShow" />
<MeEditorFileManage ref="filesManager" v-model:show="filesManager.isShow" />
<MeEditorVote v-if="vote.isShow" @close="
() => {
@@ -335,7 +336,7 @@ export default {
});
}
this.$refs.popoverEmoticon.doClose();
this.$refs.popoverEmoticon.hide();
},
},
};

View File

@@ -332,7 +332,7 @@ export default {
}
}
/deep/ .el-image {
:deep(.el-image ) {
width: 100%;
height: 100%;
transition: ease-in 0.3s;

View File

@@ -78,21 +78,14 @@
</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 { formatSize, getFileExt, parseTime } from '@/utils/functions'
import { ServeSendTalkFile } from '@/api/chat'
export default {
name: 'MeEditorFileManage',
model: {
prop: 'show',
event: 'close',
},
emits: ['close', 'update:show'],
props: {
show: Boolean,
},
@@ -141,6 +134,7 @@ export default {
},
methods: {
closeBox() {
this.$emit('update:show', false)
this.$emit('close', false)
},

View File

@@ -33,10 +33,7 @@
<script>
export default {
name: 'MeEditorImageView',
model: {
prop: 'show',
event: 'close',
},
emits: ['close', 'update:show', 'confirm'],
props: {
show: Boolean,
file: File,
@@ -60,6 +57,7 @@ export default {
return false
}
this.$emit('update:show', false)
this.$emit('close', false)
},
loadFile(file) {

View File

@@ -188,7 +188,7 @@ export default {
return `${hour}:${minute}:${seconds}`
},
},
destroyed() {
beforeUnmount() {
if (this.recorder) {
this.destroyRecorder()
}

View File

@@ -37,11 +37,12 @@
placeholder="请输入选项内容"
:maxlength="120"
>
<span
slot="prefix"
style="margin-left:7px;"
v-text="String.fromCharCode(65 + index)"
/>
<template #prefix>
<span
style="margin-left:7px;"
v-text="String.fromCharCode(65 + index)"
/>
</template>
</el-input>
</div>
<div class="rbox">
@@ -154,7 +155,7 @@ export default {
return false
}
this.$delete(this.options, index)
this.options.splice(index, 1)
},
},
}
@@ -184,7 +185,7 @@ export default {
.lbox {
width: 100%;
/deep/.el-input__prefix {
:deep(.el-input__prefix ) {
height: 36px;
line-height: 36px;
}
@@ -220,7 +221,7 @@ export default {
}
}
/deep/.el-radio__input.is-checked + .el-radio__label {
:deep(.el-radio__input.is-checked + .el-radio__label ) {
color: #606266;
}
</style>

View File

@@ -9,7 +9,7 @@
<p class="title">
{{ talk_type == 1 ? '私信消息通知' : '群聊消息通知' }}
</p>
<p class="time"><i class="el-icon-time" /> {{ datetime | format }}</p>
<p class="time"><i class="el-icon-time" /> {{ formatTime(datetime) }}</p>
</div>
<div class="xbody">
<p>@{{ nickname }}</p>
@@ -50,8 +50,8 @@ export default {
data() {
return {}
},
filters: {
format(datetime) {
methods: {
formatTime(datetime) {
datetime = datetime || new Date()
return parseTime(datetime, '{m}/{d} {h}:{i} 分')
},

View File

@@ -1,5 +1,5 @@
<template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
<svg :class="svgClass" aria-hidden="true" v-bind="$attrs">
<use :xlink:href="iconName" />
</svg>
</template>

View File

@@ -16,7 +16,7 @@
prefix-icon="el-icon-search"
placeholder="请输入对方手机号(精确查找)"
clearable
@keyup.enter.native="onSubmit"
@keyup.enter="onSubmit"
@input="error = false"
/>
<p v-show="error" class="error">

View File

@@ -31,7 +31,7 @@
<div class="card-rows no-select">
<div class="card-row">
<label>手机</label>
<span>{{ detail.mobile | mobile }}</span>
<span>{{ formatMobile(detail.mobile) }}</span>
</div>
<div class="card-row">
<label>昵称</label>
@@ -39,7 +39,7 @@
</div>
<div class="card-row">
<label>性别</label>
<span>{{ detail.gender | gender }}</span>
<span>{{ formatGender(detail.gender) }}</span>
</div>
<div v-show="detail.friend_status == 2" class="card-row">
<label>备注</label>
@@ -134,18 +134,6 @@ export default {
default: 0,
},
},
filters: {
gender(value) {
let arr = ["未知", "男", "女"];
return arr[value] || "未知";
},
// 手机号格式化
mobile(value) {
return (
value.substr(0, 3) + " " + value.substr(3, 4) + " " + value.substr(7, 4)
);
},
},
data() {
return {
detail: {
@@ -179,6 +167,16 @@ export default {
// this.loadUserDetail();
},
methods: {
formatGender(value) {
const arr = ["未知", "男", "女"];
return arr[value] || "未知";
},
formatMobile(value) {
if (!value) return "";
return (
value.substr(0, 3) + " " + value.substr(3, 4) + " " + value.substr(7, 4)
);
},
close() {
if (this.contacts) return false;

View File

@@ -1,33 +1,40 @@
import UserCardDetail from './UserCardDetail'
import { createApp, h } from "vue";
import UserCardDetail from "./UserCardDetail";
import router from "@/router";
import store from "@/store";
import { setupElementPlus } from "@/plugins/element";
import { setupLegacyMessage } from "@/utils/message";
import { registerDirectives } from "@/core/directives";
export default {
install(Vue) {
install(app) {
function user(user_id, options) {
let _vm = this
const el = new Vue({
router: _vm.$router,
store: _vm.$store,
render(h) {
return h(UserCardDetail, {
on: {
close: () => {
el.$destroy()
document.body.removeChild(el.$el)
},
changeRemark: data => {
options.editRemarkCallbak && options.editRemarkCallbak(data)
},
},
props: {
user_id,
},
})
},
}).$mount()
const container = document.createElement("div");
document.body.appendChild(container);
document.body.appendChild(el.$el)
const cardApp = createApp({
render() {
return h(UserCardDetail, {
user_id,
onClose: () => {
cardApp.unmount();
document.body.removeChild(container);
},
onChangeRemark: (data) => {
options.editRemarkCallbak && options.editRemarkCallbak(data);
},
});
},
});
setupElementPlus(cardApp);
setupLegacyMessage(cardApp);
registerDirectives(cardApp);
cardApp.use(router);
cardApp.use(store);
cardApp.mount(container);
}
Vue.prototype.$user = user
app.config.globalProperties.$user = user;
},
}
};

View File

@@ -1,49 +1,57 @@
import Vue from 'vue'
import Clickoutside from 'element-ui/src/utils/clickoutside'
function onClickOutside(el, binding) {
const handler = (e) => {
if (!el.contains(e.target)) {
binding.value(e);
}
};
el.__clickOutsideHandler__ = handler;
document.addEventListener("click", handler);
}
// 自定义聚焦指令
Vue.directive('focus', {
inserted(el) {
el.focus()
},
})
function offClickOutside(el) {
if (el.__clickOutsideHandler__) {
document.removeEventListener("click", el.__clickOutsideHandler__);
delete el.__clickOutsideHandler__;
}
}
// 自定义粘贴指令
Vue.directive('paste', {
bind(el, binding, vnode) {
el.addEventListener('paste', function(event) {
//这里直接监听元素的粘贴事件
binding.value(event)
})
},
})
export function registerDirectives(app) {
app.directive("focus", {
mounted(el) {
el.focus();
},
});
// 自定义拖拽指令
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)
})
},
})
app.directive("paste", {
mounted(el, binding) {
el.addEventListener("paste", (event) => {
binding.value(event);
});
},
});
// 点击其他地方隐藏指令
Vue.directive('outside', Clickoutside)
app.directive("drag", {
mounted(el, binding) {
const stop = (event) => {
event.stopPropagation();
event.preventDefault();
};
el.addEventListener("dragenter", stop);
el.addEventListener("dragover", stop);
el.addEventListener("dragleave", stop);
el.addEventListener("drop", (event) => {
stop(event);
binding.value(event);
});
},
});
app.directive("outside", {
mounted(el, binding) {
onClickOutside(el, binding);
},
unmounted(el) {
offClickOutside(el);
},
});
}

View File

@@ -1,4 +1,3 @@
import Vue from "vue";
import {
AudioMessage,
CodeMessage,
@@ -16,22 +15,29 @@ import {
VoteMessage,
LoginMessage,
} from "@/components/chat/messaege";
Vue.component(AudioMessage.name, AudioMessage);
Vue.component(CodeMessage.name, CodeMessage);
Vue.component(ForwardMessage.name, ForwardMessage);
Vue.component(ImageMessage.name, ImageMessage);
Vue.component(TextMessage.name, TextMessage);
Vue.component(VideoMessage.name, VideoMessage);
Vue.component(VoiceMessage.name, VoiceMessage);
Vue.component(SystemTextMessage.name, SystemTextMessage);
Vue.component(FileMessage.name, FileMessage);
Vue.component(InviteMessage.name, InviteMessage);
Vue.component(RevokeMessage.name, RevokeMessage);
Vue.component(VisitCardMessage.name, VisitCardMessage);
Vue.component(ReplyMessage.name, ReplyMessage);
Vue.component(VoteMessage.name, VoteMessage);
Vue.component(LoginMessage.name, LoginMessage);
import UserCard from "@/components/user/user-card/index";
Vue.use(UserCard);
const messageComponents = [
AudioMessage,
CodeMessage,
ForwardMessage,
ImageMessage,
TextMessage,
VideoMessage,
VoiceMessage,
SystemTextMessage,
FileMessage,
InviteMessage,
RevokeMessage,
VisitCardMessage,
ReplyMessage,
VoteMessage,
LoginMessage,
];
export function registerGlobalComponents(app) {
messageComponents.forEach((component) => {
app.component(component.name, component);
});
app.use(UserCard);
}

View File

@@ -1,22 +1,21 @@
/**
* 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.
import { h } from "vue";
export {
SvgMentionDown,
SvgNotFount,
SvgNote,
SvgNoteBook,
SvgNotData,
SvgZhuangFa,
function createSvgIcon(name) {
return {
name: `SvgIcon_${name}`,
inheritAttrs: false,
render() {
return h("svg-icon", {
iconClass: name,
...this.$attrs,
});
},
};
}
export const SvgMentionDown = createSvgIcon("mention-down");
export const SvgNotFount = createSvgIcon("not-fount");
export const SvgNote = createSvgIcon("note");
export const SvgNoteBook = createSvgIcon("note-book");
export const SvgNotData = createSvgIcon("not-data");
export const SvgZhuangFa = createSvgIcon("zhuangfa");

View File

@@ -1,88 +0,0 @@
import Vue from 'vue'
import 'element-ui/lib/theme-chalk/index.css'
import {
Notification,
Popover,
Switch,
Dropdown,
DropdownMenu,
DropdownItem,
Message,
Container,
Header,
Aside,
Main,
Footer,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Button,
Image,
Loading,
Row,
Col,
MessageBox,
Form,
FormItem,
Input,
Divider,
Link,
Tooltip,
Autocomplete,
Scrollbar,
Avatar,
Radio,
RadioGroup,
Progress,
Dialog,
Checkbox,
Tag
} from 'element-ui'
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(Checkbox)
Vue.use(RadioGroup)
Vue.use(Progress)
Vue.use(Dialog)
Vue.use(Tag)
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)

View File

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

View File

@@ -1,13 +1,13 @@
import store from "@/store";
import router from "@/router";
import { Notification } from "element-ui";
import { ElNotification } from "element-plus";
class Base {
/**
* 初始化
*/
constructor() {
this.$notify = Notification;
this.$notify = ElNotification;
}
getStoreInstance() {
@@ -54,7 +54,7 @@ class Base {
* 判断用户是否打开对话页
*/
isTalkPage() {
let path = router.currentRoute.path;
let path = router.currentRoute.value.path;
return !(path != "/message" && path != "/");
}
}

View File

@@ -1,7 +1,6 @@
import Base from "./base";
import Vue from "vue";
import router from "@/router";
import vm from "@/main";
import { nextTick } from "vue";
import { getAppBridge } from "@/utils/app-bridge";
import NewMessageNotify from "@/components/notify/NewMessageNotify";
import { ServeClearTalkUnreadNum, ServeCreateTalkList } from "@/api/chat";
import { formatTalkItem, findTalkIndex, toTalk } from "@/utils/talk";
@@ -46,11 +45,11 @@ class Talk extends Base {
this.resource = resource;
// 判断发送者消息是否在当前用户列表中
if (this.sender_id && !vm.$store.state.talks.items.find(item => {
const bridge = getAppBridge();
if (this.sender_id && !bridge.$store.state.talks.items.find(item => {
return item.userId == this.sender_id
})) {
// 没有当前用户,未在当前列表 进行重新加载
vm.loadUserSetting('update')
bridge.loadUserSetting('update')
}
}
@@ -147,15 +146,14 @@ class Talk extends Base {
receiver_id = this.sender_id;
}
const bridge = getAppBridge();
this.$notify({
message: vm.$createElement(NewMessageNotify, {
props: {
avatar,
talk_type,
nickname,
content: this.getTalkText(),
datetime: this.resource.created_at,
},
message: bridge.$createElement(NewMessageNotify, {
avatar,
talk_type,
nickname,
content: this.getTalkText(),
datetime: this.resource.created_at,
}),
customClass: "im-notify",
duration: 3000,
@@ -211,7 +209,7 @@ class Talk extends Base {
record.userId == this.getAccountId() ||
record.fromUser == this.getAccountId()
) {
Vue.nextTick(() => {
nextTick(() => {
el.scrollTop = el.scrollHeight;
});
} else {

View File

@@ -2,7 +2,7 @@ import store from "@/store";
import config from "@/config/config";
import WsSocket from "@/plugins/ws-socket";
import { getToken } from "@/utils/auth";
import { Notification } from "element-ui";
import { ElNotification } from "element-plus";
// 引入消息处理类
import KeyboardEvent from "@/im-server/event/keyboard";
@@ -86,7 +86,7 @@ class SocketInstance {
});
this.socket.on("event_error", (data) => {
Notification({
ElNotification({
title: "友情提示",
message: data.message,
type: "warning",

View File

@@ -1,82 +1,65 @@
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import "core-js/stable";
// 引入 Vue 和应用程序组件
import Vue from 'vue'
import App from '@/App'
// 引入 store 和 router
import store from '@/store'
import router from '@/router'
// 引入自定义的 mixin
import MainMixin from './mixins/main-mixin'
// 引入全局组件
import face from '@/components/face'
import faceNull from '@/components/face-null'
// 引入配置文件和其他核心模块
import { createApp } from "vue";
import App from "@/App.vue";
import store from "@/store";
import router from "@/router";
import MainMixin from "./mixins/main-mixin";
import face from "@/components/face";
import faceNull from "@/components/face-null";
import config from "@/config/config";
import './core/lazy-use'
import './core/global-component'
import './core/filter'
import './core/directives'
// 引入权限控制和图标库
import '@/permission'
import '@/icons'
// 引入自定义过滤器,并注册为全局过滤器
import { setupElementPlus } from "@/plugins/element";
import { setupLegacyMessage } from "@/utils/message";
import { registerDirectives } from "./core/directives";
import { registerGlobalComponents } from "./core/global-component";
import { registerIcons } from "@/icons";
import * as filters from "./plugins/filters";
Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key]);
import VueVirtualScroller from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
import { setupContextmenu } from "@/plugins/contextmenu";
import "@/permission";
import "@/assets/css/global.less";
const app = createApp(App);
setupElementPlus(app);
setupLegacyMessage(app);
registerDirectives(app);
registerGlobalComponents(app);
registerIcons(app);
app.use(store);
app.use(router);
setupContextmenu(app);
app.use(VueVirtualScroller);
app.mixin(MainMixin);
app.component("face", face);
app.component("face-null", faceNull);
app.component("RecycleScroller", VueVirtualScroller.RecycleScroller);
app.config.globalProperties.$filters = filters;
app.config.globalProperties.linkToGoods = function (goodsId, skuId) {
window.open(
`${config.PC_URL}goodsDetail?skuId=${skuId}&goodsId=${goodsId}`,
"_blank"
);
};
app.config.globalProperties.linkToStore = function (storeId) {
window.open(`${config.PC_URL}/Merchant?id=${storeId}`, "_blank");
};
app.config.globalProperties.linkToOrders = function (sn) {
if (localStorage.getItem("storeFlag") == "false") {
window.open(`${config.PC_STORE}order-detail?sn=${sn}`, "_blank");
} else {
window.open(`${config.PC_URL}home/OrderDetail?sn=${sn}`, "_blank");
}
};
router.isReady().then(() => {
app.mount("#app");
});
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.component('RecycleScroller', VueVirtualScroller.RecycleScroller)
// 引入自定义全局css
import '@/assets/css/global.less'
// 关闭生产提示
Vue.config.productionTip = false
// 注册全局 mixin
Vue.mixin(MainMixin)
Vue.component('face', face)
Vue.component('face-null', faceNull)
// 添加自定义原型方法
Vue.prototype.linkToGoods = function (goodsId, skuId) { // 跳转买家端商品
if (localStorage.getItem('storeFlag') == 'false') {
window.open(`${config.PC_URL}goodsDetail?skuId=${skuId}&goodsId=${goodsId}`, '_blank')
} else {
window.open(`${config.PC_URL}goodsDetail?skuId=${skuId}&goodsId=${goodsId}`, '_blank')
}
};
Vue.prototype.linkToStore = function (storeId) { // 跳转商家端商品
console.log(`${config.PC_URL}/Merchant?id=${storeId}`)
window.open(`${config.PC_URL}/Merchant?id=${storeId}`, '_blank')
};
// 订单跳转商家订单页面
Vue.prototype.linkToOrders = function (sn) {
if (localStorage.getItem('storeFlag') == 'false') {
// 商家
window.open(`${config.PC_STORE}order-detail?sn=${sn}`, '_blank')
} else {
// 用户
window.open(`${config.PC_URL}home/OrderDetail?sn=${sn}`, '_blank')
}
};
const Instance = new Vue({
router,
store,
mixins: [MainMixin],
render: h => h(App),
}).$mount('#app')
export default Instance
export default app;

View File

@@ -119,7 +119,7 @@ export default {
reload() {
this.$root.$children[0].refreshView();
import("@/utils/app-bridge").then(({ refreshView }) => refreshView());
},
},
};

View File

@@ -0,0 +1,110 @@
import { createApp, h, onMounted, onUnmounted } from "vue";
let activeMenu = null;
function destroyMenu() {
if (!activeMenu) return;
activeMenu.app.unmount();
if (activeMenu.el?.parentNode) {
activeMenu.el.parentNode.removeChild(activeMenu.el);
}
activeMenu = null;
}
function normalizeOptions(options) {
if (Array.isArray(options)) {
return { items: options };
}
return options || { items: [] };
}
function showContextmenu(options) {
destroyMenu();
const {
items = [],
event,
x = 0,
y = 0,
customClass = "",
zIndex = 3000,
minWidth = 120,
} = normalizeOptions(options);
const left = event?.clientX ?? x;
const top = event?.clientY ?? y;
const el = document.createElement("div");
document.body.appendChild(el);
const app = createApp({
setup() {
const close = () => destroyMenu();
onMounted(() => {
document.addEventListener("click", close);
document.addEventListener("contextmenu", close);
document.addEventListener("wheel", close, { passive: true });
});
onUnmounted(() => {
document.removeEventListener("click", close);
document.removeEventListener("contextmenu", close);
document.removeEventListener("wheel", close);
});
return () =>
h(
"div",
{
class: ["im-contextmenu", customClass].filter(Boolean),
style: {
position: "fixed",
left: `${left}px`,
top: `${top}px`,
zIndex,
minWidth: `${minWidth}px`,
},
onContextmenu: (e) => e.preventDefault(),
},
items.flatMap((item, index) => {
const nodes = [];
if (item.divided && index > 0) {
nodes.push(h("div", { class: "im-contextmenu-divider" }));
}
nodes.push(renderMenuItem(item, close));
return nodes;
})
);
},
});
activeMenu = { app, el };
app.mount(el);
}
function renderMenuItem(item, close) {
return h(
"div",
{
class: [
"im-contextmenu-item",
item.customClass,
item.disabled ? "is-disabled" : "",
].filter(Boolean),
onClick: (e) => {
e.stopPropagation();
if (item.disabled) return;
item.onClick?.();
close();
},
},
[
item.icon ? h("i", { class: item.icon }) : null,
h("span", item.label),
]
);
}
export function setupContextmenu(app) {
app.config.globalProperties.$contextmenu = showContextmenu;
}

10
im/src/plugins/element.js Normal file
View File

@@ -0,0 +1,10 @@
import ElementPlus from "element-plus";
import zhCn from "element-plus/es/locale/lang/zh-cn";
import "element-plus/dist/index.css";
export function setupElementPlus(app) {
app.use(ElementPlus, {
locale: zhCn,
size: "default",
});
}

View File

@@ -1,4 +1,4 @@
import { Notification, MessageBox } from "element-ui";
import { ElNotification, ElMessageBox } from "element-plus";
let wsSignIn; // ws 是否是掉线状态
import store from "@/store";
class WsSocket {
@@ -84,7 +84,7 @@ class WsSocket {
store.commit('SET_WS_STATUS',true);
wsSignIn.close();
Notification({
ElNotification({
title: "成功",
message: "重连成功",
type: "success",
@@ -125,7 +125,7 @@ class WsSocket {
* 长时间挂载页面中并且重连次数为空的时候进行提示
*/
if (this.config.reconnect.number == 0) {
MessageBox("当前对话链接已失效,请从关闭重新进入。", "提示", {
ElMessageBox("当前对话链接已失效,请从关闭重新进入。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnPressEscape: false,
@@ -134,7 +134,7 @@ class WsSocket {
})
.then(() => {
window.close();
Notification({
ElNotification({
title: "对话链接已失效提示",
message: "请手动关闭当前页面",
type: "error",
@@ -142,7 +142,7 @@ class WsSocket {
});
})
.catch(() => {
Notification({
ElNotification({
title: "对话链接已失效提示",
message: "请手动关闭当前页面",
type: "error",
@@ -152,7 +152,7 @@ class WsSocket {
return false;
}
// 掉线重连提示
wsSignIn = Notification({
wsSignIn = ElNotification({
title: "掉线重连接提示",
message: `网络连接已断开,正在尝试重新连接......`,
type: "error",

View File

@@ -1,44 +1,38 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const RouteView = {
name: 'RouteView',
render: h => h('router-view'),
}
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: '/',
name: 'home',
component: () => import('@/views/message/index'),
meta: {
title: '',
needLogin: true,
},
{
path: "/",
name: "home",
component: () => import("@/views/message/index"),
meta: {
title: "",
needLogin: true,
},
{
path: '/message',
name: 'message',
component: () => import('@/views/message/index'),
meta: {
title: '消息通知',
needLogin: true,
},
},
{
path: "/message",
name: "message",
component: () => import("@/views/message/index"),
meta: {
title: "消息通知",
needLogin: true,
},
{
path: '*',
name: '404 NotFound',
component: () => import('@/views/other/404'),
meta: {
title: '404 NotFound',
needLogin: false,
},
},
{
path: "/:pathMatch(.*)*",
name: "404 NotFound",
component: () => import("@/views/other/404"),
meta: {
title: "404 NotFound",
needLogin: false,
},
]
},
];
export default new Router({
routes,
mode: 'history',
})
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;

View File

@@ -1,21 +1,18 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { createStore } from "vuex";
import user from './modules/user'
import talks from './modules/talk'
import notify from './modules/notify'
import settings from './modules/settings'
import emoticon from './modules/emoticon'
import dialogue from './modules/dialogue'
import note from './modules/note'
import user from "./modules/user";
import talks from "./modules/talk";
import notify from "./modules/notify";
import settings from "./modules/settings";
import emoticon from "./modules/emoticon";
import dialogue from "./modules/dialogue";
import note from "./modules/note";
import state from './state'
import getters from './getters'
import mutations from './mutations'
import state from "./state";
import getters from "./getters";
import mutations from "./mutations";
Vue.use(Vuex)
const store = new Vuex.Store({
const store = createStore({
modules: {
user,
notify,
@@ -28,6 +25,6 @@ const store = new Vuex.Store({
state,
getters,
mutations,
})
});
export default store
export default store;

View File

@@ -2,7 +2,7 @@ import { ServeFindUserEmoticon, ServeUploadEmoticon } from "@/api/emoticon";
import { ServeCollectEmoticon } from "@/api/chat";
import { Notification } from "element-ui";
import { ElNotification } from "element-plus";
export default {
state: {
@@ -36,7 +36,7 @@ export default {
})
.then((res) => {
if (res.code == 200) {
Notification({
ElNotification({
title: "收藏提示",
message: "表情包收藏成功...",
type: "success",
@@ -46,7 +46,7 @@ export default {
}
})
.catch(() => {
Notification({
ElNotification({
title: "收藏提示",
message: "表情包收藏失败...",
type: "warning",
@@ -65,7 +65,7 @@ export default {
}
})
.catch(() => {
Notification({
ElNotification({
message: "网络异常请稍后再试...",
type: "error",
duration: 3000,

View File

@@ -1,8 +1,6 @@
import { getSort, getMutipSort } from "@/utils/functions";
import { formatTalkItem } from "@/utils/talk";
import { ServeGetTalkList } from "@/api/chat";
import store from '@/store/index.js'
import Vue from 'vue'
const Talk = {
state: {
// 用户对话列表
@@ -40,7 +38,7 @@ const Talk = {
// 设置对话列表
SET_TALK_ITEMS (state, resource) {
Vue.set(state, 'items', resource.items)
state.items = resource.items;
},
// 更新对话节点

View File

@@ -0,0 +1,37 @@
import { h } from "vue";
import store from "@/store";
import router from "@/router";
import MainMixin from "@/mixins/main-mixin";
let refreshViewFn = null;
const mixinCtx = {
get $store() {
return store;
},
get $router() {
return router;
},
get $route() {
return router.currentRoute.value;
},
};
export function setRefreshView(fn) {
refreshViewFn = fn;
}
export function refreshView() {
refreshViewFn?.();
}
export function getAppBridge() {
return {
$store: store,
$router: router,
$route: router.currentRoute.value,
$createElement: h,
loadUserSetting: MainMixin.methods.loadUserSetting.bind(mixinCtx),
refreshView,
};
}

11
im/src/utils/message.js Normal file
View File

@@ -0,0 +1,11 @@
import { ElMessage, ElMessageBox, ElNotification } from "element-plus";
export { ElNotification, ElMessage, ElMessageBox };
export function setupLegacyMessage(app) {
app.config.globalProperties.$message = ElMessage;
app.config.globalProperties.$notify = ElNotification;
app.config.globalProperties.$confirm = ElMessageBox.confirm;
app.config.globalProperties.$prompt = ElMessageBox.prompt;
app.config.globalProperties.$alert = ElMessageBox.alert;
}

View File

@@ -2,7 +2,7 @@ import axios from "axios";
import config from "@/config/config";
import { getToken, removeAll } from "@/utils/auth";
import { Notification, MessageBox } from "element-ui";
import { ElNotification, ElMessageBox } from "element-plus";
import qs from "qs";
// 创建 axios 实例
@@ -33,7 +33,7 @@ const errorHandler = (error) => {
/**
* 403提示将重新从商家移动端进入当前页面
*/
MessageBox("当前登录已失效,请从关闭重新进入。", "提示", {
ElMessageBox("当前登录已失效,请从关闭重新进入。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnPressEscape: false,
@@ -43,7 +43,7 @@ const errorHandler = (error) => {
.then(() => {
isRefreshing = true
window.close();
Notification({
ElNotification({
title:"登录失效提示",
message: "请手动关闭当前页面",
type:"error",
@@ -53,7 +53,7 @@ const errorHandler = (error) => {
})
.catch(() => {
isRefreshing = true
Notification({
ElNotification({
title:"登录失效提示",
message: "请手动关闭当前页面",
type:"error",
@@ -63,7 +63,7 @@ const errorHandler = (error) => {
isRefreshing = false
}
} else if(error.response.status == 400){
Notification({
ElNotification({
message: error.response.data.message,
position: "top-right",
type:"error",

View File

@@ -1,13 +1,14 @@
<template>
<div class="wrapper">
<MainLayout :idx="0" ref="mainLayout">
<el-container slot="container" class="full-height">
<template #container>
<el-container class="full-height">
<!-- 左侧侧边栏 -->
<el-aside width="320px" class="aside-box">
<el-container class="full-height" direction="vertical">
<!-- 搜索栏 -->
<el-header height="60px" class="header">
<div class="user-login" v-popover:usercard>
<div class="user-login">
<div class="user-box">
<face :text="face" :name="name" class="user-face"></face>
</div>
@@ -142,6 +143,7 @@
<!-- <OtherLink :toUser="toUser" :goodsParams="goodsParams" :id="id" class="flex-4"/> -->
</el-main>
</el-container>
</template>
</MainLayout>
<!-- 用户查询组件 -->
@@ -149,8 +151,6 @@
</div>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { mapGetters, mapState } from "vuex";
import MainLayout from "@/views/layout/MainLayout";
import WelcomeModule from "@/components/layout/WelcomeModule";
@@ -179,7 +179,6 @@ export default {
UserSearch,
OtherLink,
WelcomeModule,
RecycleScroller
},
data () {
return {
@@ -247,7 +246,7 @@ export default {
},
watch: {
talkItems (val) {
val ? this.$set(this, "userTalkItem", val) : "";
if (val) this.userTalkItem = val;
},
// 搜索用户的时候 根据当前用户表进行模糊搜索
input (val, oldVal) {
@@ -312,7 +311,7 @@ export default {
mounted () {
this.scrollEvent();
},
destroyed () {
beforeUnmount () {
document.title = title;
clearInterval(this.interval);
this.clearTalk();
@@ -665,7 +664,7 @@ export default {
};
</script>
<style lang="less" scoped>
/deep/ .el-scrollbar__wrap {
:deep(.el-scrollbar__wrap ) {
overflow-x: hidden;
}
.user-status{
@@ -689,7 +688,7 @@ export default {
flex-shrink: 0;
height: 40px;
/deep/ .el-input .el-input__inner {
:deep(.el-input .el-input__inner ) {
border-radius: 20px;
}
}

View File

@@ -39,7 +39,7 @@ export default {
}
}, 1000)
},
destroyed() {
beforeUnmount() {
clearInterval(this.setInterval)
},
methods: {

View File

@@ -9,17 +9,6 @@ function resolve(dir) {
return path.join(__dirname, dir)
}
const assetsCDN = {
externals: {
vue: 'Vue',
},
css: [],
js: [
'https://cdn.pickmall.cn/cdn/vue.min.js',
],
}
// vue.config.js
const vueConfig = {
// 公共路径(必须有的)
@@ -57,37 +46,24 @@ const vueConfig = {
}),
],
// if prod, add externals
externals: isProd ? assetsCDN.externals : {},
},
chainWebpack: config => {
config.resolve.alias.set('@', resolve('src'))
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule
.oneOf('inline')
.resourceQuery(/inline/)
.use('vue-svg-icon-loader')
.loader('vue-svg-icon-loader')
config.module.rule('svg').exclude.add(resolve('src/icons/svg')).end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons/svg'))
.end()
.end()
.oneOf('external')
.use('file-loader')
.loader('file-loader')
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
name: 'assets/[name].[hash:8].[ext]',
symbolId: 'icon-[name]',
})
// if prod is on
// assets require on cdn
if (isProd) {
config.plugin('html').tap(args => {
args[0].cdn = assetsCDN
return args
})
}
},
pluginOptions: {
'style-resources-loader': {