feat: reoede web to tailwindcss
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { defineEmits, defineProps, withDefaults } from 'vue'
|
||||
import WaysForm from './WaysForm.vue'
|
||||
|
||||
// 组件props
|
||||
interface Props {
|
||||
open?: boolean
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {
|
||||
open: false
|
||||
})
|
||||
|
||||
// 组件emits
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'save': [data: any]
|
||||
}>()
|
||||
|
||||
// 处理保存事件
|
||||
const handleSave = (data: any) => {
|
||||
emit('save', data)
|
||||
}
|
||||
|
||||
// 处理关闭事件
|
||||
const handleUpdateOpen = (value: boolean) => {
|
||||
emit('update:open', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WaysForm
|
||||
:open="open"
|
||||
mode="add"
|
||||
@update:open="handleUpdateOpen"
|
||||
@save="handleSave"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AddWays'
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { defineEmits, defineProps, withDefaults } from 'vue'
|
||||
import WaysForm from './WaysForm.vue'
|
||||
|
||||
// 组件props
|
||||
interface Props {
|
||||
open?: boolean
|
||||
editData?: any // 编辑时传入的数据
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
editData: null
|
||||
})
|
||||
|
||||
// 组件emits
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'save': [data: any]
|
||||
}>()
|
||||
|
||||
// 处理保存事件
|
||||
const handleSave = (data: any) => {
|
||||
emit('save', data)
|
||||
}
|
||||
|
||||
// 处理关闭事件
|
||||
const handleUpdateOpen = (value: boolean) => {
|
||||
emit('update:open', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WaysForm
|
||||
:open="open"
|
||||
:edit-data="editData"
|
||||
mode="edit"
|
||||
@update:open="handleUpdateOpen"
|
||||
@save="handleSave"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditWays'
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,285 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive, onMounted } from 'vue'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
|
||||
import EmptyTableState from '@/components/ui/EmptyTableState.vue'
|
||||
import Pagination from '@/components/ui/Pagination.vue'
|
||||
import AddWays from './AddWays.vue'
|
||||
import EditWays from './EditWays.vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
import { request } from '@/api/api';
|
||||
import { CONSTANT } from '@/constant';
|
||||
// @ts-ignore
|
||||
import { getPageSize } from '@/util/pageUtils';
|
||||
|
||||
|
||||
interface WayItem {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
config: string
|
||||
created_on: string
|
||||
modified_on: string
|
||||
status: number
|
||||
}
|
||||
|
||||
const router = useRoute();
|
||||
|
||||
let state = reactive({
|
||||
tableData: [] as WayItem[],
|
||||
total: 0,
|
||||
currPage: 1,
|
||||
pageSize: getPageSize(),
|
||||
search: '',
|
||||
optionValue: '',
|
||||
})
|
||||
|
||||
// 状态过滤
|
||||
const selectedStatus = ref('all')
|
||||
|
||||
// 渠道类型过滤
|
||||
const selectedChannelType = ref('all')
|
||||
|
||||
// 渠道类型选项 - 从CONSTANT.WAYS_DATA生成
|
||||
const channelTypeOptions = computed(() => {
|
||||
const options = [{ value: 'all', label: '全部类型' }]
|
||||
CONSTANT.WAYS_DATA.forEach(item => {
|
||||
options.push({ value: item.type, label: item.label })
|
||||
})
|
||||
return options
|
||||
})
|
||||
|
||||
// Sheet 相关状态
|
||||
const isSheetOpen = ref(false)
|
||||
const selectedConfig = ref('')
|
||||
const selectedChannelName = ref('')
|
||||
|
||||
// 新增渠道 Sheet 状态
|
||||
const isAddChannelDrawerOpen = ref(false)
|
||||
|
||||
// 编辑渠道 Sheet 状态
|
||||
const isEditChannelDrawerOpen = ref(false)
|
||||
const editChannelData = ref<WayItem | null>(null)
|
||||
|
||||
// 处理保存新渠道
|
||||
const handleSaveChannel = (_data: any) => {
|
||||
// 这里可以添加实际的保存逻辑
|
||||
// 保存成功后刷新列表
|
||||
queryListDataWithStatus()
|
||||
}
|
||||
|
||||
// 总页数
|
||||
const totalPages = computed(() => Math.ceil(state.total / state.pageSize))
|
||||
|
||||
const getWayTypeText = (type: string) => {
|
||||
const wayData = CONSTANT.WAYS_DATA.find(item => item.type === type)
|
||||
return wayData ? wayData.label : type
|
||||
}
|
||||
|
||||
// 打开编辑渠道Drawer
|
||||
const openEditChannelDrawer = (channel: WayItem) => {
|
||||
editChannelData.value = channel
|
||||
isEditChannelDrawerOpen.value = true
|
||||
}
|
||||
|
||||
// 处理编辑渠道保存
|
||||
const handleEditChannel = (_data: any) => {
|
||||
// 保存成功后刷新列表
|
||||
queryListDataWithStatus()
|
||||
}
|
||||
|
||||
const changePage = async (page: number) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currPage = page
|
||||
await queryListDataWithStatus()
|
||||
}
|
||||
}
|
||||
|
||||
//触发过滤筛选
|
||||
const filterFunc = async () => {
|
||||
await queryListData(state.currPage, state.pageSize, state.search, state.optionValue);
|
||||
}
|
||||
|
||||
// 按渠道类型过滤
|
||||
const filterByChannelType = async (value: any) => {
|
||||
if (value) {
|
||||
selectedChannelType.value = String(value);
|
||||
state.currPage = 1; // 重置到第一页
|
||||
await queryListDataWithStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// 查询数据(包含状态过滤)
|
||||
const queryListDataWithStatus = async () => {
|
||||
const statusParam = selectedStatus.value === 'all' ? '' : selectedStatus.value;
|
||||
const channelTypeParam = selectedChannelType.value === 'all' ? '' : selectedChannelType.value;
|
||||
await queryListData(state.currPage, state.pageSize, state.search, channelTypeParam, '', statusParam);
|
||||
}
|
||||
|
||||
const queryListData = async (page: number, size: number, name = '', channelType = '', query = '', status = '') => {
|
||||
let params: any = { page: page, size: size, name: name, type: channelType, query: query };
|
||||
if (status !== '') {
|
||||
params.status = status;
|
||||
}
|
||||
const rsp = await request.get('/sendways/list', { params: params });
|
||||
state.tableData = await rsp.data.data.lists;
|
||||
state.total = await rsp.data.data.total;
|
||||
}
|
||||
// 删除渠道
|
||||
const handleDelete = async (id: number) => {
|
||||
const rsp = await request.post('/sendways/delete', { id: id });
|
||||
if (rsp.status == 200 && await rsp.data.code == 200) {
|
||||
// state.tableData.splice(index, 1);
|
||||
toast.success(rsp.data.msg);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化查询
|
||||
state.search = router.query.name?.toString() || '';
|
||||
await queryListData(
|
||||
1,
|
||||
state.pageSize,
|
||||
router.query.name?.toString() || '',
|
||||
router.query.channel_type?.toString() || ''
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6 w-full max-w-6xl mx-auto space-y-2">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4">
|
||||
<div class="flex flex-col sm:flex-row gap-2 sm:gap-4">
|
||||
<div class="flex-1 sm:flex-initial">
|
||||
<Input v-model="state.search" placeholder="搜索发信方式名称..." class="w-full sm:w-64" @keyup.enter="filterFunc"
|
||||
@blur="filterFunc" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 sm:flex-initial">
|
||||
<Select v-model="selectedChannelType" class="w-full" @update:model-value="filterByChannelType">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择渠道类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="option in channelTypeOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex-shrink-0">
|
||||
<Drawer v-model:open="isAddChannelDrawerOpen">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="default" class="w-full sm:w-auto bg-gray-800 hover:bg-gray-900">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
新增渠道
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
|
||||
<DrawerContent class="w-[800px] max-w-[90vw] mx-auto h-[90vh] max-h-[90vh]">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>新增发信渠道</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
|
||||
<div class="px-4 pb-4 overflow-y-auto">
|
||||
<AddWays v-model:open="isAddChannelDrawerOpen" @save="handleSaveChannel" />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-20">ID</TableHead>
|
||||
<TableHead>发信方式名称</TableHead>
|
||||
<TableHead>发信方式类型</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>更新时间</TableHead>
|
||||
<TableHead class="text-center">操作/状态</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
<!-- 空数据展示 -->
|
||||
<TableRow v-if="state.tableData.length === 0">
|
||||
<TableCell colspan="6" class="text-center py-12">
|
||||
<EmptyTableState title="暂无发信方式" description="还没有配置任何发信方式,请先添加发信方式" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<!-- 数据行 -->
|
||||
<TableRow v-for="channel in state.tableData" :key="channel.id">
|
||||
<TableCell>{{ channel.id }}</TableCell>
|
||||
<TableCell>{{ channel.name }}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{{ getWayTypeText(channel.type) }}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{{ channel.created_on }}</TableCell>
|
||||
<TableCell>{{ channel.modified_on }}</TableCell>
|
||||
<TableCell class="text-center space-x-2">
|
||||
<Button size="sm" variant="outline" @click="openEditChannelDrawer(channel)">编辑</Button>
|
||||
<!-- <Button size="sm" variant="outline" @click="openConfigSheet(channel)">查看</Button> -->
|
||||
<Button size="sm" variant="outline" class="text-red-500 border-red-300 hover:bg-red-50
|
||||
hover:border-red-400 hover:text-red-600 hover:shadow-md
|
||||
transition-all duration-200" @click="handleDelete(channel.id)">删除</Button>
|
||||
<!-- <Badge :class="channel.status === 1 ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-600'">
|
||||
{{ getStatusText(channel.status) }} -->
|
||||
<!-- </Badge> -->
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination :total="state.total" :current-page="state.currPage" :page-size="state.pageSize"
|
||||
@page-change="changePage" />
|
||||
|
||||
<!-- 编辑渠道Drawer -->
|
||||
<Drawer v-model:open="isEditChannelDrawerOpen">
|
||||
<DrawerContent class="w-[800px] max-w-[90vw] mx-auto h-[90vh] max-h-[90vh]">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>编辑发信渠道</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
|
||||
<div class="px-4 pb-4 overflow-y-auto">
|
||||
<EditWays v-model:open="isEditChannelDrawerOpen" :edit-data="editChannelData" @save="handleEditChannel" />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<!-- 配置详情Sheet -->
|
||||
<Sheet v-model:open="isSheetOpen">
|
||||
<SheetContent class="w-[600px] sm:w-[900px] lg:w-[1000px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{{ selectedChannelName }} - 发信方式配置详情</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div class="mt-6">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<pre class="whitespace-pre-wrap text-sm font-mono">{{ selectedConfig }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,332 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineEmits, defineProps, withDefaults, watch } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { CONSTANT } from '@/constant'
|
||||
import { validateForm, createValidationState, type InputConfig } from '@/util/validation'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { request } from '@/api/api'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from '@/components/ui/tooltip'
|
||||
|
||||
// 组件props
|
||||
interface Props {
|
||||
open?: boolean
|
||||
editData?: any // 编辑时传入的数据
|
||||
mode?: 'add' | 'edit' // 模式:新增或编辑
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
editData: null,
|
||||
mode: 'add'
|
||||
})
|
||||
|
||||
// 组件emits
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'save': [data: any]
|
||||
}>()
|
||||
|
||||
// 前端的页面添加配置
|
||||
let waysConfigMap = CONSTANT.WAYS_DATA;
|
||||
|
||||
// Radio Group 选项 - 根据waysConfigMap动态生成
|
||||
const channelModeOptions = waysConfigMap.map(item => ({
|
||||
value: item.type,
|
||||
label: item.label
|
||||
}))
|
||||
const channelMode = ref(channelModeOptions[0]?.value || '')
|
||||
|
||||
// 当前选中渠道的配置
|
||||
const currentChannelConfig = computed(() => {
|
||||
return waysConfigMap.find(item => item.type === channelMode.value) || null
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formData = ref<Record<string, any>>({})
|
||||
|
||||
// 校验状态管理
|
||||
const validationState = createValidationState()
|
||||
|
||||
// 初始化表单数据
|
||||
const initFormData = () => {
|
||||
const config = currentChannelConfig.value
|
||||
if (!config) return
|
||||
|
||||
const newFormData: Record<string, any> = {}
|
||||
|
||||
// 如果是编辑模式且有编辑数据,先填充编辑数据
|
||||
if (props.mode === 'edit' && props.editData) {
|
||||
// 设置渠道类型
|
||||
channelMode.value = props.editData.type || channelModeOptions[0]?.value || ''
|
||||
|
||||
// 解析auth数据
|
||||
let authData: Record<string, any> = {}
|
||||
try {
|
||||
authData = props.editData.auth ? JSON.parse(props.editData.auth) : {}
|
||||
} catch (e) {
|
||||
console.error('解析auth数据失败:', e)
|
||||
}
|
||||
|
||||
// 填充基本字段
|
||||
newFormData.name = props.editData.name || ''
|
||||
|
||||
// 填充auth中的字段
|
||||
Object.keys(authData).forEach(key => {
|
||||
newFormData[key] = authData[key]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化基本输入字段
|
||||
if (config.inputs) {
|
||||
config.inputs.forEach((input: any) => {
|
||||
if (newFormData[input.col] === undefined) {
|
||||
newFormData[input.col] = input.value || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化任务指令输入字段
|
||||
if (config.taskInsInputs) {
|
||||
config.taskInsInputs.forEach((input: any) => {
|
||||
if (newFormData[input.col] === undefined) {
|
||||
newFormData[input.col] = input.value || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化任务指令单选项
|
||||
if (config.taskInsRadios && config.taskInsRadios.length > 0) {
|
||||
if (newFormData.taskInsRadio === undefined) {
|
||||
newFormData.taskInsRadio = config.taskInsRadios[0].value
|
||||
}
|
||||
}
|
||||
|
||||
formData.value = newFormData
|
||||
}
|
||||
|
||||
// 获取所有输入字段配置
|
||||
const getAllInputConfigs = (): InputConfig[] => {
|
||||
const config = currentChannelConfig.value
|
||||
if (!config) return []
|
||||
|
||||
const configs: InputConfig[] = []
|
||||
|
||||
// 基本输入字段
|
||||
if (config.inputs) {
|
||||
configs.push(...config.inputs.map((input: any) => ({
|
||||
col: input.col,
|
||||
label: input.label,
|
||||
subLabel: input.subLabel,
|
||||
type: input.type,
|
||||
required: input.required !== false,
|
||||
minLength: input.minLength,
|
||||
maxLength: input.maxLength
|
||||
})))
|
||||
}
|
||||
|
||||
// 任务指令输入字段
|
||||
if (config.taskInsInputs) {
|
||||
configs.push(...config.taskInsInputs.map((input: any) => ({
|
||||
col: input.col,
|
||||
label: input.label,
|
||||
subLabel: input.subLabel,
|
||||
type: input.type,
|
||||
required: input.required !== false,
|
||||
minLength: input.minLength,
|
||||
maxLength: input.maxLength
|
||||
})))
|
||||
}
|
||||
|
||||
return configs
|
||||
}
|
||||
|
||||
// 校验表单
|
||||
const validateFormData = () => {
|
||||
const inputConfigs = getAllInputConfigs()
|
||||
const result = validateForm(formData.value, inputConfigs)
|
||||
|
||||
validationState.setErrors(result.errors)
|
||||
return result.isValid
|
||||
}
|
||||
|
||||
// 监听渠道模式变化
|
||||
const handleChannelModeChange = () => {
|
||||
initFormData()
|
||||
validationState.clearAllErrors()
|
||||
}
|
||||
|
||||
// 监听编辑数据变化(仅编辑模式)
|
||||
watch(() => props.editData, () => {
|
||||
if (props.mode === 'edit') {
|
||||
initFormData()
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 初始化表单数据(新增模式)
|
||||
if (props.mode === 'add') {
|
||||
initFormData()
|
||||
}
|
||||
|
||||
// 关闭drawer
|
||||
const handleClose = () => {
|
||||
emit('update:open', false)
|
||||
}
|
||||
|
||||
// 获取最终提交数据
|
||||
const getFinalData = () => {
|
||||
// 根据当前渠道配置的inputs中的col字段,从formData中提取对应的值组成auth对象
|
||||
const config = currentChannelConfig.value
|
||||
const authData: Record<string, any> = {}
|
||||
if (config && config.inputs) {
|
||||
config.inputs.forEach((input: any) => {
|
||||
if (formData.value[input.col] !== undefined && input.col != 'name') {
|
||||
authData[input.col] = formData.value[input.col]
|
||||
if (config.type == 'Email' && input.col == 'port') {
|
||||
authData[input.col] = parseInt(formData.value[input.col])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let postData: Record<string, any> = {
|
||||
auth: JSON.stringify(authData),
|
||||
type: channelMode.value,
|
||||
name: formData.value.name,
|
||||
}
|
||||
|
||||
// 编辑时需要传递ID
|
||||
if (props.mode === 'edit' && props.editData && props.editData.id) {
|
||||
postData.id = props.editData.id
|
||||
}
|
||||
|
||||
return postData
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
const handleTest = async () => {
|
||||
if (!validateFormData()) { return }
|
||||
|
||||
let postData = getFinalData()
|
||||
const rsp = await request.post('/sendways/test', postData)
|
||||
if (await rsp.data.code == 200) {
|
||||
toast({ message: await rsp.data.msg, type: 'success' })
|
||||
}
|
||||
}
|
||||
|
||||
// 保存数据
|
||||
const handleSave = async () => {
|
||||
if (!validateFormData()) { return }
|
||||
|
||||
let postData = getFinalData()
|
||||
|
||||
// 根据模式选择API路径和成功消息
|
||||
const apiUrl = props.mode === 'edit' ? '/sendways/edit' : '/sendways/add'
|
||||
const successMessage = props.mode === 'edit' ? '更新渠道成功!' : '添加渠道成功!'
|
||||
|
||||
const rsp = await request.post(apiUrl, postData);
|
||||
if (await rsp.data.code == 200) {
|
||||
toast(successMessage )
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算保存按钮文本
|
||||
const saveButtonText = computed(() => {
|
||||
return props.mode === 'edit' ? '更新' : '保存'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<!-- Radio Group -->
|
||||
<div class="mb-6">
|
||||
<label class="text-lg font-medium mb-3 block">渠道类型</label>
|
||||
<RadioGroup v-model="channelMode" @update:model-value="handleChannelModeChange" class="flex flex-wrap gap-4">
|
||||
<div v-for="option in channelModeOptions" :key="option.value" class="flex items-center space-x-2">
|
||||
<RadioGroupItem :value="option.value" :id="option.value" />
|
||||
<label :for="option.value"
|
||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
{{ option.label }}
|
||||
</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<!-- 动态表单 -->
|
||||
<div v-if="currentChannelConfig" class="mt-6">
|
||||
<!-- 基本配置输入字段 -->
|
||||
<div v-if="currentChannelConfig.inputs && currentChannelConfig.inputs.length > 0" class="mb-8">
|
||||
<h4 class="text-base font-medium mb-4 text-gray-800">基本配置</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="input in currentChannelConfig.inputs" :key="input.col" class="space-y-2" :class="{
|
||||
'md:col-span-2': input.isTextArea
|
||||
}">
|
||||
<Label :for="input.col" class="text-sm font-medium">
|
||||
{{ input.subLabel || input.label }}
|
||||
<span v-if="input.tips" class="text-xs text-gray-500 ml-1">({{ input.tips }})</span>
|
||||
</Label>
|
||||
<!-- 配置输入框 -->
|
||||
<Textarea v-if="input.isTextArea" :id="input.col" v-model="formData[input.col]"
|
||||
:placeholder="input.desc || input.placeholder || input.subLabel || input.label" :class="{
|
||||
'w-full': true,
|
||||
'border-red-500 focus:border-red-500': validationState.errors.value[input.col]
|
||||
}" @input="() => validationState.clearFieldError(input.col)" />
|
||||
<Input v-else :id="input.col" v-model="formData[input.col]"
|
||||
:placeholder="input.desc || input.placeholder || input.subLabel || input.label" :class="{
|
||||
'w-full': true,
|
||||
'border-red-500 focus:border-red-500': validationState.errors.value[input.col]
|
||||
}" @input="() => validationState.clearFieldError(input.col)" />
|
||||
<!-- 异常校验提示显示 -->
|
||||
<div v-if="validationState.errors.value[input.col]" class="text-red-500 text-xs mt-1">
|
||||
{{ validationState.errors.value[input.col] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 ml-4" v-if="currentChannelConfig.tips && currentChannelConfig.tips.text">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger class="text-sm hover:text-gray-700 inline-flex items-center gap-1">
|
||||
{{ currentChannelConfig.tips.text }}
|
||||
<span
|
||||
class="cursor-help inline-flex items-center justify-center w-4 h-4 rounded-full border border-gray-300 hover:border-gray-400 text-xs">?</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p class="text-sm">{{ currentChannelConfig.tips.desc }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-6 p-6 bg-gray-50 rounded-lg">
|
||||
<p class="text-gray-500">请选择一个渠道类型开始配置</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-8 pt-4 border-t">
|
||||
<Button variant="outline" @click="handleClose">取消</Button>
|
||||
<Button @click="handleTest">测试</Button>
|
||||
<Button @click="handleSave">{{ saveButtonText }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WaysForm'
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user