添加会话授权支持

This commit is contained in:
RockYang
2023-03-21 18:12:24 +08:00
parent 3bb6814493
commit 005d219a8c
16 changed files with 403 additions and 73 deletions

View File

@@ -1 +1,2 @@
VUE_APP_WS_HOST=ws://172.22.11.200:5678
VUE_APP_API_HOST=172.22.11.200:5678
VUE_APP_API_SECURE=false

View File

@@ -1 +1,2 @@
VUE_APP_WS_HOST=ws://127.0.0.1:5678
VUE_APP_API_SECURE=false

112
web/package-lock.json generated
View File

@@ -14,6 +14,7 @@
"element-plus": "^2.1.11",
"good-storage": "^1.1.1",
"json-bigint": "^1.0.0",
"qs": "^6.11.1",
"vue": "^3.2.13",
"vue-router": "^4.0.15"
},
@@ -3624,6 +3625,18 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"dev": true,
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/bonjour-service": {
"version": "1.0.12",
"resolved": "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.0.12.tgz",
@@ -3712,7 +3725,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -5728,6 +5740,18 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"node_modules/express/node_modules/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"dev": true,
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -6013,8 +6037,7 @@
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/functional-red-black-tree": {
"version": "1.0.1",
@@ -6044,7 +6067,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -6157,7 +6179,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
@@ -6187,7 +6208,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -7682,6 +7702,14 @@
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
@@ -8747,12 +8775,17 @@
}
},
"node_modules/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"dev": true,
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/queue-microtask": {
@@ -9308,6 +9341,19 @@
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -13832,6 +13878,12 @@
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"dev": true
}
}
},
@@ -13911,7 +13963,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -15514,6 +15565,12 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"dev": true
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -15745,8 +15802,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"functional-red-black-tree": {
"version": "1.0.1",
@@ -15770,7 +15826,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -15865,7 +15920,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@@ -15888,8 +15942,7 @@
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"hash-sum": {
"version": "2.0.0",
@@ -17079,6 +17132,11 @@
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true
},
"object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
@@ -17844,10 +17902,12 @@
"dev": true
},
"qs": {
"version": "6.9.7",
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"dev": true
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
"requires": {
"side-channel": "^1.0.4"
}
},
"queue-microtask": {
"version": "1.2.3",
@@ -18317,6 +18377,16 @@
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",

View File

@@ -14,6 +14,7 @@
"element-plus": "^2.1.11",
"good-storage": "^1.1.1",
"json-bigint": "^1.0.0",
"qs": "^6.11.1",
"vue": "^3.2.13",
"vue-router": "^4.0.15"
},

View File

@@ -1,3 +1,3 @@
/**
* actions for chat page
*/
*/

View File

@@ -24,10 +24,6 @@ export default defineComponent({
icon: {
type: String,
default: 'images/gpt-icon.png',
},
cursor: {
type: Boolean,
default: true
}
},
data() {

View File

@@ -3,15 +3,17 @@ import {createApp} from 'vue'
import ElementPlus from "element-plus"
import "element-plus/dist/index.css"
import App from './App.vue'
import Home from './views/Chat.vue'
import Chat from './views/Chat.vue'
import NotFound from './views/404.vue'
import './utils/prototype'
import "./assets/css/bootstrap.min.css"
import {Global} from "@/utils/storage";
Global['Chat'] = Chat
const routes = [
{
name: 'home', path: '/', component: Home, meta: {
name: 'home', path: '/', component: Chat, meta: {
title: 'ChatGPT-Console'
}
},

54
web/src/utils/http.js Normal file
View File

@@ -0,0 +1,54 @@
import axios from 'axios'
import {getSessionId} from "@/utils/storage";
axios.defaults.timeout = 5000
axios.defaults.baseURL = process.env.VUE_APP_API_SECURE === true ? 'https://' + process.env.VUE_APP_API_HOST : 'http://' + process.env.VUE_APP_API_HOST
axios.defaults.withCredentials = true
axios.defaults.headers.post['Content-Type'] = 'application/json'
// HTTP拦截器
axios.interceptors.request.use(
config => {
// set token
config.headers['ChatGPT-Token'] = getSessionId();
return config
}, error => {
return Promise.reject(error)
})
axios.interceptors.response.use(
response => {
let data = response.data;
if (data.code === 0) {
return response
} else {
return Promise.reject(response.data)
}
}, error => {
return Promise.reject(error)
})
// send a http get request
export function httpGet(url, params = {}) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
}).then(response => {
resolve(response.data)
}).catch(err => {
reject(err)
})
})
}
// send a http post request
export function httpPost(url, data = {}, options = {}) {
return new Promise((resolve, reject) => {
axios.post(url, data, options).then(response => {
resolve(response.data)
}).catch(err => {
reject(err)
})
})
}

