提交 58fed0c1 作者: Hao

Initial commit

父级
> 1%
last 2 versions
not dead
not ie 11
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# tokcos-socket-web-ts
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-auto-import
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
}
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
AutomaticPrompt: typeof import('./src/components/AutomaticPrompt.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElImage: typeof import('element-plus/es')['ElImage']
ElMain: typeof import('element-plus/es')['ElMain']
EmotionComponent: typeof import('./src/components/EmotionComponent.vue')['default']
IndexComponent: typeof import('./src/components/IndexComponent.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "tokcos-socket-web-ts",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@luohc92/vue3-image-viewer": "^1.0.0",
"axios": "^1.6.7",
"core-js": "^3.8.3",
"cos-js-sdk-v5": "^1.7.0",
"cos-wx-sdk-v5": "^1.6.0",
"element-plus": "^2.6.1",
"emoji.json": "^15.1.0",
"mockjs": "^1.1.0",
"moment": "^2.30.1",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"socket.io-client": "^4.7.4",
"unplugin-auto-import": "0.16.1",
"unplugin-vue-components": "^0.25.2",
"vue": "^3.2.13",
"vue-native-websocket": "^2.0.15",
"vue-router": "^4.0.3",
"vue3-uuid": "^1.0.0",
"vuex": "^4.0.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"typescript": "~4.5.5"
}
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>用户端</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<router-view />
</template>
<style lang="scss">
* {
padding: 0;
margin: 0;
}
html,
body,#app {
width: 100%;
height: 100%;
}
</style>
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
::-webkit-scrollbar {
display: none;
}
\ No newline at end of file
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
@font-face {
font-family: "iconfont"; /* Project id 2716892 */
src: url('iconfont.woff2?t=1628492942036') format('woff2'),
url('iconfont.woff?t=1628492942036') format('woff'),
url('iconfont.ttf?t=1628492942036') format('truetype');
}
.iconfonts {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-jieshu:before {
content: "\e637";
}
.icon-diannao:before {
content: "\e6ac";
}
.icon-xiaolian:before {
content: "\e631";
}
.icon-zan1:before {
content: "\e600";
}
!function(e){var t,c,n,o,i,l='<svg><symbol id="icon-jieshu" viewBox="0 0 1024 1024"><path d="M522.1 954.6c-52.9 0-104.2-10.4-152.6-30.8-46.7-19.7-88.6-48-124.6-84s-64.3-77.9-84-124.6c-20.4-48.3-30.8-99.7-30.8-152.6 0-68.6 18-136.1 52-195.2 33-57.3 80.3-105.8 136.8-140.1 10.9-6.6 25-3.1 31.6 7.7 6.6 10.9 3.1 25-7.7 31.6-104.4 63.4-166.7 174-166.7 295.9 0 190.8 155.2 346 346 346s346-155.2 346-346c0-127-69.4-243.6-181.1-304.2-11.2-6.1-15.3-20-9.2-31.2 6.1-11.2 20-15.3 31.2-9.2 126.4 68.8 205 200.8 205 344.7 0 52.9-10.4 104.2-30.8 152.6-19.7 46.7-48 88.6-84 124.6s-77.9 64.3-124.6 84c-48.3 20.4-99.6 30.8-152.5 30.8z" ></path><path d="M528.6 607.9c-12.7 0-23-10.3-23-23V114c0-12.7 10.3-23 23-23s23 10.3 23 23v470.9c0 12.7-10.3 23-23 23z" ></path></symbol><symbol id="icon-diannao" viewBox="0 0 1024 1024"><path d="M943.104 80.896H80.896C36.352 80.896 0 117.248 0 161.792v539.136c0 44.544 36.352 80.896 80.896 80.896h403.968v108.032H296.448c-14.848 0-27.136 12.288-27.136 27.136s12.288 27.136 27.136 27.136h431.104c14.848 0 27.136-12.288 27.136-27.136s-12.288-27.136-27.136-27.136h-188.416v-108.032h403.968c44.544 0 80.896-36.352 80.896-80.896V161.792c0-44.544-36.352-80.896-80.896-80.896z m27.136 619.52c0 14.848-12.288 27.136-27.136 27.136H80.896c-14.848 0-27.136-12.288-27.136-27.136V161.792c0-14.848 12.288-27.136 27.136-27.136h862.208c14.848 0 27.136 12.288 27.136 27.136v538.624z" ></path></symbol><symbol id="icon-xiaolian" viewBox="0 0 1024 1024"><path d="M512 938.666667C276.309333 938.666667 85.333333 747.690667 85.333333 512S276.309333 85.333333 512 85.333333s426.666667 190.976 426.666667 426.666667-190.976 426.666667-426.666667 426.666667z m0-800.085334C306.176 138.581333 138.581333 306.176 138.581333 512c0 205.824 167.424 373.248 373.418667 373.248 205.824 0 373.248-167.424 373.248-373.248S717.824 138.581333 512 138.581333z m-1.365333 641.365334h-3.242667c-162.816 0-231.253333-146.944-233.984-153.258667-6.144-13.482667-0.170667-29.184 13.312-35.328 13.482667-5.973333 29.184-0.170667 35.328 13.141333 2.218667 4.949333 57.514667 122.026667 185.514667 122.026667h2.56c136.021333-1.536 190.634667-120.661333 191.146666-121.685333 5.973333-13.482667 21.674667-19.626667 35.157334-13.482667 13.482667 5.973333 19.456 21.674667 13.482666 35.157333-2.730667 6.144-69.461333 151.381333-239.274666 153.429334z m134.314666-294.741334c-29.354667 0-53.248-23.893333-53.248-53.248s23.722667-53.248 53.248-53.248c29.354667 0 53.248 23.893333 53.248 53.248s-23.893333 53.248-53.248 53.248z m-266.752 0c-29.354667 0-53.248-23.893333-53.248-53.248s23.722667-53.248 53.248-53.248c29.354667 0 53.248 23.893333 53.248 53.248s-23.893333 53.248-53.248 53.248z m0 0" fill="" ></path></symbol><symbol id="icon-zan1" viewBox="0 0 1024 1024"><path d="M729.074456 961.131137 414.095691 961.131137c-20.687158 0-37.453007-17.068747-37.453007-38.10383 0-21.064758 16.738219-38.10383 37.453007-38.10383l314.978764 0c2.27174 0 5.650697-0.496304 9.219989-2.921539 3.382027-2.316765 5.111414-5.431709 5.815449-7.609305l135.962601-314.645167c8.654101-19.658735 6.788613-42.11519-4.676509-60.063981-11.520381-17.963117-30.962176-28.688389-52.001351-28.688389l-197.778482-0.054235c-16.57756 0-31.045063-10.988262-35.803437-27.103288-4.650927-16.08842 1.460258-33.459043 15.250332-42.694381 32.125675-21.588691 63.331397-140.283 37.642319-219.082694-14.439874-44.582381-37.642319-43.259246-47.674812-42.694381l-10.9504 0.096191c-14.656815 0-28.447912 5.804193-38.886659 16.363689-10.330276 10.588149-16.008602 24.6494-16.008602 39.647999l0 51.971675c0 60.561307-23.229051 117.509321-65.196885 160.28659-37.181831 37.993313-85.288478 60.835553-137.045259 65.49569l0 413.845493c0 19.798928-7.517207 38.325887-21.255093 52.27662-13.764492 14.034645-32.070416 21.782096-51.324946 21.782096L133.70263 961.13216c-18.685571 0-36.235272-7.444553-49.323359-20.871353-13.168927-13.425777-20.335141-31.293727-20.335141-50.207495L64.04413 475.819986c0-20.719904 7.895831-40.336684 22.309098-55.019081 14.520715-14.710027 33.640168-22.760376 54.029544-22.760376l166.493965 0c39.4812 0 76.472696-15.70161 104.434537-44.128033 27.744901-28.454052 43.185568-66.228377 43.185568-106.467847l0-51.971675c0-35.387975 13.43908-68.585051 37.965683-93.453439 24.580838-24.994254 57.056484-38.737256 91.670839-38.737256l8.760524 0c55.543014-3.226484 99.999528 31.196513 120.768551 94.77555 21.469987 66.186422 15.440667 166.007895-15.198144 236.770542l124.956943 0c46.37726 0.081864 89.265046 23.711028 114.520243 63.290465 25.339108 39.647999 29.313629 89.110527 10.600429 132.286885L813.227064 903.645888c-6.166443 15.909341-17.631566 30.631648-32.829709 41.301661C765.092787 955.560258 747.353774 961.131137 729.074456 961.131137zM138.733203 884.951107l113.465215 0L252.198417 474.123344 140.382772 474.123344 138.733203 884.951107z" fill="#D6CBC5" ></path></symbol></svg>',s=(s=document.getElementsByTagName("script"))[s.length-1].getAttribute("data-injectcss"),d=function(e,t){t.parentNode.insertBefore(e,t)};if(s&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}function a(){i||(i=!0,n())}function m(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(m,50)}a()}t=function(){var e,t;(t=document.createElement("div")).innerHTML=l,l=null,(e=t.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",t=e,(e=document.body).firstChild?d(t,e.firstChild):e.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(c=function(){document.removeEventListener("DOMContentLoaded",c,!1),t()},document.addEventListener("DOMContentLoaded",c,!1)):document.attachEvent&&(n=t,o=e.document,i=!1,m(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,a())})}(window);
\ No newline at end of file
{
"id": "2716892",
"name": "qia",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "5050475",
"name": "结束",
"font_class": "jieshu",
"unicode": "e637",
"unicode_decimal": 58935
},
{
"icon_id": "1812545",
"name": "电脑",
"font_class": "diannao",
"unicode": "e6ac",
"unicode_decimal": 59052
},
{
"icon_id": "6480674",
"name": "笑脸",
"font_class": "xiaolian",
"unicode": "e631",
"unicode_decimal": 58929
},
{
"icon_id": "1939759",
"name": "赞",
"font_class": "zan1",
"unicode": "e600",
"unicode_decimal": 58880
}
]
}
// axios.js
import axios from "axios";
const instance = axios.create({
// baseURL: "http://192.168.31.228:8080", // 设置基础 URL
baseURL:'/api',
timeout: 1000, // 设置请求超时时间
});
instance.interceptors.request.use(
(config: any) => {
// 在发送请求之前做些什么,例如添加 token
const token = localStorage.getItem("token");
if (token) {
config.headers['X-Access-Token'] = `Bearer ${token}`;
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response: any) => {
// 对响应数据做点什么
return response.data;
},
(error: any) => {
// 对响应错误做点什么
return Promise.reject(error);
}
);
export default instance;
import http from "../index";
//登录
const Login = (query: any) => {
return http({
url: `/kf/auth/login`,
method: "post",
data: query,
});
};
//新增询价单
const InquiryAdd = (query: any) => {
return http({
url: `/inquiryAdd`,
method: "get",
data: query,
});
};
//快捷回复
const Kjiehuifu = (query: any) => {
return http({
url: "/Kjiehuifu",
method: "get",
data: query,
});
};
//获取用户列表
const getUserList = (query: any) => {
return http({
url: "/getUserList",
method: "get",
data: query,
});
};
//查看未读消息
const checkMesssages = (query: any) => {
return http({
url: "/checkMesssages",
method: "get",
data: query,
});
};
//上传接口
const getUploadConfigInfo = (query: any) => {
return http({
url: "/getUploadConfigInfo",
method: "get",
data: query,
});
};
//系统上传
const upload = (query: any) => {
return http({
url: "/sys/common/upload",
method: "get",
data: query,
});
};
export {
Login,
InquiryAdd,
Kjiehuifu,
getUserList,
checkMesssages,
upload,
getUploadConfigInfo,
};
<template>
<div>
<div class="chat-textarea">
<div class="chat-bar">
<div>
<img
src="../assets/emojiSmiles-v1-light.png"
class="icon emoji"
@click.stop="showEmotion = true"
/>
<img
src="../assets/icon_image_s1_01.png"
@click="upfile"
class="icon emoji"
alt=""
/>
<img
src="../assets/icon_folder_s1_01.png"
@click="upfile"
class="icon emoji"
alt=""
/>
</div>
<div class="chat-bar-right">
<img
src="../assets/icon_folder_s1_01.png"
@click="upfile"
class="icon emoji"
alt=""
/>
快捷回复
</div>
</div>
<!-- <el-autocomplete
class="autocomplete"
v-model="state"
:fetch-suggestions="querySearchAsync"
placeholder="请输入问题"
type="textarg"
@select="handleSelect"
ref="automaticPromptRef"
/> -->
<!-- <el-input
v-model="state"
ref="automaticPromptRef"
class="el-autocomplete"
placeholder="输入信息"
type="textarea"
resize="none"
@change="handleSelect"
/> -->
<div
class="el-autocomplete"
id="el-autocomplete"
ref="el-autocomplete"
contenteditable="true"
style="outline: none;height: 150px;"
@paste="onPaste"
@input="handleSelect"
>
</div>
</div>
<emotion
v-if="showEmotion"
style="position: absolute; top: calc(100% - 465px); background: #fff;"
:height="200"
@emotion="handleEmotion"
/>
<input
ref="file"
style="display: none;"
accept="image/bmp,image/jpeg,image/jpg,image/png"
type="file"
@change="fileChange"
/>
</div>
</template>
<script lang="ts" setup>
import Emotion from './IndexComponent.vue'
import { ElLoading } from 'element-plus'
import { ref, watch, defineEmits, defineExpose } from 'vue'
import { upLoadFilesHander } from '../minxins/UploadMixin'
import axios from 'axios'
const state = ref('')
const elAutocomplete = ref(null)
const file = ref(null)
interface LinkItem {
value: string
link: string
}
// uploadMixin()
const showEmotion = ref<boolean>(false)
const automaticPromptRef = ref(null)
const links = ref<LinkItem[]>([])
const handleEmotion = (i) => {
state.value += i
showEmotion.value = false
automaticPromptRef.value.focus()
}
const upfile = () => {
file.value.click()
}
const fileChange = () => {
var e = window.event || event
var oFile = e.target.files[0]
upLoadFilesHander(oFile).then((ress) => {
const { success, result } = ress
console.log(result)
})
const loading = ElLoading.service({
lock: true,
text: '上传中...',
spinner: 'el-icon-loading',
})
loading.close()
}
const showWord = () => {
console.log(3)
}
// const loadFromBackend = async (value: string) => {
// try {
// //输入时候请求后端根据输入值得到提示。 后端返回集合,集合里面对象属性为value和link都是string类型
// const response = await axios.get(
// `http://localhost:8080/getAutoMsg/${value}`,
// )
// links.value = response.data
// } catch (error) {
// console.error(error)
// }
// }
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
const results = queryString
? links.value.filter(createFilter(queryString))
: links.value
cb(results)
}
const createFilter = (queryString: string) => {
return (link: LinkItem) => {
return link.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
}
}
const handleSelect = (value: string) => {
state.value = value.target.innerHTML
}
const emit = defineEmits(['updateState'])
watch(state, async (newValue) => {
if(!newValue) document.getElementById('el-autocomplete').innerHTML = ''
emit('updateState', newValue)
})
const getPaste = (e: any) => {
console.log(e, '粘贴')
// 如果剪贴板没有数据则直接返回
// 用Promise封装便于将来使用
return new Promise((resolve, reject) => {
if (!(e.clipboardData && e.clipboardData.items)) {
reject(new Error('Not allow to paste this type!'))
} else {
// 复制的内容在剪贴板里位置不确定,所以通过遍历来保证数据准确
for (let i = 0, len = e.clipboardData.items.length; i < len; i++) {
const item = e.clipboardData.items[i]
// 文本格式内容处理
if (item.kind === 'string') {
item.getAsString((str: any) => {
resolve(str)
})
// 图片格式内容处理
} else if (item.kind === 'file') {
const pasteFile = item.getAsFile()
// 处理pasteFile
const imgEvent = {
target: {
files: [pasteFile],
},
}
chooseImg(imgEvent, (url: any) => {
resolve(url)
})
} else {
reject(new Error('Not allow to paste this type!'))
}
}
}
})
}
const onPaste = (event: any) => {
// navigator.permissions.query({name: 'clipboard-read'}).then(function(result) {
// if (result.state === 'granted') {
// // 用户已授权访问剪贴板
// } else if (result.state === 'prompt') {
// // 用户尚未授权访问剪贴板,需要提示用户授权
// result.onchange = function() {
// if (result.state === 'granted') {
// // 用户已授权访问剪贴板
// consoel
// } else if (result.state === 'denied') {
// // 用户拒绝了访问剪贴板的请求
// }
// };
// } else if (result.state === 'denied') {
// // 用户拒绝了访问剪贴板的请求
// }
// });
const items = (event.clipboardData || window.clipboardData).items
console.log(items)
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
const file = items[i].getAsFile()
const reader = new FileReader()
reader.onload = function (e) {
const img = document.createElement('img')
img.src = e.target.result
img.style.width = '200px' // 设置图片宽度
img.style.height = '300px' // 设置图片高度
event.target.appendChild(img)
}
reader.readAsDataURL(file)
}
}
// e.stopPropagation()
// e.preventDefault()
// var text = '',
// event = e.originalEvent || e
// if (event.clipboardData && event.clipboardData.getData) {
// text = event.clipboardData.getData('text/plain')
// } else if (window.clipboardData && window.clipboardData.getData) {
// text = window.clipboardData.getData('Text')
// }
// if (document.queryCommandSupported('insertText')) {
// document.execCommand('insertText', false, text)
// } else {
// document.execCommand('paste', false, text)
// }
// getPaste(e).then((res: any) => {
// console.log(res, 'res')
// })
}
const toPreviewer = (dataUrl, cb) => {
cb && cb(dataUrl)
}
const compress = (img, fileType, maxWidth) => {
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
const proportion = img.width / img.height
const width = maxWidth
const height = maxWidth / proportion
console.log(width, height, '打印宽高')
canvas.width = width
canvas.height = height
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, width, height)
const base64data = canvas.toDataURL(fileType, 0.75)
canvas = ctx = null
return base64data
}
const chooseImg = (e, cb, maxsize = 200 * 1024) => {
const file = e.target.files[0]
if (!file || !/\/(?:jpeg|jpg|png)/i.test(file.type)) {
return
}
const reader = new FileReader()
reader.onload = function () {
const result = this.result
let img = new Image()
if (result.length <= maxsize) {
toPreviewer(result, cb)
return
}
img.onload = function () {
const compressedDataUrl = compress(img, file.type, maxsize / 1024)
toPreviewer(compressedDataUrl, cb)
img = null
}
img.style.width = '100px'
img.style.height = '100px'
img.src = result
}
reader.readAsDataURL(file)
}
defineExpose({
setState(res: any) {
state.value = res
},
getState() {
return state.value
},
})
</script>
<style lang="scss" scoped>
.icon {
width: 24px;
height: 24px;
margin-right: 32px;
}
.chat-bar {
display: flex;
align-items: center;
margin-bottom: 8px;
justify-content: space-between;
}
.el-autocomplete {
width: 100%;
max-width: 100%;
max-height: 150px;
overflow: auto;
}
.el-autocomplete img {
max-width: 100%; /* 设置图片最大宽度为容器宽度 */
max-height: 100%; /* 设置图片最大高度为容器高度 */
}
.chat-bar-right {
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
color: #384860;
display: flex;
align-items: center;
.icon {
margin-right: 8px;
}
}
</style>
<template>
<div class="ly-emotion">
<slot />
</div>
</template>
<script setup>
import * as emoji from 'emoji.json'
// const emoji = require('emoji.json')
const list = emoji.slice(0, 55)
</script>
<style scoped>
.ly-emotion {
display: inline-block;
}
.ly-static-emotion {
width: 24px;
height: 24px;
display: inline-block;
}
</style>
<template>
<div>
<div class="emotion-box" :style="{ height: height + 'px' }">
<div
v-for="(line, i) in list"
:key="i"
class="emotion-box-line"
@click.enter="clickHandler(line.char)"
>
{{ line.char }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, defineEmits, defineExpose, defineProps } from 'vue'
const list = ref([])
import * as emoji from 'emoji.json'
const getList = () => {
list.value = emoji.slice(0, 50)
list.value.push(emoji[55])
list.value.push(emoji[56])
list.value.push(emoji[57])
list.value.push(emoji[60])
}
const emits = defineEmits(['emotion'])
const clickHandler = (i: number | string) => {
emits('emotion', i)
}
getList()
defineProps({
height: {
type: Number,
default: 200,
},
})
</script>
<style scoped>
.emotion-box {
margin: 0 auto;
width: 250px;
box-sizing: border-box;
padding: 5px;
border: 1px solid #b4b4b4;
overflow: hidden;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
}
.emotion-box-line {
display: flex;
font-size: 21px;
}
.emotion-item {
flex: 1;
text-align: center;
cursor: pointer;
}
</style>
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store/index";
import "./assets/css/reset.css";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import UUID from 'vue3-uuid'
import { useUserStore } from "./store/modules/user";
import "@/permission"; // permission control
import "element-plus/dist/index.css";
import "../src/mocks/mock";
const app = createApp(App);
app.use(store).use(UUID).use(router).mount("#app");
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
// const COS = require("cos-wx-sdk-v5");
// import { ElMessage } from "element-plus";
// import { upload, getUploadConfigInfo } from "../axios/model/user";
// export default function () {
// interface UploadMixin {
// uploadMixinPath: string;
// uploadMixinType: string;
// uploadMixinCredentials: any;
// uploadMixinTimeStamp: number;
// }
// const uploadMixin: UploadMixin = {
// uploadMixinPath: "",
// uploadMixinType: "",
// uploadMixinCredentials: {},
// uploadMixinTimeStamp: 0,
// };
// const upLoadFilesHander = (tempFilePaths:any, tempFiles:any, zip:any) => {
// return new Promise((resolve) => {
// if (tempFiles.length != 0) {
// if (typeof upLoadHander !== "function") return;
// let uploadObj = {};
// let uploadNum = tempFiles.length;
// uploadObj.length = tempFiles.length;
// //处理上传结果函数
// function loadHandler({ success, message }:any, i) {
// uploadNum = uploadNum - 1;
// uploadObj[i] = message;
// if (!success) {
// uploadObj[i] = "";
// ElMessage({
// message: `[${i + 1}]上传失败,请重新上传!`,
// type: "none",
// });
// }
// if (uploadNum == 0) {
// const arr = Array.from(uploadObj);
// // ;
// resolve({ success: true, result: arr });
// }
// }
// //进行上传
// tempFiles.forEach((file:any, i:any) => {
// upLoadHander({
// path: tempFilePaths[i],
// file: file.path,
// zip: zip || "",
// onSuccess: ({ success, message }:any) => {
// loadHandler({ success, message }, i);
// },
// onError: (res:any) => {
// loadHandler({ success: false, message: "" }, i);
// },
// });
// });
// } else {
// resolve({ success: true, result: [] });
// }
// });
// };
// const upLoadHander = (obj:any) => {
// const { path, file, biz } = obj;
// const pathArr = (path || "").split("/");
// const pathStr = pathArr[pathArr.length - 1];
// const fileName = (pathStr || "").split(".");
// let fileInfo = {
// fileTyle: fileName.length > 1 ? fileName[fileName.length - 1] : "",
// fileName: fileName[0],
// biz: biz || "temp",
// file: file,
// };
// const none = (async function hander(noCredentials) {
// let res = {
// result:{}
// },
// staticDomain = "",
// resResult = true;
// //noCredentials变量控制是否走对象存储
// if (noCredentials) {
// let query = {}
// res = await getUploadConfigInfo(query);
// console.log(res)
// if (res.code === 200 && res.result) {
// let result = res.result
// uploadMixin.uploadMixinPath = result.upLoadPath;
// uploadMixin.uploadMixinType = result.uploadType;
// staticDomain = result.staticDomain || "https://cdn.tikcos.cn";
// if (uploadMixin.uploadMixinType === "txcos") {
// const credentials = result.credentials;
// uploadMixin.uploadMixinCredentials = {
// TmpSecretId: credentials.tmpSecretId,
// TmpSecretKey: credentials.tmpSecretKey,
// SecurityToken: credentials.sessionToken,
// StartTime: result.startTime,
// ExpiredTime: result.expiredTime,
// };
// }
// } else {
// resResult = false;
// }
// }
// //resResult判断接口是否正常
// if (resResult) {
// if (uploadMixin.uploadMixinType == "txcos") {
// //走对象存储
// const cos = new COS({
// getAuthorization: (options:any, callback:any) => {
// callback(uploadMixin.uploadMixinCredentials);
// },
// });
// const tempFolder = fileInfo.biz === "temp" ? "" : `${fileInfo.biz}/`;
// const folder = `${fileInfo.fileName}_${new Date().getTime()}.${
// fileInfo.fileTyle
// }`;
// let result = res.result || {}
// cos.postObject(
// {
// FilePath: fileInfo.file,
// Bucket: result.bucketName || "tikcos-1257774783",
// Region: result.regionid || "ap-guangzhou",
// Key: uploadMixin.uploadMixinPath + tempFolder + folder,
// Body: fileInfo.file,
// onProgress: function (progressData:any) {
// obj.onProgress && obj.onProgress(progressData);
// },
// },
// (err:any, data:any) => {
// if (!err && data.statusCode === 200) {
// const imgurl = `${staticDomain}/${uploadMixin.uploadMixinPath}${tempFolder}${folder}`;
// obj.onSuccess({
// success: true,
// message: imgurl,
// });
// } else {
// obj.onError({
// success: false,
// message: err || "图片上传失败",
// });
// }
// }
// );
// } else {
// //走系统上传
// upload({
// biz: fileInfo.biz,
// file: fileInfo.file,
// }).then((uploadRes:any) => {
// if (uploadRes.success && uploadRes.message) {
// //保存绝对路径,不然小程序会有问题
// let url = uploadRes.message;
// obj.onSuccess({ success: true, message: url });
// } else {
// obj.onError({
// success: false,
// message: uploadRes.message || "图片上传失败",
// });
// }
// })
// } else {
// obj.onError({
// success: false,
// message: "服务接口报错",
// });
// }}
// })(true);
// };
// }
import { reactive, watch, ref, defineEmits, defineExpose } from "vue";
import { ElMessage } from "element-plus";
import { upload, getUploadConfigInfo } from "../axios/model/user";
// const COS = require("cos-wx-sdk-v5");
import COS from "cos-js-sdk-v5";
interface UploadMixin {
uploadMixinPath: string;
uploadMixinType: string;
uploadMixinCredentials: any;
uploadMixinTimeStamp: number;
}
const uploadMixin: UploadMixin = {
uploadMixinPath: "",
uploadMixinType: "",
uploadMixinCredentials: {},
uploadMixinTimeStamp: 0,
};
interface CustomAxiosResponse {
code: number;
result: {
bucketName: string;
regionid: string;
upLoadPath: string;
uploadType: string;
staticDomain: string;
startTime: string;
expiredTime: string;
credentials: {
tmpSecretId: string;
tmpSecretKey: string;
sessionToken: string;
};
};
}
export const upLoadFilesHander = (file: any) => {
return new Promise((resolve) => {
const loadHandler = ({ success, message }: any) => {
if (!success) {
ElMessage({
message: `上传失败,请重新上传!`,
type: "warning",
});
} else {
resolve({ success: true, result: message });
}
};
const obj = {
file: file,
onSuccess: ({ success, message }: any) => {
loadHandler({ success, message });
},
onError: ({ success, message }: any) => {
loadHandler({ success: false, message: "" });
},
};
//进行上传
upLoadHander(obj);
});
};
export const upLoadHander = async (obj: any) => {
const { file } = obj;
const fileName = file.name.split(".");
const fileInfo = {
fileTyle: fileName.length > 1 ? fileName[fileName.length - 1] : "",
fileName: fileName[0],
biz: obj?.data?.biz || "temp",
file: file,
};
const none = async function hander(noCredentials: boolean) {
// const res:CustomAxiosResponse = reactive({
// code: 0,
// result: {
// bucketName: "",
// regionid: "",
// upLoadPath: "",
// uploadType: "",
// staticDomain: "",
// startTime: "",
// expiredTime: "",
// credentials: {
// tmpSecretId: "",
// tmpSecretKey: "",
// sessionToken: "",
// },
// },
// });
let staticDomain = "";
let resResult = true;
//noCredentials变量控制是否走对象存储
if (noCredentials) {
const query = {};
const res:any = await getUploadConfigInfo(query);
// console.log(res1,'res')
// console.log(res);
if (res.code === 200 && res.result) {
const result = res.result;
uploadMixin.uploadMixinPath = result.upLoadPath;
uploadMixin.uploadMixinType = result.uploadType;
staticDomain = result.staticDomain || "https://cdn.tikcos.cn";
if (uploadMixin.uploadMixinType === "txcos") {
const credentials = result.credentials;
uploadMixin.uploadMixinCredentials = {
TmpSecretId: credentials?.tmpSecretId,
TmpSecretKey: credentials?.tmpSecretKey,
SecurityToken: credentials?.sessionToken,
StartTime: result.startTime,
ExpiredTime: result.expiredTime,
};
}
} else {
resResult = false;
}
}
//resResult判断接口是否正常
if (resResult) {
const query = {};
const res:any = await getUploadConfigInfo(query);
if (uploadMixin.uploadMixinType == "txcos") {
//走对象存储
const cos = new COS({
getAuthorization: (options: any, callback: any) => {
callback(uploadMixin.uploadMixinCredentials);
},
});
const tempFolder = fileInfo.biz === "temp" ? "" : `${fileInfo.biz}/`;
const folder = `${fileInfo.fileName}_${new Date().getTime()}.${
fileInfo.fileTyle
}`;
const result = res.result || {};
cos.putObject(
{
// FilePath: fileInfo.file,
Bucket: result.bucketName || "tikcos-1257774783",
Region: result.regionid || "ap-guangzhou",
Key: uploadMixin.uploadMixinPath + tempFolder + folder,
Body: fileInfo.file,
onProgress: function (progressData: any) {
obj.onProgress && obj.onProgress(progressData);
},
},
async (err: any, data: any) => {
if (!err && data.statusCode === 200) {
const imgurl = `${staticDomain}/${uploadMixin.uploadMixinPath}${tempFolder}${folder}`;
obj.onSuccess({
success: true,
message: imgurl,
});
} else {
obj.onError({
success: false,
message: data.message || "图片上传失败",
});
}
}
);
} else {
// 其他存储类型暂不支持
obj.onError({
success: false,
message: "服务接口报错",
});
}
} else {
obj.onError({
success: false,
message: "服务接口报错",
});
}
};
none(true);
};
const COS = require('cos-wx-sdk-v5');
import { ElMessage } from 'element-plus'
const uploadMixin = {
data() {
return {
uploadMixinPath: '',
uploadMixinType: '',
uploadMixinCredentials: {},
uploadMixinTimeStamp: 0
};
},
methods: {
upLoadFilesHander(tempFilePaths, tempFiles, zip){
return new Promise((resolve)=>{
if (tempFiles.length != 0) {
if(typeof this.upLoadHander !== 'function') return;
let uploadObj = {};
let uploadNum = tempFiles.length;
uploadObj.length = tempFiles.length;
//处理上传结果函数
function loadHandler({ success, message },i){
uploadNum = uploadNum - 1;
uploadObj[i] = message;
if (!success) {
uploadObj[i] = '';
ElMessage({
message: `[${i+1}]上传失败,请重新上传!`,
type: 'none',
})
}
if (uploadNum == 0) {
const arr = Array.from(uploadObj);
// ;
resolve({success: true, result: arr})
}
}
//进行上传
tempFiles.forEach((file, i) => {
this.upLoadHander({
path: tempFilePaths[i],
file: file.path,
zip: zip || '',
onSuccess: ({ success, message }) => {
loadHandler({ success, message },i)
},
onError: (res) => {
loadHandler({ success: false, message:''}, i)
}
});
});
}else{
resolve({success: true,result: []})
}
});
},
upLoadHander(obj) {
const { path, file, biz} = obj;
const pathArr = (path || '').split('/');
const pathStr = pathArr[pathArr.length -1];
const fileName = (pathStr || '').split('.');
let fileInfo = {
fileTyle: fileName.length > 1 ? fileName[fileName.length - 1] : '',
fileName: fileName[0],
biz: biz || 'temp',
file: file
};
const that = this;
const none = (async function hander(noCredentials) {
let res = {},
staticDomain = '',
resResult = true;
//noCredentials变量控制是否走对象存储
if (noCredentials) {
console.log(that.$https,'https')
res = await that.$https.request({
url: that.$interfaces.getUploadConfigInfo,
method: "get",
});
if (res.code === 200 && res.result) {
that.uploadMixinPath = res.result.upLoadPath;
that.uploadMixinType = res.result.uploadType;
staticDomain = res.result.staticDomain || 'https://cdn.tikcos.cn';
if (that.uploadMixinType === 'txcos') {
const ress = res.result.response;
const credentials = ress.credentials;
that.uploadMixinCredentials = {
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
SecurityToken: credentials.sessionToken,
StartTime: ress.startTime,
ExpiredTime: ress.expiredTime
};
}
} else {
resResult = false;
}
}
//resResult判断接口是否正常
if (resResult) {
if (that.uploadMixinType == 'txcos') {
//走对象存储
const cos = new COS({
getAuthorization: (options, callback) => {
callback(that.uploadMixinCredentials);
}
});
const tempFolder = fileInfo.biz === 'temp' ? '' : `${fileInfo.biz}/`;
const folder = `${fileInfo.fileName}_${new Date().getTime()}.${fileInfo.fileTyle}`;
cos.postObject(
{
FilePath: fileInfo.file,
Bucket: res.result.bucketName || 'tikcos-1257774783',
Region: res.result.regionid || 'ap-guangzhou',
Key: that.uploadMixinPath + tempFolder + folder,
Body: fileInfo.file,
onProgress: function (progressData) {
obj.onProgress && obj.onProgress(progressData);
}
},
(err, data) => {
if (!err && data.statusCode === 200) {
const imgurl = `${staticDomain}/${that.uploadMixinPath}${tempFolder}${folder}`;
obj.onSuccess({
success: true,
message: imgurl
});
} else {
obj.onError({
success: false,
message: err || '图片上传失败'
});
}
}
);
} else {
//走系统上传
that.$utils.ossUpload(
{
biz: fileInfo.biz,
file: fileInfo.file
},
(uploadRes) => {
// ;
if (uploadRes.success && uploadRes.message) {
//保存绝对路径,不然小程序会有问题
let url = uploadRes.message;
obj.onSuccess({ success: true, message: url });
} else {
obj.onError({
success: false,
message: uploadRes.message || '图片上传失败'
});
}
}
);
}
} else {
obj.onError({
success: false,
message: '服务接口报错'
});
}
})(true);
},
setOssUpload(obj) {
ossUpload(
{
biz: obj.biz,
file: obj.file
},
(uploadRes) => {
if (uploadRes.success && uploadRes.message) {
//保存绝对路径,不然小程序会有问题
let url = uploadRes.message;
const pre = url.startsWith('/') ? '' : '/';
url = axios['imgUrl'] + url;
obj.onSuccess({ success: true, message: url });
} else {
obj.onError({
success: false,
message: uploadRes.message || '图片上传失败'
});
}
}
);
}
}
};
export { uploadMixin };
// import io from 'socket.io-client';
// import { ref, onMounted, onUnmounted } from 'vue'
// import { ElMessage } from 'element-plus'
// export default function () {
// const message = {};
// const showMessage = {};
// const socket = ref(new WebSocket("ws://192.168.31.228:8888?username=admin&password=123"));
// socket.value.onopen = function (e) {
// console.log(e)
// }
// socket.value.onerror = function(e){
// console.log(e)
// };
// socket.value.onmessage = function (e) {
// console.log(e)
// };
// return {
// message,
// showMessage
// }
// }
\ No newline at end of file
const Mock = require('mockjs')
Mock.mock("/api/login", "get", () => {
return {
code: "200",
success: true,
message: '',
result: {
username: "admin",
token: 'admin'
},
};
});
//询价单
Mock.mock("/api/inquiryAdd", "get", () => {
return {
code: "200",
success: true,
message: '操作成功!',
result: {
username: "admin",
token: 'admin'
},
};
});
//快捷回复
Mock.mock("/api/kjiehuifu", 'get', () => {
return {
code: '200',
success: true,
message: '',
result: [
{
message: '感谢您的咨询,我们会尽快回复您',
}, {
message: '感谢您的耐心等待,您的问题已经收到,将尽快给您答复。'
}, {
messag: '谢谢你的来信,我会尽快回复。'
}, {
messag: '谢谢你的信息,我会尽快做出回复。'
}
]
}
})
//列表
Mock.mock("/api/getUserList", 'get', () => {
return {
code: '200',
success: true,
message: '',
result: [{
userId: '11fb824c0deb4e8b947275e1e7c4f7a2',
username: '1710323861524',
userImg: 'https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png',
messages: []
}, {
userId: '4f99b6dc32d84a288d88e7d9635521a3',
username: '1710317951930',
userImg: 'https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png',
messages: []
}]
}
})
//查看未读消息
Mock.mock("/api/checkMesssages", 'get', () => {
return {
code: '200',
success: true,
message: '',
result: []
}
})
Mock.mock("/api/getUploadConfigInfo", 'get', () => {
return {
code: '200',
success: true,
message: '',
result: {
bucketName: "",
regionid: "",
upLoadPath: "",
uploadType: "",
staticDomain: "",
startTime: "",
expiredTime: "",
credentials: {
tmpSecretId: "",
tmpSecretKey: "",
sessionToken: "",
},
}
}
})
Mock.mock("/api/upload", 'get', () => {
return {
code: '200',
success: true,
message: '',
result: {
bucketName: "",
regionid: "",
upLoadPath: "",
uploadType: "",
staticDomain: "",
startTime: "",
expiredTime: "",
credentials: {
tmpSecretId: "",
tmpSecretKey: "",
sessionToken: "",
},
},
}
})
\ No newline at end of file
//路由守卫.ts
// import router from "../src/router";
// import { useUserStore } from "./store/modules/user";
// router.beforeEach((to: any, from: any, next: any) => {
// const store = useUserStore();
// const whiteList = ["/login", "/register"];
// if (store.userInfo.token) {
// next();
// } else {
// if (whiteList.includes(to.path)) {
// //白名单登录
// next();
// } else {
// next("/login");
// }
// }
// });
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: HomeView,
},
// {
// path: "/login",
// name: "login",
// component: () =>
// import(/* webpackChunkName: "about" */ "../views/loginView.vue"),
// },
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'cos-wx-sdk-v5';
\ No newline at end of file
import { createPinia } from "pinia";
// 创建 Pinia 实例
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
console.log(piniaPluginPersistedstate,'打印')
pinia.use(piniaPluginPersistedstate);
export default pinia;
import moment from "moment";
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => {
return {
isConnected: false, //连接状态
messages: [],
customerInfo: {
username: "10086",
messages: [],
},
count: 0,
userInfo: {
password: "",
token: "",
},
// userList: [],
};
},
actions: {
setCount(num: number) {
this.count = num;
},
setCustomerInfo(res: any) {
this.customerInfo = res;
},
setUserInfo(res: any) {
this.userInfo = res;
},
setUserListMessages(res: any) {
const messages: any = this.customerInfo.messages || [];
const obj = {
isSent: false,
msgType: res.msgType,
content: res.content,
cmd: res.cmd,
to: res.to,
id: res.id,
form: res.from,
userImg:
"https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png",
time: moment(res.createTime).format("YYYY-DD-MM HH:mm:ss"),
};
messages.push(obj);
this.customerInfo.messages = messages;
},
setusernameMessage(res: any) {
const friends = res.friends;
for (const key in friends) {
const chatDatas = friends[key];
chatDatas.forEach((item: any) => {
item.userImg =
"https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png";
item.time = moment(res.createTime).format("YYYY-DD-MM HH:mm:ss");
});
for (const index in chatDatas) {
const userId = chatDatas[index].from;
this.customerInfo.messages = chatDatas;
}
}
},
connect() {
// 连接成功后,将 isConnected 状态设置为 true
this.isConnected = true;
},
disconnect() {
// 断开连接或退出登录时,将 isConnected 状态设置为 false
this.isConnected = false;
},
},
persist: true,
});
export interface LoginFace {
username: string;
password: string;
}
export class LoginData<LoginFace> {
username = "10086";
password = "123456";
}
export function getShortDate(): string {
const now = new Date();
let month: string = (now.getMonth() + 1).toString();
if (month.length < 2) {
month = "0" + month;
}
let day: string = now.getDate().toString();
if (day.length < 2) {
day = "0" + day;
}
let hour: string = now.getHours().toString();
if (hour.length < 2) {
hour = "0" + hour;
}
let minutes: string = now.getMinutes().toString();
if (minutes.length < 2) {
minutes = "0" + minutes;
}
return month + "-" + day + " " + hour + ":" + minutes;
}
import { ElMessage } from "element-plus";
import { useUserStore } from "../store/modules/user";
let websocket: WebSocket | null = null; // 用于存储实例化后websocket
let rec: any; // 断线重连后,延迟5秒重新创建WebSocket连接 rec用来存储延迟请求的代码
// 创建websocket
function creatWebSocket(wsUrl: string) {
// 判断当前浏览器是否支持WebSocket
if ("WebSocket" in window) {
console.log("当前浏览器支持 WebSocket");
} else if ("MozWebSocket" in window) {
console.log("当前浏览器支持 MozWebSocket");
} else {
console.log("当前浏览器不支持 WebSocket");
}
try {
initWebSocket(wsUrl); // 初始化websocket连接
} catch (e) {
console.log("尝试创建连接失败");
reConnect(wsUrl); // 如果无法连接上 webSocket 那么重新连接!可能会因为服务器重新部署,或者短暂断网等导致无法创建连接
}
}
// 初始化websocket
function initWebSocket(wsUrl: string) {
if (!websocket) websocket = new WebSocket(wsUrl);
websocket.onopen = function () {
websocketOpen();
};
// // 接收
websocket.onmessage = function (e: MessageEvent<any>) {
websocketonmessage(e);
};
// 连接发生错误
websocket.onerror = function () {
console.log("WebSocket连接发生错误");
// isConnect = false; // 连接断开修改标识
reConnect(wsUrl); // 连接错误 需要重连
};
websocket.onclose = function (e) {
websocketclose(e);
};
}
// 定义重连函数
const reConnect = (wsUrl: string) => {
console.log("尝试重新连接");
if (useUserStore().isConnected) return; // 如果已经连上就不在重连了
rec && clearTimeout(rec);
rec = setTimeout(function () {
// 延迟5秒重连 避免过多次过频繁请求重连
creatWebSocket(wsUrl);
}, 5000);
};
// 创建连接
function websocketOpen() {
console.log("连接成功");
useUserStore().connect();
}
// 数据接收
function websocketonmessage(e: MessageEvent<any>) {
const res = JSON.parse(e.data); // 解析JSON格式的数据
// 下面的判断则是后台返回的接收到的数据 如何处理自己决定
if (res.command == 11) {
//将数据放在store中
useUserStore().setUserListMessages(res.data);
} else if (res.command == 20) {
useUserStore().setusernameMessage(res.data);
}
}
// 关闭
function websocketclose(e: any) {
useUserStore().disconnect(); // 修改连接状态
}
// 数据发送
function websocketsend(res: any) {
console.log(websocket, "websocket");
if (websocket && useUserStore().isConnected) {
// 检查连接状态
console.log("发送的数据", JSON.stringify(res));
websocket.send(JSON.stringify(res));
} else {
ElMessage({
showClose: true,
message: "请选择设备连接",
type: "error",
});
}
}
// 实际调用的方法==============
// 发送
function sendWebSocket(data: any) {
// 如果未保持连接状态 不允许直接发送消息 提示请选择连接设备
if (!useUserStore().isConnected) {
ElMessage({
showClose: true,
message: "请选择设备连接",
type: "error",
});
} else {
websocketsend(data);
console.log("------------------");
}
}
// 关闭
const closeWebSocket = () => {
if (websocket) {
websocket.close();
ElMessage({
showClose: true,
message: "设备已关闭",
type: "success",
});
}
};
export { initWebSocket, sendWebSocket, creatWebSocket, closeWebSocket };
<template>
<div class="common-layout">
<el-container>
<el-aside width="280px">
<el-header class="el-header-left">
<el-input placeholder="搜索" />
</el-header>
<div
active-text-color="#000"
background-color="#fff"
default-active="1"
>
<div
class="el-menu-item"
:class="getMenuItem(item)"
:index="item?.userId"
:key="item?.userId"
v-for="item in userList"
@click="handleMenuClick(item)"
>
<div class="user-info-box">
<div class="user-img-box">
<img :src="item.userImg" alt="" />
</div>
<div class="user-info-right">
<div class="user-box-right-nbs">
<div class="user-name-box">
<span class="user-name">{{ item?.username }}</span>
<div class="user-time">
05-19 16:38
</div>
</div>
<div class="user-reply-box">
<span class="user-reply">
{{
item?.messages[
item?.messages.length - 1
]?.content.includes('http')
? '[图片]'
: item.messages[item.messages.length - 1]?.content
}}
</span>
<div class="count">1</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-aside>
<el-container class="el-container-center" v-if="customerInfo.username">
<el-header class="el-header-center">
<p class="name">{{ customerInfo.username }}</p>
<p class="url">US|https://www.ibeautytop.com</p>
</el-header>
<el-main
id="srollId"
ref="srollId"
:style="{
height: '600px',
width: '100%',
border: '1px solid #ccc',
}"
>
<div
class="message-container"
v-for="(message, index) in messages"
:key="index"
:class="getMessageClass(message?.isSent)"
>
<div class="time">{{ message.time }}</div>
<div v-if="message.isSent" class="message-container">
<div class="bubble">
<div
v-if="message?.msgType == 0"
class="message"
v-html="message?.content"
@click.prevent="handleMessageClick($event)"
></div>
<div v-else class="img-wraper">
<el-image
:src="message?.content"
:preview-src-list="[message?.content]"
/>
</div>
</div>
<div class="avatar">
<img
src="../assets/logo.png"
alt="Avatar"
class="avatar-image"
/>
</div>
</div>
<div v-if="!message.isSent" class="message-container">
<div class="avatar">
<img :src="message?.userImg" class="avatar-image" />
</div>
<div class="bubble">
<div
v-if="message?.msgType == 0"
class="message"
v-html="message?.content"
@click.prevent="handleMessageClick($event)"
></div>
<div v-else class="img-wraper">
<el-image
:src="message?.content"
:preview-src-list="[message?.content]"
/>
</div>
</div>
</div>
</div>
</el-main>
<el-footer class="el-footer">
<AutomaticPrompt
@keydown.enter="handleButtonClick"
@updateState="getState"
ref="automaticPromptRef"
></AutomaticPrompt>
<el-button
type="primary"
plain
class="btn-send"
style="width: 50px;"
@click.stop="handleButtonClick"
>
发送
</el-button>
</el-footer>
</el-container>
<el-container class="el-container-center" v-else></el-container>
<el-aside width="400px">
<div>
<el-header class="el-header-right">
<img src="../assets/logo.png" alt="Avatar" />
<div class="name">询价单</div>
</el-header>
<div class="customer-info user-wrapper">
<div class="customer-info-right">
<div class="customer-info-box">
<div class="avatar-box">
<img src="../assets/shop.jpg" alt="Avatar" />
</div>
<div class="avatar-right">
<div class="avatar-right-name">
{{ inquiryInfo.name }}
</div>
<div class="avatar-right-price">
<div>{{ inquiryInfo.price }}</div>
<div>{{ inquiryInfo.unit }}</div>
</div>
<div class="avatar-input-box">
<div>
<el-input-number
class="avatar-number-input"
:min="1"
@change="handleChange"
v-model="ruleForm.count"
:max="10"
/>
</div>
<div class="avatar-input-right">
<div class="change">更换</div>
<div class="delete">删除</div>
</div>
</div>
</div>
</div>
<div class="add">
<img src="../assets/add-line.png" />
<div>添加</div>
</div>
</div>
<div class="form">
<el-form
ref="ruleFormRef"
style="max-width: 600px;"
:model="ruleForm"
:rules="rules"
label-position="top"
label-width="auto"
>
<el-form-item label="客户名称" prop="customerName">
<el-input
v-model="ruleForm.customerName"
type="password"
placeholder="默认展示"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="国家/地区" prop="countriesRegions">
<el-input
v-model="ruleForm.countriesRegions"
type="password"
placeholder="默认展示"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="Email" prop="Email">
<el-input
placeholder="默认展示"
v-model.number="ruleForm.Email"
/>
</el-form-item>
<el-form-item label="WatchApp" prop="WatchApp">
<el-input
placeholder="默认展示"
v-model.number="ruleForm.WatchApp"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
placeholder="默认展示"
v-model.number="ruleForm.remark"
/>
</el-form-item>
<el-form-item
label="备注"
prop="content"
style="margin-bottom: 3px;"
>
<el-input
placeholder="文本内容"
v-model="ruleForm.content"
type="textarea"
:rows="8"
/>
</el-form-item>
<el-button
type="primary"
plain
style="width: 50px;"
@click.stop="sumbitFuleForm(ruleFormRef)"
>
发送
</el-button>
</el-form>
</div>
</div>
</div>
</el-aside>
</el-container>
</div>
</template>
<script lang="ts" setup>
import '../assets/font/iconfont.css'
import { ElMessage } from 'element-plus'
import { ref, onMounted, watch, nextTick, reactive } from 'vue'
import { getShortDate } from '../utils/index'
import { useUserStore } from '../store/modules/user'
import AutomaticPrompt from '../components/AutomaticPrompt.vue'
import ImageViewer from '@luohc92/vue3-image-viewer'
import { sendWebSocket } from '../utils/websocket'
import type { FormInstance } from 'element-plus'
import { initWebSocket } from "../utils/websocket";
import '@luohc92/vue3-image-viewer/dist/style.css'
import { v4 as uuidv4 } from "uuid"
import { getUserList, InquiryAdd, checkMesssages } from '../axios/model/user'
const ruleForm = ref({
count: 1,
})
const connectMsg = () => {
const useUser = useUserStore();
const toIp = `ws://192.168.31.228:8081?type=yk&code=${uuidv4()}&kf=10086`;
console.log(toIp)
useUser.connect();
initWebSocket(toIp);
};
connectMsg();
const ruleFormRef = ref<FormInstance>()
const customerInfo = ref({})
const messages = ref([])
const count = ref(0)
const store = useUserStore()
const userList = ref([])
const automaticPromptRef = ref('')
const inquiryInfo = ref({
price: '20.00',
unit: 'USD',
name: 'Industrial 5V 12V RS232 RS-232 Parallel PCI PC',
})
const rules = reactive({
customerName: [
{
required: true,
message: '请输入客户名称',
trigger: 'blur',
},
],
countriesRegions: [
{
required: true,
message: '请输入国家/地区',
trigger: 'blur',
},
],
Email: [
{
required: true,
message: '请输入Email',
trigger: 'blur',
},
],
WatchApp: [
{
required: true,
message: '请输入WatchApp',
trigger: 'blur',
},
],
remark: [
{
required: true,
message: '请输入描述',
trigger: 'blur',
},
],
content: [
{
required: true,
message: '请输入描述',
trigger: 'blur',
},
],
})
let msg = ''
//进入页面直接发送请求从后端获取热点数据
onMounted(async () => {
count.value = store.count
if (store.userList.length > 0) {
userList.value = store.userList
} else {
getList()
}
setMessage()
})
//设置message
const setMessage = () => {
if (store.customerInfo.username) {
customerInfo.value = store.customerInfo
messages.value = customerInfo.value.messages || []
}
setSrollHeight()
}
//获取列表的数据
const getList = async () => {
let query = {}
try {
const { success, result, message }: any = await getUserList(query)
if (success) {
let list = result.map((item: any) => {
item.messages = []
return item
})
userList.value = list
store.setUserList(list)
if (customerInfo.value.username) handleMenuClick(customerInfo.value)
}
} catch (error) {
console.log(error)
}
}
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const getState = (v: string) => {
msg = v
}
const handleChange = (val: any) => {
store.setCount(val)
}
//监听聊天数据的改变
watch(
userList,
(newVal, oldVal) => {
if (newVal && newVal.length > 0 && customerInfo.value.username) {
const obj =
newVal.find((item: any) => {
return item.username == customerInfo.value.username
}) || {}
messages.value = obj.messages || []
}
},
{ deep: true },
)
//监听聊天框数据的改变
watch(
messages,
(newVal, oldVal) => {
if (newVal?.length > 0) setSrollHeight()
},
{ deep: true },
)
//让聊天滑动窗口滑到底部
const setSrollHeight = () => {
nextTick(() => {
const div = document.getElementById('srollId')
if (div) div.scrollTop = div?.scrollHeight
})
}
//切换聊天人
const handleMenuClick = (item: object) => {
customerInfo.value = item
store.setCustomerInfo(item)
messages.value = item?.messages || []
const data = {
cmd: '19',
type: '1',
fromUserId: item?.username,
group_id: '',
userId: store.userInfo.username,
}
sendWebSocket(data)
getCheckMesssages(data)
}
//发送按钮
const handleButtonClick = () => {
if (!msg) {
return ElMessage({
message: '请输入内容',
type: 'error',
})
}
let data = {
content: msg,
isSent: true,
cmd: '11',
msgType: 0,
chatType: '2',
group_id: '',
time: getShortDate(),
to: customerInfo.value.username,
form: store.userInfo.username,
}
messages.value?.push(data)
sendWebSocket(data)
automaticPromptRef.value.setState('')
}
const handleMessageClick = (event: any) => {
const target = event.target
if (target.tagName === 'A') {
// 点击的是超链接
// 执行相应的操作
if (target.innerHTML === '解决') {
alert('感谢您的使用')
} else if (target.innerHTML === '未解决') {
alert('很抱歉未能解决你的问题')
} else {
handleLinkClick(target.innerHTML)
}
} else if (target.tagName === 'IMG') {
// 点击的图片进行放大操作
ImageViewer({
//切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址
images: [target.valueOf().src],
curIndex: 0,
zIndex: 2000,
showDownload: true,
showThumbnail: true,
handlePosition: 'bottom',
maskBgColor: 'rgba(0,0,0,0.7)',
onClose: () => {
console.log('close')
},
})
}
}
//查看消息将其改为已读
const getCheckMesssages = async (res: any) => {
try {
const { success, result, message }: any = await checkMesssages({
username: res.username,
})
} catch (error) {
console.log(error)
}
}
//提交询价单
const sumbitFuleForm = (formEl: FormInstance | undefined) => {
let query = {}
if (!formEl) return
formEl.validate(async (valid: boolean) => {
if (valid) {
try {
const { success, result, message }: any = await InquiryAdd(
ruleForm.value,
)
if (success) {
ElMessage({
message,
type: 'success',
})
}
} catch (error) {
console.log(error)
}
}
})
}
const handleLinkClick = (msg: string) => {
messages.value.push({ content: msg, isSent: true })
}
//消息框样式动态选择
const getMessageClass = (isSent: boolean) => {
return isSent ? 'message-container-right' : 'message-container-left'
}
//左边按钮操作
const getMenuItem = (item: any) => {
return item.username == customerInfo.value?.username ? 'is-active' : ''
}
</script>
<style lang="scss" scoped>
.el-header-left {
height: 76px;
width: 100%;
border: 1px solid #e6e8ed;
text-align: center;
padding: 16px;
.el-input {
:deep(.el-input__wrapper) {
background: var(--Fill-Grey-Fill-1, #f8f8fa);
}
height: 44px;
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
width: 100%;
.el-input__inner {
font-size: 14px;
}
}
}
.el-container-center {
background: #e6e8ed;
}
.el-header-center {
height: 76px;
width: 100%;
border: 1px solid #e6e8ed;
padding: 16px 24px;
display: flex;
justify-content: space-between;
flex-direction: column;
.name {
color: #010914;
font-family: 'Inter';
font-size: 18px;
font-style: normal;
font-weight: 600;
}
.url {
color: #798494;
}
}
.el-header-right {
height: 76px;
width: 100%;
border: 1px solid #e6e8ed;
display: flex;
align-items: center;
img {
width: 15.42px;
height: 17.92px;
margin-right: 14px;
}
.name {
color: #010914;
font-family: 'PingFang SC';
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px;
}
}
.el-footer {
width: 100%;
border: 1px solid #ccc;
border-top: 0px;
padding: 10px;
height: calc(100vh - 600px - 76px);
position: relative;
.btn-send {
position: absolute;
bottom: 20px;
right: 20px;
}
}
.el-menu-item {
line-height: initial;
padding: 0px !important;
height: 86px;
border-left: 3px solid transparent;
&.is-active {
background: #eff0f1;
border-left: 3px solid #1890ff;
}
}
.user-info-box {
display: flex;
width: 100%;
height: 100%;
padding: 0px 10px;
box-sizing: border-box;
justify-content: space-between;
.user-img-box {
margin-right: 10px;
display: flex;
align-items: center;
img {
width: 40px;
height: 40px;
}
}
.user-info-right {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
width: 100%;
.user-box-right-nbs {
width: 100%;
}
.user-reply-box {
position: relative;
display: flex;
}
.count {
right: 16px;
top: 4px;
position: absolute;
display: flex;
width: var(--Edges-xl, 16px);
height: var(--Edges-xl, 16px);
min-width: var(--Edges-xl, 16px);
padding: var(--Spacing-none, 0);
flex-direction: column;
justify-content: center;
color: #fff;
align-items: center;
border-radius: var(--Radius-full, 1000px);
background: red;
}
.label {
margin-left: 5px;
color: #3875ea;
font-size: 12px;
background: #d8e5ff;
border-radius: 2px;
padding: 1px 5px;
&.pc {
background: rgba(100, 64, 194, 0.16);
color: #6440c2;
}
}
}
.user-name-box {
display: flex;
align-items: center;
justify-content: space-between;
.user-name {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
max-width: 120px;
font-size: 16px;
color: rgba(0, 0, 0, 0.65);
margin-right: 10px;
font-family: 'Inter';
font-style: normal;
font-weight: 600;
}
}
.user-reply {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
color: #798494;
font-feature-settings: 'clig' off, 'liga' off;
text-overflow: ellipsis;
font-family: 'Inter';
margin-top: 3px;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.user-time {
color: #999;
font-size: 12px;
font-style: normal;
font-family: 'Inter';
font-weight: 400;
}
}
.customer-info-right {
.customer-info-box {
display: flex;
.avatar-box {
width: 88px;
height: 88px;
border-radius: 8px;
margin-right: 12px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.avatar-input-box {
display: flex;
align-items: center;
margin-top: 8px;
justify-content: space-between;
.change {
margin-right: 27px;
color: #010914;
text-align: right;
font-family: 'PingFang SC';
font-size: 12px;
font-style: normal;
font-weight: 400;
}
.delete {
color: #ff6600;
text-align: right;
font-family: 'PingFang SC';
font-size: 12px;
font-style: normal;
font-weight: 400;
}
}
.avatar-input-right {
display: flex;
}
.avatar-right {
flex: 1;
.avatar-right-name {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex: 1 0 0;
overflow: hidden;
color: #010914;
font-feature-settings: 'clig' off, 'liga' off;
text-overflow: ellipsis;
font-family: 'Inter';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.avatar-right-price {
display: flex;
color: #010914;
font-feature-settings: 'clig' off, 'liga' off;
font-family: 'Inter';
font-size: 16px;
font-style: normal;
line-height: 24px;
div {
&:nth-of-type(1) {
margin-right: 4px;
font-weight: 600;
}
&:nth-of-type(2) {
font-weight: 400;
}
}
}
}
}
}
.el-input__wrapper {
width: 48px;
}
.user-wrapper {
padding: 16px;
}
.user-info {
padding-top: 15px;
padding-bottom: 10px;
}
.chat-bar {
height: 50px;
width: 100%;
line-height: 50px;
text-align: left;
}
.send-btn {
position: absolute;
right: 0;
bottom: 10px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
margin-top: 10px;
margin-right: 10px;
}
.underline-link {
text-decoration: underline;
}
.message-container {
display: flex;
align-items: center;
margin-bottom: 10px;
position: relative;
}
.message-container .time {
text-align: center;
color: #999;
font-size: 14px;
position: absolute;
width: 100%;
}
.avatar {
margin-left: 10px; /* 修改这里将头像放在消息框的右边 */
}
.avatar-image {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.bubble {
background-color: #e8e8e8;
color: #000;
padding: 10px;
border-radius: 5px;
max-width: 320px;
background: #f5f5f5;
border-radius: 10px;
color: #000;
font-size: 14px;
overflow: hidden;
.img-wraper img {
max-width: 100%;
height: auto;
display: block;
}
}
.message {
text-align: left;
margin: 0;
}
.message-container-right {
justify-content: flex-end;
}
.message-container-left {
justify-content: flex-start;
}
.avatar-number-input :deep(.el-input) {
width: 102px !important;
}
.avatar-number-input {
width: 102px !important;
}
.add {
display: flex;
height: var(--Layout-lg, 32px);
padding: 0 var(--Spacing-lg, 12px) 0 var(--Spacing-md, 8px);
justify-content: center;
align-items: center;
gap: 4px;
border-radius: var(--Radius-xs, 4px);
border: var(--Edges-zero, 1px) solid var(--color-Stroke-Weak, #e6e8ed);
background: var(--color-bg-Program-White, #fff);
width: 72px;
height: 32px;
margin-top: 16px;
img {
width: 20px;
height: 20px;
}
}
.form {
margin-top: 32px;
:deep(.el-form-item__label) {
color: #010914;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: bolder;
line-height: 20px;
}
:deep(.el-input__wrapper) {
padding: 0px;
}
:deep(.el-input__inner) {
padding: 8px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex: 1 0 0;
overflow: hidden;
color: #798494;
font-feature-settings: 'clig' off, 'liga' off;
text-overflow: ellipsis;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
background: #f2f3f5;
}
:deep(.el-textarea__inner) {
background: #f2f3f5;
}
}
</style>
<template>
<div class="common-layout">
<el-container>
<el-container class="el-container-center" v-if="customerInfo.username">
<el-header class="el-header-center">
<p class="name">{{ customerInfo.username }}</p>
<p class="url">US|https://www.ibeautytop.com</p>
</el-header>
<el-main
id="srollId"
ref="srollId"
:style="{
height: '600px',
width: '100%',
border: '1px solid #ccc',
}"
>
<div
class="message-container"
v-for="(message, index) in messages"
:key="index"
:class="getMessageClass(message?.isSent)"
>
<div class="time">{{ message.time }}</div>
<div v-if="message.isSent" class="message-container">
<div class="bubble">
<div
v-if="message?.msgType == 0"
class="message"
v-html="message?.content"
@click.prevent="handleMessageClick($event)"
></div>
<div v-else class="img-wraper">
<el-image
:src="message?.content"
:preview-src-list="[message?.content]"
/>
</div>
</div>
<div class="avatar">
<img
src="../assets/logo.png"
alt="Avatar"
class="avatar-image"
/>
</div>
</div>
<div v-if="!message.isSent" class="message-container">
<div class="avatar">
<img :src="message?.userImg" class="avatar-image" />
</div>
<div class="bubble">
<div
v-if="message?.msgType == 0"
class="message"
v-html="message?.content"
@click.prevent="handleMessageClick($event)"
></div>
<div v-else class="img-wraper">
<el-image
:src="message?.content"
:preview-src-list="[message?.content]"
/>
</div>
</div>
</div>
</div>
</el-main>
<el-footer class="el-footer">
<AutomaticPrompt
@keydown.enter="handleButtonClick"
@updateState="getState"
ref="automaticPromptRef"
></AutomaticPrompt>
<el-button
type="primary"
plain
class="btn-send"
style="width: 50px;"
@click.stop="handleButtonClick"
>
发送
</el-button>
</el-footer>
</el-container>
<el-container class="el-container-center" v-else></el-container>
</el-container>
</div>
</template>
<script lang="ts" setup>
import '../assets/font/iconfont.css'
import { ElMessage } from 'element-plus'
import { ref, onMounted, watch, nextTick, reactive } from 'vue'
import { getShortDate } from '../utils/index'
import { useUserStore } from '../store/modules/user'
import AutomaticPrompt from '../components/AutomaticPrompt.vue'
import ImageViewer from '@luohc92/vue3-image-viewer'
import { sendWebSocket } from '../utils/websocket'
import type { FormInstance } from 'element-plus'
import { initWebSocket } from '../utils/websocket'
import '@luohc92/vue3-image-viewer/dist/style.css'
import { v4 as uuidv4 } from 'uuid'
import { getUserList, InquiryAdd, checkMesssages } from '../axios/model/user'
const ruleForm = ref({
count: 1,
})
const ruleFormRef = ref<FormInstance>()
const customerInfo = ref({})
const messages = ref([])
const count = ref(0)
const store = useUserStore()
const userList = ref([])
const automaticPromptRef = ref('')
const inquiryInfo = ref({
price: '20.00',
unit: 'USD',
name: 'Industrial 5V 12V RS232 RS-232 Parallel PCI PC',
})
const rules = reactive({
customerName: [
{
required: true,
message: '请输入客户名称',
trigger: 'blur',
},
],
countriesRegions: [
{
required: true,
message: '请输入国家/地区',
trigger: 'blur',
},
],
Email: [
{
required: true,
message: '请输入Email',
trigger: 'blur',
},
],
WatchApp: [
{
required: true,
message: '请输入WatchApp',
trigger: 'blur',
},
],
remark: [
{
required: true,
message: '请输入描述',
trigger: 'blur',
},
],
content: [
{
required: true,
message: '请输入描述',
trigger: 'blur',
},
],
})
let msg = ''
//进入页面直接发送请求从后端获取热点数据
onMounted(async () => {
connectMsg().then((res) => {
getHistoryMessage()
setMessage()
})
})
const connectMsg = () => {
return new Promise((resolve, reject) => {
const useUser = useUserStore()
useUser.setUserInfo({
username: '17340570937',
})
const toIp = `ws://192.168.31.228:8081?type=yk&code=${useUser.userInfo.username}&kf=${store.customerInfo.username}`
console.log(toIp)
useUser.connect()
initWebSocket(toIp)
setTimeout(() => {
resolve()
}, 800)
})
}
//获取历史记录
const getHistoryMessage = () => {
const data = {
cmd: '19',
type: '1',
fromUserId: store.customerInfo.username,
group_id: '',
userId: store.userInfo.username,
}
sendWebSocket(data)
getCheckMesssages(data)
}
//设置message
const setMessage = () => {
customerInfo.value = store.customerInfo
messages.value = customerInfo.value.messages || []
}
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const getState = (v: string) => {
msg = v
}
const handleChange = (val: any) => {
store.setCount(val)
}
//监听聊天框数据的改变
watch(
messages,
(newVal, oldVal) => {
if (newVal?.length > 0) setSrollHeight()
},
{ deep: true },
)
//让聊天滑动窗口滑到底部
const setSrollHeight = () => {
nextTick(() => {
const div = document.getElementById('srollId')
if (div) div.scrollTop = div?.scrollHeight
})
}
//发送按钮
const handleButtonClick = () => {
if (!msg) {
return ElMessage({
message: '请输入内容',
type: 'error',
})
}
let data = {
content: msg,
isSent: true,
cmd: '11',
msgType: 0,
chatType: '2',
group_id: '',
time: getShortDate(),
to: customerInfo.value.username,
form: store.userInfo.username,
}
console.log('打印用户发送的内容',data)
messages.value?.push(data)
sendWebSocket(data)
automaticPromptRef.value.setState('')
}
const handleMessageClick = (event: any) => {
const target = event.target
if (target.tagName === 'A') {
// 点击的是超链接
// 执行相应的操作
if (target.innerHTML === '解决') {
alert('感谢您的使用')
} else if (target.innerHTML === '未解决') {
alert('很抱歉未能解决你的问题')
} else {
handleLinkClick(target.innerHTML)
}
} else if (target.tagName === 'IMG') {
// 点击的图片进行放大操作
ImageViewer({
//切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址
images: [target.valueOf().src],
curIndex: 0,
zIndex: 2000,
showDownload: true,
showThumbnail: true,
handlePosition: 'bottom',
maskBgColor: 'rgba(0,0,0,0.7)',
})
}
}
//查看消息将其改为已读
const getCheckMesssages = async (res: any) => {
try {
const { success, result, message }: any = await checkMesssages({
username: res.username,
})
} catch (error) {
console.log(error)
}
}
const handleLinkClick = (msg: string) => {
messages.value.push({ content: msg, isSent: true })
}
//消息框样式动态选择
const getMessageClass = (isSent: boolean) => {
return isSent ? 'message-container-right' : 'message-container-left'
}
</script>
<style lang="scss" scoped>
.el-header-left {
height: 76px;
width: 100%;
border: 1px solid #e6e8ed;
text-align: center;
padding: 16px;
.el-input {
:deep(.el-input__wrapper) {
background: var(--Fill-Grey-Fill-1, #f8f8fa);
}
height: 44px;
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
width: 100%;
.el-input__inner {
font-size: 14px;
}
}
}
.el-container-center {
background: #e6e8ed;
}
.el-header-center {
height: 76px;
width: 100%;
border: 1px solid #e6e8ed;
padding: 16px 24px;
display: flex;
justify-content: space-between;
flex-direction: column;
.name {
color: #010914;
font-family: 'Inter';
font-size: 18px;
font-style: normal;
font-weight: 600;
}
.url {
color: #798494;
}
}
.el-header-right {
height: 76px;
width: 100%;
border: 1px solid #e6e8ed;
display: flex;
align-items: center;
img {
width: 15.42px;
height: 17.92px;
margin-right: 14px;
}
.name {
color: #010914;
font-family: 'PingFang SC';
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px;
}
}
.el-footer {
width: 100%;
border: 1px solid #ccc;
border-top: 0px;
padding: 10px;
height: calc(100vh - 600px - 76px);
position: relative;
.btn-send {
position: absolute;
bottom: 20px;
right: 20px;
}
}
.el-menu-item {
line-height: initial;
padding: 0px !important;
height: 86px;
border-left: 3px solid transparent;
&.is-active {
background: #eff0f1;
border-left: 3px solid #1890ff;
}
}
.user-info-box {
display: flex;
width: 100%;
height: 100%;
padding: 0px 10px;
box-sizing: border-box;
justify-content: space-between;
.user-img-box {
margin-right: 10px;
display: flex;
align-items: center;
img {
width: 40px;
height: 40px;
}
}
.user-info-right {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
width: 100%;
.user-box-right-nbs {
width: 100%;
}
.user-reply-box {
position: relative;
display: flex;
}
.count {
right: 16px;
top: 4px;
position: absolute;
display: flex;
width: var(--Edges-xl, 16px);
height: var(--Edges-xl, 16px);
min-width: var(--Edges-xl, 16px);
padding: var(--Spacing-none, 0);
flex-direction: column;
justify-content: center;
color: #fff;
align-items: center;
border-radius: var(--Radius-full, 1000px);
background: red;
}
.label {
margin-left: 5px;
color: #3875ea;
font-size: 12px;
background: #d8e5ff;
border-radius: 2px;
padding: 1px 5px;
&.pc {
background: rgba(100, 64, 194, 0.16);
color: #6440c2;
}
}
}
.user-name-box {
display: flex;
align-items: center;
justify-content: space-between;
.user-name {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
max-width: 120px;
font-size: 16px;
color: rgba(0, 0, 0, 0.65);
margin-right: 10px;
font-family: 'Inter';
font-style: normal;
font-weight: 600;
}
}
.user-reply {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
color: #798494;
font-feature-settings: 'clig' off, 'liga' off;
text-overflow: ellipsis;
font-family: 'Inter';
margin-top: 3px;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.user-time {
color: #999;
font-size: 12px;
font-style: normal;
font-family: 'Inter';
font-weight: 400;
}
}
.customer-info-right {
.customer-info-box {
display: flex;
.avatar-box {
width: 88px;
height: 88px;
border-radius: 8px;
margin-right: 12px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.avatar-input-box {
display: flex;
align-items: center;
margin-top: 8px;
justify-content: space-between;
.change {
margin-right: 27px;
color: #010914;
text-align: right;
font-family: 'PingFang SC';
font-size: 12px;
font-style: normal;
font-weight: 400;
}
.delete {
color: #ff6600;
text-align: right;
font-family: 'PingFang SC';
font-size: 12px;
font-style: normal;
font-weight: 400;
}
}
.avatar-input-right {
display: flex;
}
.avatar-right {
flex: 1;
.avatar-right-name {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex: 1 0 0;
overflow: hidden;
color: #010914;
font-feature-settings: 'clig' off, 'liga' off;
text-overflow: ellipsis;
font-family: 'Inter';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.avatar-right-price {
display: flex;
color: #010914;
font-feature-settings: 'clig' off, 'liga' off;
font-family: 'Inter';
font-size: 16px;
font-style: normal;
line-height: 24px;
div {
&:nth-of-type(1) {
margin-right: 4px;
font-weight: 600;
}
&:nth-of-type(2) {
font-weight: 400;
}
}
}
}
}
}
.el-input__wrapper {
width: 48px;
}
.user-wrapper {
padding: 16px;
}
.user-info {
padding-top: 15px;
padding-bottom: 10px;
}
.chat-bar {
height: 50px;
width: 100%;
line-height: 50px;
text-align: left;
}
.send-btn {
position: absolute;
right: 0;
bottom: 10px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
margin-top: 10px;
margin-right: 10px;
}
.underline-link {
text-decoration: underline;
}
.message-container {
display: flex;
align-items: center;
margin-bottom: 10px;
position: relative;
}
.message-container .time {
text-align: center;
color: #999;
font-size: 14px;
position: absolute;
width: 100%;
}
.avatar {
margin-left: 10px; /* 修改这里将头像放在消息框的右边 */
}
.avatar-image {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.bubble {
background-color: #e8e8e8;
color: #000;
padding: 10px 14px;
border-radius: 5px;
max-width: 320px;
background: #f5f5f5;
border-radius: 10px;
color: #000;
font-size: 14px;
overflow: hidden;
.img-wraper img {
max-width: 100%;
height: auto;
display: block;
}
}
.message-container-right {
justify-content: flex-end;
}
.message-container-left {
justify-content: flex-start;
}
.avatar-number-input :deep(.el-input) {
width: 102px !important;
}
.avatar-number-input {
width: 102px !important;
}
.add {
display: flex;
height: var(--Layout-lg, 32px);
padding: 0 var(--Spacing-lg, 12px) 0 var(--Spacing-md, 8px);
justify-content: center;
align-items: center;
gap: 4px;
border-radius: var(--Radius-xs, 4px);
border: var(--Edges-zero, 1px) solid var(--color-Stroke-Weak, #e6e8ed);
background: var(--color-bg-Program-White, #fff);
width: 72px;
height: 32px;
margin-top: 16px;
img {
width: 20px;
height: 20px;
}
}
.form {
margin-top: 32px;
:deep(.el-form-item__label) {
color: #010914;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: bolder;
line-height: 20px;
}
:deep(.el-input__wrapper) {
padding: 0px;
}
:deep(.el-input__inner) {
padding: 8px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex: 1 0 0;
overflow: hidden;
color: #798494;
font-feature-settings: 'clig' off, 'liga' off;
text-overflow: ellipsis;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
background: #f2f3f5;
}
:deep(.el-textarea__inner) {
background: #f2f3f5;
}
}
</style>
<template>
<div class="login-box">
<el-form
ref="ruleFormRef"
style="max-width: 450px;"
:model="ruleForm"
:rules="rules"
label-width="auto"
class="form-box"
>
<h2>用户登录</h2>
<el-form-item label="账号" prop="username" size="large">
<el-input
v-model="ruleForm.username"
type="username"
placeholder="请输入账号"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="密码" prop="password" size="large">
<el-input
v-model="ruleForm.password"
type="password"
autocomplete="off"
placeholder="请输入密码"
/>
</el-form-item>
<el-form-item style="margin: 0px;">
<el-button
class="login-submit"
type="primary"
@click="submitForm(ruleFormRef)"
>
登录
</el-button>
<el-button class="login-reset" @click="resetForm(ruleFormRef)">
重置
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import { LoginData } from '../type/Login'
import { reactive, ref } from 'vue'
import { useUserStore } from '../store/modules/user'
import type { FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import { Login } from '../axios/model/user'
export default {
setup() {
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive(new LoginData())
const store = useUserStore()
const router = useRouter()
const rules = reactive({
username: [
{
required: true,
message: '请输入账号',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur',
},
],
})
//重置
const resetForm = (formEl: FormInstance | undefined) => {
ruleForm.username = ''
ruleForm.password = ''
if (!formEl) return
formEl.resetFields()
}
//登录
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid: boolean) => {
if (valid) {
let data = {
username: ruleForm.username,
password: ruleForm.password,
}
Login(data).then(({ success, message, result }: any) => {
if (success) {
router.push('/')
store.setUserInfo({
...result.userInfo,
token: result.token,
})
}
})
} else {
return false
}
})
}
return { ruleForm, ruleFormRef, rules, resetForm, submitForm }
},
}
</script>
<style lang="scss" scoped>
.login-box {
width: 100%;
height: 100%;
padding-top: 1px;
background: url('../assets/login.jpg') no-repeat;
background-size: 100%;
.form-box {
background: #fff;
.el-input__inner {
height: 40px;
}
h2 {
text-align: center;
margin-bottom: 40px;
font-weight: bolder;
font-size: 20px;
}
margin: 0px auto;
margin-top: 250px;
width: 500px;
text-align: center;
border-radius: 20px;
padding: 40px;
box-shadow: 0px 1px 5px 0 rgba(0, 0, 0, 0.3);
.login-submit,
.login-reset {
width: 48%;
height: 40px;
}
}
}
</style>
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
},
// chainWebpack: config => {
// config.plugin('html').use(HtmlWebpackPlugin, [{
// title:'用户端',
// template: 'public/index.html', // 模块文件路径
// }])
// }
})
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论