chore: opt template style

This commit is contained in:
engigu
2025-12-06 22:38:08 +08:00
parent fb35769e52
commit 5d3bfaa2de
9 changed files with 142 additions and 94 deletions
@@ -7,6 +7,7 @@ import { Badge } from '@/components/ui/badge'
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import EmptyTableState from '@/components/ui/EmptyTableState.vue'
import Pagination from '@/components/ui/Pagination.vue'
import ClickableTruncate from '@/components/ui/ClickableTruncate.vue'
import TemplateApiViewer from './TemplateApiViewer.vue'
import TemplateInstanceConfig from './TemplateInstanceConfig.vue'
import TemplateEditor from './TemplateEditor.vue'
@@ -201,9 +202,11 @@ onMounted(async () => {
<!-- 数据行 -->
<TableRow v-for="item in state.tableData" :key="item.id">
<TableCell>{{ item.id }}</TableCell>
<TableCell class="font-medium">{{ item.name }}</TableCell>
<TableCell>
<div class="max-w-xs truncate">{{ item.description || '-' }}</div>
<ClickableTruncate :text="item.name" wrapper-class="max-w-[80px] sm:max-w-[100px]" preview-title="模板名称" />
</TableCell>
<TableCell>
<ClickableTruncate :text="item.description || '-'" wrapper-class="max-w-[80px] sm:max-w-[130px]" preview-title="模板描述" />
</TableCell>
<TableCell>
<div class="flex gap-1">
@@ -7,6 +7,7 @@ import { Textarea } from '@/components/ui/textarea'
import { Label } from '@/components/ui/label'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Checkbox } from '@/components/ui/checkbox'
import { toast } from 'vue-sonner'
import { request } from '@/api/api'
@@ -219,7 +220,7 @@ const loadTemplateData = (template: TemplateData) => {
placeholders: template.placeholders,
at_mobiles: template.at_mobiles || '',
at_user_ids: template.at_user_ids || '',
is_at_all: template.is_at_all || false,
is_at_all: Boolean(template.is_at_all),
status: template.status
}
@@ -243,18 +244,28 @@ const saveTemplate = async () => {
toast.error('请输入模板名称')
return
}
// 验证至少填写一种格式的模板内容
if (!formData.value.text_template && !formData.value.html_template && !formData.value.markdown_template) {
toast.error('至少需要填写一种格式的模板内容')
return
}
// 同步占位符数据
formData.value.placeholders = JSON.stringify(placeholdersList.value)
try {
const url = props.isEditing ? '/templates/edit' : '/templates/add'
await request.post(url, formData.value)
toast.success(props.isEditing ? '更新模板成功' : '添加模板成功')
emit('update:open', false)
emit('saved')
const response = await request.post(url, formData.value)
if (response.data.code === 200) {
toast.success(props.isEditing ? '更新模板成功' : '添加模板成功')
emit('update:open', false)
emit('saved')
} else {
toast.error(response.data.msg || '操作失败')
}
} catch (error: any) {
toast.error(error.response?.data?.message || '操作失败')
toast.error(error.response?.data?.msg || error.response?.data?.message || '操作失败')
}
}
@@ -278,9 +289,25 @@ watch(() => props.open, (newVal) => {
</DialogHeader>
<div class="space-y-4 py-4">
<!-- 基本信息 -->
<div class="space-y-2">
<Label for="name">模板名称 *</Label>
<Input id="name" v-model="formData.name" placeholder="请输入模板名称" />
<div class="grid grid-cols-10 gap-4">
<div class="col-span-7 space-y-2">
<Label for="name">模板名称 *</Label>
<Input id="name" v-model="formData.name" placeholder="请输入模板名称" />
</div>
<div class="col-span-3 space-y-2">
<Label>状态</Label>
<Select v-model="formData.status">
<SelectTrigger class="w-full">
<SelectValue placeholder="全部" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="enabled">启用</SelectItem>
<SelectItem value="disabled">禁用</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div class="space-y-2">
@@ -288,21 +315,6 @@ watch(() => props.open, (newVal) => {
<Textarea id="description" v-model="formData.description" placeholder="请输入模板描述" />
</div>
<div class="space-y-2">
<Label>状态</Label>
<Select v-model="formData.status">
<SelectTrigger class="w-full">
<SelectValue placeholder="全部" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="enabled">启用</SelectItem>
<SelectItem value="disabled">禁用</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<!-- 占位符配置 -->
<div class="space-y-2">
<div class="flex justify-between items-center">
@@ -337,11 +349,10 @@ watch(() => props.open, (newVal) => {
<Label>@提醒配置 <span class="text-xs text-muted-foreground font-normal">适用于钉钉企业微信等</span></Label>
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
<div class="flex items-center gap-2 px-3 py-2 border rounded-md">
<input
type="checkbox"
<Checkbox
id="is_at_all"
v-model="formData.is_at_all"
class="w-4 h-4 rounded border-gray-300"
:model-value="formData.is_at_all"
@update:model-value="(newVal: boolean | 'indeterminate') => formData.is_at_all = newVal === true"
/>
<Label for="is_at_all" class="cursor-pointer text-sm">@所有人</Label>
</div>
@@ -269,35 +269,42 @@ watch(() => props.open, (newVal) => {
<div class="space-y-4">
<!-- 模板信息 -->
<div class="mb-6 p-4 bg-muted rounded-lg">
<div class="text-sm text-muted-foreground">模板名称</div>
<div class="text-lg font-medium">{{ templateData?.name }}</div>
<div class="text-xs text-muted-foreground mt-1">ID: {{ templateData?.id }}</div>
<div class="mb-6 p-3 bg-muted rounded-lg space-y-1">
<div class="flex items-baseline gap-2">
<span class="text-base font-semibold">{{ templateData?.name }}</span>
<Badge variant="outline" class="text-xs">{{ templateData?.id }}</Badge>
</div>
<div class="text-xs text-muted-foreground">为此模板配置发送实例</div>
</div>
<!-- 添加实例表单 -->
<div class="space-y-4">
<Label class="text-sm font-medium">选择发送渠道</Label>
<Combobox v-model="channelName" @update:model-value="handlechannelNameChange">
<ComboboxAnchor class="w-full">
<ComboboxInput v-model="inputDisplayValue" @input="handleSearch(inputDisplayValue)"
class="flex h-10 w-full" placeholder="搜索或选择渠道类型进行实例的添加..." />
</ComboboxAnchor>
<ComboboxList class="w-[var(--reka-combobox-trigger-width)]">
<ComboboxViewport>
<ComboboxItem v-for="option in displayOptions" :key="option.id" :value="option.name">
<div class="flex items-center justify-between w-full">
<span>{{ option.name }}</span>
<CheckIcon v-if="channelName === option.name" class="h-4 w-4" />
</div>
</ComboboxItem>
<div v-if="isSearching" class="p-2 text-sm text-muted-foreground">搜索中...</div>
<div v-if="!isSearching && displayOptions.length === 0 && searchQuery" class="p-2 text-sm text-muted-foreground">
未找到匹配的渠道
</div>
</ComboboxViewport>
</ComboboxList>
</Combobox>
<div class="flex items-end gap-2">
<div class="flex-1 space-y-2">
<Label class="text-sm font-medium">选择发送渠道</Label>
<Combobox v-model="channelName" @update:model-value="handlechannelNameChange">
<ComboboxAnchor class="w-full">
<ComboboxInput v-model="inputDisplayValue" @input="handleSearch(inputDisplayValue)"
class="flex h-10 w-full" placeholder="搜索或选择渠道类型进行实例的添加..." />
</ComboboxAnchor>
<ComboboxList class="w-[var(--reka-combobox-trigger-width)]">
<ComboboxViewport>
<ComboboxItem v-for="option in displayOptions" :key="option.id" :value="option.name">
<div class="flex items-center justify-between w-full">
<span>{{ option.name }}</span>
<CheckIcon v-if="channelName === option.name" class="h-4 w-4" />
</div>
</ComboboxItem>
<div v-if="isSearching" class="p-2 text-sm text-muted-foreground">搜索中...</div>
<div v-if="!isSearching && displayOptions.length === 0 && searchQuery" class="p-2 text-sm text-muted-foreground">
未找到匹配的渠道
</div>
</ComboboxViewport>
</ComboboxList>
</Combobox>
</div>
<Button size="sm" variant="outline" @click="handleAddSubmit">添加实例</Button>
</div>
</div>
<!-- 渠道配置表单 -->
@@ -326,10 +333,6 @@ watch(() => props.open, (newVal) => {
</div>
</div>
<div class="flex justify-end gap-2 border-b pb-4 mt-2">
<Button @click="handleAddSubmit">添加实例</Button>
</div>
<!-- 关联的实例表 -->
<div class="mt-4">
<h3 class="text-sm font-medium mb-3">已经关联的实例</h3>
@@ -82,11 +82,6 @@ const handlechannelNameChange = () => {
}
}
// 关闭drawer
const handleClose = () => {
emit('update:open', false)
}
// 添加单条实例配置
const handleAddSubmit = async () => {
// 组建表单数据
@@ -244,19 +239,19 @@ onMounted(() => {
<div v-if="props.editData" class="flex flex-col sm:flex-row sm:items-center gap-2 border-b p-4">
<Label class="w-16 sm:w-16">任务名称</Label>
<Input v-model="props.editData.name" placeholder="请输入任务名称" class="w-full sm:w-64" />
<Button class="w-full sm:w-auto sm:ml-auto" @click="handleEditTask">修改</Button>
<Button size="sm" variant="outline" class="w-full sm:w-auto sm:ml-auto" @click="handleEditTask">修改</Button>
</div>
<div class="mt-4">
<div class="flex gap-4">
<div class="flex-1">
<div class="flex items-end gap-2">
<div class="flex-1 space-y-2">
<Label class="text-sm font-medium">选择发送渠道</Label>
<Combobox v-model="channelName" @update:model-value="handlechannelNameChange">
<ComboboxAnchor class="w-full">
<ComboboxInput v-model="inputDisplayValue" @input="handleSearch(inputDisplayValue)"
class="flex h-10 w-full " placeholder="搜索或选择渠道类型进行实例的添加..." />
class="flex h-10 w-full" placeholder="搜索或选择渠道类型进行实例的添加..." />
</ComboboxAnchor>
<ComboboxList class="w-[var(--reka-combobox-trigger-width)]">
<ComboboxViewport>
@@ -276,6 +271,7 @@ onMounted(() => {
</ComboboxList>
</Combobox>
</div>
<Button size="sm" variant="outline" @click="handleAddSubmit">添加实例</Button>
</div>
<!-- 动态任务配置区域 -->
<div v-if="currentChannelConfig" class="mt-4">
@@ -309,11 +305,6 @@ onMounted(() => {
</div>
</div>
<div class="flex justify-end gap-2 border-b pb-4 mt-2">
<Button variant="outline" @click="handleClose">取消</Button>
<Button @click="handleAddSubmit">添加实例</Button>
</div>
<!-- 关联的实例表 -->
<div class="mt-4">
<h3 class="text-sm font-medium text-gray-900 mb-3">已经关联的实例</h3>