mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-13 04:33:42 +08:00
merge v4.1.5
This commit is contained in:
@@ -132,18 +132,19 @@ const content =ref(processPrompt(props.data.content))
|
||||
const files = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
// if (!finalTokens.value) {
|
||||
// httpPost("/api/chat/tokens", {text: props.data.content, model: props.data.model}).then(res => {
|
||||
// finalTokens.value = res.data;
|
||||
// }).catch(() => {
|
||||
// })
|
||||
// }
|
||||
processFiles()
|
||||
})
|
||||
|
||||
const processFiles = () => {
|
||||
if (!props.data.content) {
|
||||
return
|
||||
}
|
||||
|
||||
const linkRegex = /(https?:\/\/\S+)/g;
|
||||
const links = props.data.content.match(linkRegex);
|
||||
if (links) {
|
||||
httpPost("/api/upload/list", {urls: links}).then(res => {
|
||||
files.value = res.data
|
||||
files.value = res.data.items
|
||||
|
||||
for (let link of links) {
|
||||
if (isExternalImg(link, files.value)) {
|
||||
@@ -159,8 +160,7 @@ onMounted(() => {
|
||||
|
||||
}
|
||||
content.value = md.render(content.value.trim())
|
||||
})
|
||||
|
||||
}
|
||||
const isExternalImg = (link, files) => {
|
||||
return isImage(link) && !files.find(file => file.url === link)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
<el-radio value="chat">对话样式</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="流式输出:">
|
||||
<el-switch v-model="data.stream" @change="(val) => {store.setChatStream(val)}" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@@ -28,6 +30,7 @@ const store = useSharedStore();
|
||||
|
||||
const data = ref({
|
||||
style: store.chatListStyle,
|
||||
stream: store.chatStream,
|
||||
})
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
|
||||
@@ -60,6 +60,7 @@ const removeFile = (file) => {
|
||||
display flex
|
||||
flex-flow row
|
||||
margin-right 10px
|
||||
max-width 600px
|
||||
position relative
|
||||
|
||||
.el-image {
|
||||
|
||||
@@ -1,57 +1,64 @@
|
||||
<template>
|
||||
<el-container class="file-select-box">
|
||||
<a class="file-upload-img" @click="fetchFiles">
|
||||
<a class="file-upload-img" @click="fetchFiles(1)">
|
||||
<i class="iconfont icon-attachment-st"></i>
|
||||
</a>
|
||||
<el-dialog
|
||||
class="file-list-dialog"
|
||||
v-model="show"
|
||||
:close-on-click-modal="true"
|
||||
:show-close="true"
|
||||
:width="800"
|
||||
title="文件管理"
|
||||
>
|
||||
|
||||
<div class="file-list">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="3">
|
||||
<div class="grid-content">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3"
|
||||
>
|
||||
<el-icon class="avatar-uploader-icon">
|
||||
<Plus/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="3" v-for="file in fileList" :key="file.url">
|
||||
<div class="grid-content">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="file.name"
|
||||
placement="top">
|
||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="opt">
|
||||
<el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
|
||||
<el-scrollbar ref="scrollbarRef" max-height="80vh" style="height: 100%;" @scroll="onScroll">
|
||||
<div class="file-list">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="3">
|
||||
<div class="grid-content">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3"
|
||||
>
|
||||
<el-icon class="avatar-uploader-icon">
|
||||
<Plus/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="3" v-for="file in fileData.items" :key="file.url">
|
||||
<div class="grid-content">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="file.name"
|
||||
placement="top">
|
||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="opt">
|
||||
<el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row justify="center" v-if="!fileData.isLastPage" @click="fetchFiles(fileData.page)">
|
||||
<el-link>加载更多</el-link>
|
||||
|
||||
</el-row>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
</el-dialog>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {reactive, ref} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {Delete, Plus} from "@element-plus/icons-vue";
|
||||
@@ -64,15 +71,45 @@ const props = defineProps({
|
||||
const emits = defineEmits(['selected']);
|
||||
const show = ref(false)
|
||||
const fileList = ref([])
|
||||
const scrollbarRef = ref(null)
|
||||
const fileData = reactive({
|
||||
items:[],
|
||||
page: 1,
|
||||
isLastPage: true,
|
||||
})
|
||||
|
||||
const fetchFiles = () => {
|
||||
show.value = true
|
||||
httpPost("/api/upload/list").then(res => {
|
||||
fileList.value = res.data
|
||||
const fetchFiles = (pageNo) => {
|
||||
if(pageNo === 1) show.value = true
|
||||
httpPost("/api/upload/list", { page: pageNo || 1, page_size: 30 }).then(res => {
|
||||
const { items, page, total_page } = res.data
|
||||
|
||||
if(page === 1){
|
||||
fileData.items = items
|
||||
}else{
|
||||
fileData.items = [...fileData.items, ...items]
|
||||
}
|
||||
|
||||
fileData.isLastPage = (page === total_page)
|
||||
|
||||
if(!fileData.isLastPage){
|
||||
fileData.page = page + 1
|
||||
}
|
||||
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
// el-scrollbar 滚动回调
|
||||
const onScroll = (options) => {
|
||||
const wrapRef = scrollbarRef.value.wrapRef
|
||||
scrollbarRef.value.moveY = wrapRef.scrollTop * 100 / wrapRef.clientHeight
|
||||
scrollbarRef.value.moveX = wrapRef.scrollLeft * 100 / wrapRef.clientWidth
|
||||
const poor = wrapRef.scrollHeight - wrapRef.clientHeight
|
||||
// 判断滚动到底部 自动加载数据
|
||||
if (options.scrollTop + 2 >= poor && !fileData.isLastPage) {
|
||||
fetchFiles(fileData.page)
|
||||
}
|
||||
}
|
||||
|
||||
const afterRead = (file) => {
|
||||
const formData = new FormData();
|
||||
@@ -92,6 +129,7 @@ const removeFile = (file) => {
|
||||
return v1.id === v2.id
|
||||
})
|
||||
ElMessage.success("文件删除成功!")
|
||||
fetchFiles(1)
|
||||
}).catch((e) => {
|
||||
ElMessage.error('文件删除失败:' + e.message)
|
||||
})
|
||||
@@ -120,9 +158,10 @@ const insertURL = (file) => {
|
||||
|
||||
.el-dialog__body {
|
||||
//padding 0
|
||||
overflow hidden
|
||||
|
||||
.file-list {
|
||||
|
||||
margin-right 10px
|
||||
.grid-content {
|
||||
margin-bottom 10px
|
||||
position relative
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="list-box" ref="containerRef">
|
||||
<div class="item__list-box" ref="containerRef">
|
||||
<el-row :gutter="gap">
|
||||
<el-col v-for="item in items" :key="item.id" :span="span" :style="{marginBottom:gap+'px'} ">
|
||||
<slot :item="item"></slot>
|
||||
@@ -54,9 +54,10 @@ const calcSpan = () => {
|
||||
window.onresize = () => calcSpan()
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
<style lang="stylus">
|
||||
|
||||
.list-box {
|
||||
.item__list-box {
|
||||
width 100%
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -251,6 +251,7 @@ import Captcha from "@/components/Captcha.vue";
|
||||
import ResetPass from "@/components/ResetPass.vue";
|
||||
import {setRoute} from "@/store/system";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
@@ -263,8 +264,8 @@ watch(() => props.show, (newValue) => {
|
||||
|
||||
const login = ref(true)
|
||||
const data = ref({
|
||||
username: "",
|
||||
password: "",
|
||||
username: process.env.VUE_APP_USER,
|
||||
password: process.env.VUE_APP_PASS,
|
||||
mobile: "",
|
||||
email: "",
|
||||
repass: "",
|
||||
@@ -285,6 +286,9 @@ const action = ref("login")
|
||||
const enableVerify = ref(false)
|
||||
const showResetPass = ref(false)
|
||||
const router = useRouter()
|
||||
const store = useSharedStore()
|
||||
// 是否需要验证码,输入一次密码错之后就要验证码
|
||||
const needVerify = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
const returnURL = `${location.protocol}//${location.host}/login/callback?action=login`
|
||||
@@ -297,17 +301,17 @@ onMounted(() => {
|
||||
getSystemInfo().then(res => {
|
||||
if (res.data) {
|
||||
const registerWays = res.data['register_ways']
|
||||
if (arrayContains(registerWays, "mobile")) {
|
||||
enableMobile.value = true
|
||||
activeName.value = activeName.value === "" ? "mobile" : activeName.value
|
||||
if (arrayContains(registerWays, "username")) {
|
||||
enableUser.value = true
|
||||
activeName.value = 'username'
|
||||
}
|
||||
if (arrayContains(registerWays, "email")) {
|
||||
enableEmail.value = true
|
||||
activeName.value = activeName.value === "" ? "email" : activeName.value
|
||||
activeName.value = 'email'
|
||||
}
|
||||
if (arrayContains(registerWays, "username")) {
|
||||
enableUser.value = true
|
||||
activeName.value = activeName.value === "" ? "username" : activeName.value
|
||||
if (arrayContains(registerWays, "mobile")) {
|
||||
enableMobile.value = true
|
||||
activeName.value = 'mobile'
|
||||
}
|
||||
// 是否启用注册
|
||||
enableRegister.value = res.data['enabled_register']
|
||||
@@ -338,7 +342,7 @@ const submitLogin = () => {
|
||||
if (data.value.password === '') {
|
||||
return ElMessage.error('请输入密码');
|
||||
}
|
||||
if (enableVerify.value) {
|
||||
if (enableVerify.value && needVerify.value) {
|
||||
captchaRef.value.loadCaptcha()
|
||||
action.value = "login"
|
||||
} else {
|
||||
@@ -352,11 +356,14 @@ const doLogin = (verifyData) => {
|
||||
data.value.x = verifyData.x
|
||||
httpPost('/api/user/login', data.value).then((res) => {
|
||||
setUserToken(res.data.token)
|
||||
store.setIsLogin(true)
|
||||
ElMessage.success("登录成功!")
|
||||
emits("hide")
|
||||
emits('success')
|
||||
needVerify.value = false
|
||||
}).catch((e) => {
|
||||
ElMessage.error('登录失败,' + e.message)
|
||||
needVerify.value = true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -384,7 +391,7 @@ const submitRegister = () => {
|
||||
if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') {
|
||||
return ElMessage.error('请输入验证码');
|
||||
}
|
||||
if (enableVerify.value) {
|
||||
if (enableVerify.value && activeName.value === 'username') {
|
||||
captchaRef.value.loadCaptcha()
|
||||
action.value = "register"
|
||||
} else {
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
<template>
|
||||
<div class="running-job-list">
|
||||
<div class="running-job-box" v-if="list.length > 0">
|
||||
<div class="job-item" v-for="item in list">
|
||||
<div class="job-item" v-for="item in list" :key="item.id">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
<el-image v-if="item.img_url" :src="item['img_url']" fit="cover" loading="lazy">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="progress" v-if="item.progress > 0">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
@@ -39,11 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {CircleCloseFilled, Picture} from "@element-plus/icons-vue";
|
||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
|
||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="user-bill" v-loading="loading">
|
||||
<div class="user-bill" v-loading="loading" element-loading-background="rgba(255,255,255,.3)">
|
||||
<el-row v-if="items.length > 0">
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||
style="--el-table-border-color:#373C47;
|
||||
@@ -22,7 +22,8 @@
|
||||
<span>{{ scope.row.remark?.power }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pay_way" label="支付方式"/>
|
||||
<el-table-column prop="pay_method" label="支付渠道"/>
|
||||
<el-table-column prop="pay_name" label="支付名称"/>
|
||||
<el-table-column label="支付时间">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row['pay_time']">{{ dateFormat(scope.row['pay_time']) }}</span>
|
||||
|
||||
@@ -52,13 +52,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {computed, onMounted, ref} from 'vue';
|
||||
import {onMounted, ref, watch} from 'vue';
|
||||
import {getMenuItems, useSidebarStore} from '@/store/sidebar';
|
||||
import {useRouter} from "vue-router";
|
||||
import {ArrowDown, ArrowRight, Expand, Fold, Moon, Sunny} from "@element-plus/icons-vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {removeAdminToken} from "@/store/session";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
|
||||
const version = ref(process.env.VUE_APP_VERSION)
|
||||
const avatar = ref('/images/user-info.jpg')
|
||||
@@ -66,24 +67,18 @@ const sidebar = useSidebarStore();
|
||||
const router = useRouter();
|
||||
const breadcrumb = ref([])
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
theme: String,
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
return props.theme
|
||||
const store = useSharedStore()
|
||||
const dark = ref(store.adminTheme === 'dark')
|
||||
const theme = ref(store.adminTheme)
|
||||
watch(() => store.adminTheme, (val) => {
|
||||
theme.value = val
|
||||
})
|
||||
const dark = ref(props.theme === 'dark' ? true : false)
|
||||
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['changeTheme']);
|
||||
const changeTheme = () => {
|
||||
emits('changeTheme', dark.value)
|
||||
store.setAdminTheme(dark.value ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
router.afterEach((to) => {
|
||||
initBreadCrumb(to.path)
|
||||
});
|
||||
|
||||
|
||||
@@ -52,11 +52,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, ref} from 'vue';
|
||||
import {computed, ref, watch} from 'vue';
|
||||
import {setMenuItems, useSidebarStore} from '@/store/sidebar';
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
|
||||
const title = ref('')
|
||||
const logo = ref('')
|
||||
@@ -68,16 +69,11 @@ httpGet('/api/admin/config/get?key=system').then(res => {
|
||||
}).catch(e => {
|
||||
ElMessage.error("加载系统配置失败: " + e.message)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
theme: String,
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
return props.theme
|
||||
const store = useSharedStore()
|
||||
const theme = ref(store.adminTheme)
|
||||
watch(() => store.adminTheme, (val) => {
|
||||
theme.value = val
|
||||
})
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: 'home',
|
||||
@@ -90,12 +86,22 @@ const items = [
|
||||
index: '/admin/user',
|
||||
title: '用户管理',
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'menu',
|
||||
index: '/admin/app',
|
||||
index: '1',
|
||||
title: '应用管理',
|
||||
subs: [
|
||||
{
|
||||
index: '/admin/app',
|
||||
title: '应用列表',
|
||||
},
|
||||
{
|
||||
index: '/admin/app/type',
|
||||
title: '应用分类',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'api-key',
|
||||
index: '/admin/apikey',
|
||||
|
||||
@@ -38,17 +38,14 @@ import {onBeforeRouteUpdate, useRoute, useRouter} from 'vue-router';
|
||||
import {ArrowDown, Close} from "@element-plus/icons-vue";
|
||||
import {checkAdminSession} from "@/store/cache";
|
||||
import {ElMessageBox} from "element-plus";
|
||||
import {computed} from "vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import {ref, watch} from "vue";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
theme: String,
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
return props.theme
|
||||
const store = useSharedStore()
|
||||
const theme = ref(store.adminTheme)
|
||||
watch(() => store.adminTheme, (val) => {
|
||||
theme.value = val
|
||||
})
|
||||
|
||||
const router = useRouter();
|
||||
checkAdminSession().catch(() => {
|
||||
ElMessageBox({
|
||||
|
||||
@@ -47,6 +47,7 @@ watch(() => props.value, (newValue) => {
|
||||
model.value = newValue
|
||||
})
|
||||
const model = ref(props.value)
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['update:value']);
|
||||
const onInput = (value) => {
|
||||
emits('update:value',value)
|
||||
|
||||
79
web/src/components/ui/ItemsInput.vue
Normal file
79
web/src/components/ui/ItemsInput.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<!-- 多项目输入组件 -->
|
||||
<div class="items-input-box">
|
||||
<el-tag
|
||||
v-for="tag in tags"
|
||||
:key="tag"
|
||||
closable
|
||||
:disable-transitions="false"
|
||||
@close="handleClose(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
<el-input
|
||||
v-if="inputVisible"
|
||||
ref="InputRef"
|
||||
v-model="inputValue"
|
||||
class="w-20"
|
||||
size="small"
|
||||
@keyup.enter="handleInputConfirm"
|
||||
@blur="handleInputConfirm"
|
||||
/>
|
||||
<el-button v-else class="button-new-tag" size="small" @click="showInput">
|
||||
+ 新增
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import {nextTick, ref, watch} from "vue";
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
value : {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['update:value']);
|
||||
const tags = ref(props.value)
|
||||
const inputValue = ref('')
|
||||
const inputVisible = ref(false)
|
||||
const InputRef = ref(null)
|
||||
|
||||
watch(() => props.value, (newValue) => {
|
||||
tags.value = newValue
|
||||
})
|
||||
|
||||
const handleClose = (tag) => {
|
||||
tags.value.splice(tags.value.indexOf(tag), 1)
|
||||
}
|
||||
|
||||
const showInput = () => {
|
||||
inputVisible.value = true
|
||||
nextTick(() => {
|
||||
InputRef.value?.input?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue.value) {
|
||||
tags.value.push(inputValue.value)
|
||||
}
|
||||
inputVisible.value = false
|
||||
inputValue.value = ''
|
||||
emits('update:value', tags.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
.items-input-box {
|
||||
display flex
|
||||
|
||||
.el-tag {
|
||||
display flex
|
||||
margin-right 6px
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user