16
web/src/utils/storage.js Normal file
View File

@@ -0,0 +1,16 @@
/* eslint-disable no-constant-condition */
/**
* storage handler
*/
import Storage from 'good-storage'
const SessionIdKey = 'ChatGPT_SESSION_ID';
export const Global = {}
export function getSessionId() {
return Storage.get(SessionIdKey)
}
export function setSessionId(value) {
Storage.set(SessionIdKey, value)
}

View File

@@ -1,6 +1,11 @@
<template>
<div class="body">
<div class="body" v-loading="loading">
<div id="container">
<div class="tool-box">
<el-image style="width: 24px; height: 24px" :src="logo"/>
<el-button round>欢迎来到人工智能时代</el-button>
</div>
<div class="chat-box" :style="{height: chatBoxHeight+'px'}">
<div v-for="chat in chatData" :key="chat.id">
<chat-prompt
@@ -9,7 +14,6 @@
:content="chat.content"/>
<chat-reply v-else-if="chat.type==='reply'"
:icon="chat.icon"
:cursor="chat.cursor"
:content="chat.content"/>
</div>
@@ -25,14 +29,14 @@
v-on:focus="focus"
autofocus
type="textarea"
placeholder="Input any thing here..."
placeholder="开始你的提问"
/>
</div>
<div class="btn-container">
<el-row>
<el-button type="success" class="send" :disabled="sending" v-on:click="sendMessage">发送</el-button>
<el-button type="info" class="config" circle @click="showDialog = true">
<el-button type="info" class="config" circle @click="showConnectDialog = true">
<el-icon>
<Tools/>
</el-icon>
@@ -44,7 +48,28 @@
</div><!-- end container -->
<config-dialog v-model:show="showDialog"></config-dialog>
<config-dialog v-model:show="showConnectDialog"></config-dialog>
<div class="token-dialog">
<el-dialog
v-model="showLoginDialog"
:show-close="false"
:close-on-click-modal="false"
title="请输入口令继续访问"
>
<el-row>
<el-input v-model="token" placeholder="在此输入口令">
<template #prefix>
<el-icon class="el-input__icon">
<Lock/>
</el-icon>
</template>
</el-input>
<el-button type="primary" @click="submitToken">提交</el-button>
</el-row>
</el-dialog>
</div>
</div>
</template>
@@ -54,34 +79,63 @@ import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import {randString} from "@/utils/libs";
import {ElMessage, ElMessageBox} from 'element-plus'
import {Tools} from '@element-plus/icons-vue'
import {Tools, Lock} from '@element-plus/icons-vue'
import ConfigDialog from '@/components/ConfigDialog.vue'
import {httpPost} from "@/utils/http";
import {getSessionId, setSessionId} from "@/utils/storage";
export default defineComponent({
name: "XChat",
components: {ChatPrompt, ChatReply, Tools, ConfigDialog},
components: {ChatPrompt, ChatReply, Tools, Lock, ConfigDialog},
data() {
return {
title: "ChatGPT 控制台",
title: 'ChatGPT 控制台',
logo: 'images/logo.png',
chatData: [],
inputValue: '',
chatBoxHeight: 0,
showDialog: false,
showConnectDialog: false,
showLoginDialog: false,
token: '',
connectingMessageBox: null,
socket: null,
sending: false
toolBoxHeight: 61 + 42,
sending: false,
loading: false
}
},
computed: {},
mounted: function () {
nextTick(() => {
this.chatBoxHeight = window.innerHeight - 61;
this.chatBoxHeight = window.innerHeight - this.toolBoxHeight;
})
this.connect();
// 获取会话
httpPost("/api/session/get").then(() => {
this.connect();
}).catch(() => {
this.showLoginDialog = true;
})
for (let i = 0; i < 10; i++) {
this.chatData.push({
type: "prompt",
id: randString(32),
icon: 'images/user-icon.png',
content: "孙悟空为什么可以把金棍棒放进耳朵?",
});
this.chatData.push({
type: "reply",
id: randString(32),
icon: 'images/gpt-icon.png',
content: "孙悟空是中国神话中的人物,传说中他可以把金箍棒放进耳朵里,这是一种超自然能力,无法用现代科学解释。这种能力可能是象征孙悟空超人力量的古代文化传说。",
});
}
window.addEventListener("resize", () => {
this.chatBoxHeight = window.innerHeight - this.toolBoxHeight;
});
},
methods: {
@@ -91,7 +145,8 @@ export default defineComponent({
}
// 初始化 WebSocket 对象
const socket = new WebSocket(process.env.VUE_APP_WS_HOST + '/api/chat');
const token = getSessionId();
const socket = new WebSocket('ws://' + process.env.VUE_APP_API_HOST + '/api/chat', [token]);
socket.addEventListener('open', () => {
ElMessage.success('创建会话成功!');
@@ -122,6 +177,9 @@ export default defineComponent({
let content = data.content;
// 替换换行符
if (content.indexOf("\n\n") >= 0) {
if (this.chatData[this.chatData.length - 1]["content"].length === 0) {
return
}
content = content.replace("\n\n", "<br />");
}
this.chatData[this.chatData.length - 1]["content"] += content;
@@ -182,7 +240,6 @@ export default defineComponent({
content: this.inputValue
});
// TODO: 使用 websocket 提交数据到后端
this.sending = true;
this.socket.send(this.inputValue);
this.$refs["text-input"].blur();
@@ -199,6 +256,26 @@ export default defineComponent({
}, 200)
},
// 提交 Token
submitToken: function () {
this.showLoginDialog = false;
this.loading = true
// 获取会话
httpPost("/api/login", {
token: this.token
}).then((res) => {
setSessionId(res.data)
this.connect();
this.loading = false;
}).catch(() => {
ElMessage.error("口令错误");
this.token = '';
this.showLoginDialog = true;
this.loading = false;
})
}
},
})
@@ -211,7 +288,7 @@ export default defineComponent({
.body {
background-color: rgba(247, 247, 248, 1);
display flex;
justify-content center;
//justify-content center;
align-items flex-start;
height 100%;
@@ -219,13 +296,24 @@ export default defineComponent({
overflow auto;
width 100%;
.tool-box {
padding-top 10px;
display flex;
justify-content center;
align-items center;
.el-image {
margin-right 5px;
}
}
.chat-box {
// 变量定义
--content-font-size: 16px;
--content-color: #374151;
font-family 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
padding: 20px 10px;
padding: 0 10px 10px 10px;
.chat-line {
padding 10px;
@@ -304,10 +392,27 @@ export default defineComponent({
}
.el-message {
width 90%;
min-width: 300px;
min-width: 100px;
max-width 600px;
}
.token-dialog {
.el-dialog {
--el-dialog-width 90%;
max-width 400px;
.el-dialog__body {
padding 10px 10px 20px 10px;
}
.el-row {
flex-wrap nowrap
button {
margin-left 5px;
}
}
}
}
</style>