|
|
|
|
@@ -1,50 +1,120 @@
|
|
|
|
|
<template>
|
|
|
|
|
<el-dialog v-model="isVisible" width="480" align-center class="dark-dialog">
|
|
|
|
|
<template #title>
|
|
|
|
|
<span>添加渠道</span>
|
|
|
|
|
</template>
|
|
|
|
|
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="pl-4 pr-4 pt-4 dark-form">
|
|
|
|
|
<el-form-item prop="channelName">
|
|
|
|
|
<template #label>
|
|
|
|
|
<span>渠道名称</span>
|
|
|
|
|
</template>
|
|
|
|
|
<el-input v-model="form.channelName" placeholder="请输入渠道名称" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item prop="channelUrl">
|
|
|
|
|
<template #label>
|
|
|
|
|
<span>渠道链接</span>
|
|
|
|
|
</template>
|
|
|
|
|
<el-input v-model="form.channelUrl" placeholder="请输入渠道链接" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
<el-button class="cancel-btn" @click="cancel">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="confirm">确认</el-button>
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="isVisible"
|
|
|
|
|
width="560"
|
|
|
|
|
align-center
|
|
|
|
|
class="custom-script-dialog"
|
|
|
|
|
:show-close="false"
|
|
|
|
|
@closed="handleClosed"
|
|
|
|
|
>
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="sticky top-0 z-10 bg-[#F4F3EB] dark:bg-[#1f1f22] flex justify-between items-start">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="text-[20px] font-serif text-[#171717] dark:text-[#f3f4f6] font-normal tracking-tight">
|
|
|
|
|
关联渠道
|
|
|
|
|
</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<button @click="close" class="text-[#99A0AE] dark:text-gray-500 hover:text-[#171717] dark:hover:text-[#f3f4f6] transition-colors mt-[4px]">
|
|
|
|
|
<el-icon class="text-[20px] cursor-pointer"><Close /></el-icon>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div class="px-[24px] pb-[24px] pt-[8px] space-y-5">
|
|
|
|
|
<!-- Search -->
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
<div class="text-[14px] text-[#171717]/80 dark:text-[#f3f4f6]/80 font-bold mb-2">搜索添加渠道</div>
|
|
|
|
|
<el-autocomplete
|
|
|
|
|
v-if="channelStore.availableChannels.length"
|
|
|
|
|
v-model="searchQuery"
|
|
|
|
|
:fetch-suggestions="querySearch"
|
|
|
|
|
clearable
|
|
|
|
|
placeholder="输入渠道名称或链接"
|
|
|
|
|
class="w-full"
|
|
|
|
|
@select="handleSelect"
|
|
|
|
|
>
|
|
|
|
|
<template #default="{ item }">
|
|
|
|
|
<div class="flex flex-col py-1">
|
|
|
|
|
<span class="text-[13px] font-medium text-[#171717] dark:text-[#f3f4f6]">{{ item.channelName }}</span>
|
|
|
|
|
<span class="text-[12px] text-[#99A0AE] dark:text-gray-500 truncate">{{ item.channelUrl }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-autocomplete>
|
|
|
|
|
<div v-else class="text-[13px] text-[#99A0AE] dark:text-gray-500 py-2">
|
|
|
|
|
暂无可用渠道
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Selected list -->
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
<div class="text-[14px] text-[#171717]/80 dark:text-[#f3f4f6]/80 font-bold mb-2">已选渠道</div>
|
|
|
|
|
<div
|
|
|
|
|
v-if="localSelected.length > 0"
|
|
|
|
|
class="space-y-2 max-h-[240px] overflow-y-auto pr-1"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
v-for="item in localSelected"
|
|
|
|
|
:key="item.id"
|
|
|
|
|
class="flex items-center justify-between gap-3 bg-[#E8E6DE]/50 dark:bg-[#222225] p-3 rounded-xl border border-black/5 dark:border-[#2a2a2d]"
|
|
|
|
|
>
|
|
|
|
|
<div class="min-w-0 flex-1">
|
|
|
|
|
<div class="text-[13px] font-medium text-[#171717] dark:text-[#f3f4f6] truncate">
|
|
|
|
|
{{ item.channelName }}
|
|
|
|
|
</div>
|
|
|
|
|
<el-tooltip :content="item.channelUrl" placement="top" :show-after="300">
|
|
|
|
|
<div class="text-[12px] text-[#99A0AE] dark:text-gray-500 truncate cursor-default">
|
|
|
|
|
{{ item.channelUrl }}
|
|
|
|
|
</div>
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
class="shrink-0 text-[#99A0AE] dark:text-gray-500 hover:text-red-500 dark:hover:text-red-400 transition-colors"
|
|
|
|
|
@click="removeItem(item.id)"
|
|
|
|
|
>
|
|
|
|
|
<el-icon class="text-[18px]"><Delete /></el-icon>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="text-[13px] text-[#99A0AE] dark:text-gray-500 py-4 text-center bg-[#E8E6DE]/30 dark:bg-[#222225]/50 rounded-xl border border-dashed border-black/5 dark:border-[#2a2a2d]">
|
|
|
|
|
未选择任何渠道
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Actions -->
|
|
|
|
|
<div class="flex justify-end gap-3 pt-2">
|
|
|
|
|
<el-button
|
|
|
|
|
@click="cancel"
|
|
|
|
|
class="!rounded-full !px-6 !h-[40px] !text-[13px] !font-semibold cancel-btn"
|
|
|
|
|
>
|
|
|
|
|
取消
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
@click="confirm"
|
|
|
|
|
class="!rounded-full !px-6 !h-[40px] !text-[13px] !font-semibold"
|
|
|
|
|
>
|
|
|
|
|
确认
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
import { Close, Delete } from '@element-plus/icons-vue'
|
|
|
|
|
import { useChannelStore, type ChannelItem } from '@stores/channel'
|
|
|
|
|
|
|
|
|
|
const channelStore = useChannelStore()
|
|
|
|
|
|
|
|
|
|
const isVisible = ref(false)
|
|
|
|
|
const formRef = ref()
|
|
|
|
|
const form = ref({
|
|
|
|
|
channelName: '',
|
|
|
|
|
channelUrl: ''
|
|
|
|
|
})
|
|
|
|
|
const rules = ref({
|
|
|
|
|
channelName: [
|
|
|
|
|
{ required: true, message: '请输入渠道名称', trigger: 'blur' },
|
|
|
|
|
],
|
|
|
|
|
channelUrl: [
|
|
|
|
|
{ required: true, message: '请输入渠道链接', trigger: 'blur' },
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
const searchQuery = ref('')
|
|
|
|
|
const localSelected = ref<ChannelItem[]>([])
|
|
|
|
|
|
|
|
|
|
const open = () => {
|
|
|
|
|
const open = async () => {
|
|
|
|
|
await channelStore.loadSelectedChannels()
|
|
|
|
|
localSelected.value = channelStore.selectedChannels.map((item) => ({ ...item }))
|
|
|
|
|
searchQuery.value = ''
|
|
|
|
|
isVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -53,24 +123,46 @@ const close = () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reset = () => {
|
|
|
|
|
form.value.channelName = ''
|
|
|
|
|
form.value.channelUrl = ''
|
|
|
|
|
formRef.value?.resetFields()
|
|
|
|
|
searchQuery.value = ''
|
|
|
|
|
localSelected.value = []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleClosed = () => {
|
|
|
|
|
reset()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cancel = () => {
|
|
|
|
|
close()
|
|
|
|
|
reset()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const confirm = () => {
|
|
|
|
|
formRef.value.validate((valid: boolean) => {
|
|
|
|
|
if (!valid) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
close()
|
|
|
|
|
reset()
|
|
|
|
|
})
|
|
|
|
|
const confirm = async () => {
|
|
|
|
|
channelStore.setSelectedChannels(localSelected.value.map((item) => ({ ...item })))
|
|
|
|
|
await channelStore.saveSelectedChannels()
|
|
|
|
|
close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const querySearch = (queryString: string, cb: (results: ChannelItem[]) => void) => {
|
|
|
|
|
const list = channelStore.availableChannels
|
|
|
|
|
const query = queryString.trim().toLowerCase()
|
|
|
|
|
const results = query
|
|
|
|
|
? list.filter(
|
|
|
|
|
(item) =>
|
|
|
|
|
item.channelName.toLowerCase().includes(query) ||
|
|
|
|
|
item.channelUrl.toLowerCase().includes(query)
|
|
|
|
|
)
|
|
|
|
|
: list
|
|
|
|
|
cb(results)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSelect = (item: ChannelItem) => {
|
|
|
|
|
if (!localSelected.value.some((c) => c.channelUrl === item.channelUrl)) {
|
|
|
|
|
localSelected.value.push({ ...item })
|
|
|
|
|
}
|
|
|
|
|
searchQuery.value = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const removeItem = (id: string) => {
|
|
|
|
|
localSelected.value = localSelected.value.filter((c) => c.id !== id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
@@ -79,105 +171,98 @@ defineExpose({
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.dark-dialog {
|
|
|
|
|
<style>
|
|
|
|
|
.custom-script-dialog {
|
|
|
|
|
background-color: #F4F3EB !important;
|
|
|
|
|
border-radius: 20px !important;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15) !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-dialog {
|
|
|
|
|
.dark .custom-script-dialog {
|
|
|
|
|
background-color: #1f1f22 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-dialog :deep(.el-dialog__header) {
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
padding: 20px 24px 16px;
|
|
|
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-dialog :deep(.el-dialog__header) {
|
|
|
|
|
border-bottom-color: rgba(255, 255, 255, 0.06);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-dialog :deep(.el-dialog__title) {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #171717;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-dialog :deep(.el-dialog__title) {
|
|
|
|
|
color: #f3f4f6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-dialog :deep(.el-dialog__body) {
|
|
|
|
|
.custom-script-dialog .el-dialog__body {
|
|
|
|
|
padding: 0 !important;
|
|
|
|
|
max-height: calc(100vh - 360px);
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-dialog__body::-webkit-scrollbar {
|
|
|
|
|
width: 6px;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-dialog__body::-webkit-scrollbar-thumb {
|
|
|
|
|
background-color: #D1CFC7;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
}
|
|
|
|
|
.dark .custom-script-dialog .el-dialog__body::-webkit-scrollbar-thumb {
|
|
|
|
|
background-color: #2a2a2d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-form :deep(.el-form-item__label) {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #4B4B4B !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-form :deep(.el-form-item__label) {
|
|
|
|
|
color: #9ca3af !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-form :deep(.el-input__wrapper) {
|
|
|
|
|
/* Input styling */
|
|
|
|
|
.custom-script-dialog .el-input__wrapper,
|
|
|
|
|
.custom-script-dialog .el-autocomplete .el-input__wrapper {
|
|
|
|
|
background-color: #EDECE4 !important;
|
|
|
|
|
border-radius: 12px !important;
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
border: 1px solid transparent !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-form :deep(.el-input__wrapper) {
|
|
|
|
|
background-color: #222225 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-form :deep(.el-input__wrapper.is-focus) {
|
|
|
|
|
border-color: #3B6DE8 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-form :deep(.el-input__inner) {
|
|
|
|
|
color: #171717 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-form :deep(.el-input__inner) {
|
|
|
|
|
.dark .custom-script-dialog .el-input__wrapper,
|
|
|
|
|
.dark .custom-script-dialog .el-autocomplete .el-input__wrapper {
|
|
|
|
|
background-color: #222225 !important;
|
|
|
|
|
color: #f3f4f6 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark-form :deep(.el-input__inner::placeholder) {
|
|
|
|
|
.custom-script-dialog .el-input__wrapper.is-focus,
|
|
|
|
|
.custom-script-dialog .el-autocomplete .el-input__wrapper.is-focus {
|
|
|
|
|
border-color: #3B6DE8 !important;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-input__inner {
|
|
|
|
|
color: #171717 !important;
|
|
|
|
|
}
|
|
|
|
|
.dark .custom-script-dialog .el-input__inner {
|
|
|
|
|
color: #f3f4f6 !important;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-input__inner::placeholder {
|
|
|
|
|
color: #99A0AE !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .dark-form :deep(.el-input__inner::placeholder) {
|
|
|
|
|
.dark .custom-script-dialog .el-input__inner::placeholder {
|
|
|
|
|
color: #6b7280 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
padding: 16px 24px 20px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
/* Autocomplete dropdown */
|
|
|
|
|
.custom-script-dialog .el-autocomplete-suggestion {
|
|
|
|
|
background-color: #F4F3EB !important;
|
|
|
|
|
border-radius: 12px !important;
|
|
|
|
|
}
|
|
|
|
|
.dark .custom-script-dialog .el-autocomplete-suggestion {
|
|
|
|
|
background-color: #1f1f22 !important;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-autocomplete-suggestion__wrap {
|
|
|
|
|
padding: 8px !important;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-autocomplete-suggestion__list li {
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
}
|
|
|
|
|
.custom-script-dialog .el-autocomplete-suggestion__list li:hover {
|
|
|
|
|
background-color: #E8E6DE !important;
|
|
|
|
|
}
|
|
|
|
|
.dark .custom-script-dialog .el-autocomplete-suggestion__list li:hover {
|
|
|
|
|
background-color: #222225 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Cancel button */
|
|
|
|
|
.cancel-btn {
|
|
|
|
|
background-color: #EDECE4 !important;
|
|
|
|
|
border-color: transparent !important;
|
|
|
|
|
color: #4B4B4B !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cancel-btn:hover {
|
|
|
|
|
background-color: #E5E4DC !important;
|
|
|
|
|
color: #171717 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .cancel-btn {
|
|
|
|
|
background-color: #222225 !important;
|
|
|
|
|
color: #9ca3af !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .cancel-btn:hover {
|
|
|
|
|
background-color: #2a2a2d !important;
|
|
|
|
|
color: #f3f4f6 !important;
|
|
|
|
|
|