This commit is contained in:
2026-04-05 16:14:23 +08:00
parent 9936b2b92c
commit 33d7460ea0
19 changed files with 1515 additions and 2 deletions

View File

@@ -0,0 +1,77 @@
<template>
<div class="detail-container">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="ID">{{ record.id }}</a-descriptions-item>
<a-descriptions-item label="名称">{{ record.name }}</a-descriptions-item>
<a-descriptions-item label="描述" :span="2">{{ record.description || '-' }}</a-descriptions-item>
<a-descriptions-item label="启用状态">
<a-tag :color="record.enabled ? 'green' : 'gray'">
{{ record.enabled ? '已启用' : '已禁用' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ formatTime(record.created_at) }}</a-descriptions-item>
<a-descriptions-item label="更新时间">{{ formatTime(record.updated_at) }}</a-descriptions-item>
</a-descriptions>
<div class="actions">
<a-space>
<a-button type="primary" @click="handleEdit">
<template #icon>
<icon-edit />
</template>
编辑
</a-button>
<a-button status="danger" @click="handleDelete">
<template #icon>
<icon-delete />
</template>
删除
</a-button>
</a-space>
</div>
</div>
</template>
<script lang="ts" setup>
import { IconEdit, IconDelete } from '@arco-design/web-vue/es/icon'
import type { {{Module}}Item } from '@/api/ops/{{module}}'
const props = defineProps<{
record: {{Module}}Item
}>()
const emit = defineEmits<{
(e: 'edit'): void
(e: 'delete'): void
}>()
const formatTime = (time?: string) => {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const handleEdit = () => {
emit('edit')
}
const handleDelete = () => {
emit('delete')
}
</script>
<style scoped lang="less">
.detail-container {
.actions {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid var(--color-border);
}
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<a-modal
v-model:visible="visible"
:title="isEdit ? '编辑{{ModuleTitle}}' : '新增{{ModuleTitle}}'"
:width="600"
:mask-closable="false"
unmount-on-close
@ok="handleOk"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical">
<a-form-item field="name" label="名称" required>
<a-input v-model="formData.name" placeholder="请输入名称" :max-length="100" />
</a-form-item>
<a-form-item field="description" label="描述">
<a-textarea v-model="formData.description" placeholder="请输入描述" :max-length="500" :auto-size="{ minRows: 3, maxRows: 5 }" />
</a-form-item>
<a-form-item field="enabled" label="启用状态">
<a-switch v-model="formData.enabled" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { {{Module}}Item, {{Module}}CreateData, {{Module}}UpdateData } from '@/api/ops/{{module}}'
import { create{{Module}}, update{{Module}} } from '@/api/ops/{{module}}'
const props = defineProps<{
visible: boolean
record: {{Module}}Item | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}>()
const formRef = ref()
const isEdit = computed(() => !!props.record)
const formData = ref<{{Module}}CreateData>({
name: '',
description: '',
enabled: true,
})
const rules = {
name: [
{ required: true, message: '请输入名称' },
{ max_length: 100, message: '名称不能超过100个字符' },
],
}
watch(
() => props.visible,
(val) => {
if (val && props.record) {
formData.value = {
name: props.record.name,
description: props.record.description || '',
enabled: props.record.enabled,
}
} else if (val) {
formData.value = {
name: '',
description: '',
enabled: true,
}
}
}
)
const handleOk = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) return
if (isEdit.value && props.record) {
const res = await update{{Module}}(props.record.id, formData.value as {{Module}}UpdateData)
if (res && res.code === 0) {
Message.success('更新成功')
emit('success')
emit('update:visible', false)
}
} else {
const res = await create{{Module}}(formData.value)
if (res && res.code === 0) {
Message.success('创建成功')
emit('success')
emit('update:visible', false)
}
}
} catch (error) {
console.error('操作失败:', error)
Message.error('操作失败')
}
}
const handleCancel = () => {
emit('update:visible', false)
}
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,66 @@
import { request } from '@/api/request'
/** {{Module}}类型 */
export interface {{Module}}Item {
id: number
created_at: string
updated_at: string
name: string
description?: string
enabled: boolean
}
/** {{Module}}列表响应 */
export interface {{Module}}ListResponse {
total: number
page: number
page_size: number
data: {{Module}}Item[]
}
/** {{Module}}列表请求参数 */
export interface {{Module}}ListParams {
page?: number
size?: number
keyword?: string
enabled?: boolean
}
/** 创建{{Module}}请求参数 */
export interface {{Module}}CreateData {
name: string
description?: string
enabled?: boolean
}
/** 更新{{Module}}请求参数 */
export interface {{Module}}UpdateData {
name?: string
description?: string
enabled?: boolean
}
/** 获取{{Module}}列表(分页) */
export const fetch{{Module}}List = (params?: {{Module}}ListParams) => {
return request.get<{{Module}}ListResponse>('/DC-Control/v1/{{module}}s', { params })
}
/** 获取{{Module}}详情 */
export const fetch{{Module}}Detail = (id: number) => {
return request.get<{{Module}}Item>(`/DC-Control/v1/{{module}}s/${id}`)
}
/** 创建{{Module}} */
export const create{{Module}} = (data: {{Module}}CreateData) => {
return request.post<{ message: string; id: number }>('/DC-Control/v1/{{module}}s', data)
}
/** 更新{{Module}} */
export const update{{Module}} = (id: number, data: {{Module}}UpdateData) => {
return request.put<{ message: string }>(`/DC-Control/v1/{{module}}s/${id}`, data)
}
/** 删除{{Module}} */
export const delete{{Module}} = (id: number) => {
return request.delete<{ message: string }>(`/DC-Control/v1/{{module}}s/${id}`)
}

View File

@@ -0,0 +1,39 @@
export const columns = [
{
dataIndex: 'id',
title: 'ID',
width: 80,
slotName: 'id',
},
{
dataIndex: 'name',
title: '名称',
width: 150,
},
{
dataIndex: 'description',
title: '描述',
width: 200,
ellipsis: true,
tooltip: true,
},
{
dataIndex: 'enabled',
title: '启用状态',
width: 100,
slotName: 'enabled',
},
{
dataIndex: 'created_at',
title: '创建时间',
width: 180,
slotName: 'created_at',
},
{
dataIndex: 'actions',
title: '操作',
width: 180,
fixed: 'right' as const,
slotName: 'actions',
},
]

235
.kilo/templates/index.vue Normal file
View File

@@ -0,0 +1,235 @@
<template>
<div class="container">
<search-table
:form-model="formModel"
:form-items="formItems"
:data="tableData"
:columns="columns"
:loading="loading"
:pagination="pagination"
title="{{ModuleTitle}}管理"
search-button-text="查询"
reset-button-text="重置"
@update:form-model="handleFormModelUpdate"
@search="handleSearch"
@reset="handleReset"
@page-change="handlePageChange"
@refresh="handleRefresh"
>
<template #toolbar-left>
<a-button type="primary" @click="handleAdd">
<template #icon>
<icon-plus />
</template>
新增
</a-button>
</template>
<template #id="{ record }">
{{ record.id }}
</template>
<template #enabled="{ record }">
<a-tag :color="record.enabled ? 'green' : 'gray'">
{{ record.enabled ? '已启用' : '已禁用' }}
</a-tag>
</template>
<template #created_at="{ record }">
{{ formatTime(record.created_at) }}
</template>
<template #actions="{ record }">
<a-space>
<a-button type="text" size="small" @click="handleDetail(record)">
<template #icon>
<icon-eye />
</template>
详情
</a-button>
<a-button type="text" size="small" @click="handleEdit(record)">
<template #icon>
<icon-edit />
</template>
编辑
</a-button>
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
<template #icon>
<icon-delete />
</template>
删除
</a-button>
</a-space>
</template>
</search-table>
<FormDialog v-model:visible="formDialogVisible" :record="currentRecord" @success="handleFormSuccess" />
<a-drawer v-model:visible="detailVisible" :width="600" title="{{ModuleTitle}}详情" :footer="false" unmount-on-close>
<Detail v-if="currentRecord" :record="currentRecord" @edit="handleDetailEdit" @delete="handleDetailDelete" />
</a-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { IconPlus, IconEdit, IconDelete, IconEye } from '@arco-design/web-vue/es/icon'
import type { FormItem } from '@/components/search-form/types'
import SearchTable from '@/components/search-table/index.vue'
import FormDialog from './components/FormDialog.vue'
import Detail from './components/Detail.vue'
import { searchFormConfig } from './config/search-form'
import { columns as columnsConfig } from './config/columns'
import { fetch{{Module}}List, delete{{Module}}, type {{Module}}Item, type {{Module}}ListParams } from '@/api/ops/{{module}}'
const loading = ref(false)
const tableData = ref<{{Module}}Item[]>([])
const formDialogVisible = ref(false)
const detailVisible = ref(false)
const currentRecord = ref<{{Module}}Item | null>(null)
const formModel = ref({
keyword: '',
enabled: undefined as boolean | undefined,
})
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
})
const formItems = computed<FormItem[]>(() => searchFormConfig)
const columns = computed(() => columnsConfig)
const formatTime = (time?: string) => {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const fetchData = async () => {
loading.value = true
try {
const params: {{Module}}ListParams = {
page: pagination.current,
size: pagination.pageSize,
keyword: formModel.value.keyword,
enabled: formModel.value.enabled,
}
const response: any = await fetch{{Module}}List(params)
if (response && response.code === 0 && response.details) {
tableData.value = response.details?.data || []
pagination.total = response.details?.total || 0
} else {
tableData.value = []
pagination.total = 0
}
} catch (error) {
console.error('获取列表失败:', error)
Message.error('获取列表失败')
tableData.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
const handleSearch = () => {
pagination.current = 1
fetchData()
}
const handleFormModelUpdate = (value: any) => {
formModel.value = value
}
const handleReset = () => {
formModel.value = {
keyword: '',
enabled: undefined,
}
pagination.current = 1
fetchData()
}
const handlePageChange = (current: number) => {
pagination.current = current
fetchData()
}
const handleRefresh = () => {
fetchData()
Message.success('数据已刷新')
}
const handleAdd = () => {
currentRecord.value = null
formDialogVisible.value = true
}
const handleEdit = (record: {{Module}}Item) => {
currentRecord.value = record
formDialogVisible.value = true
}
const handleDetail = (record: {{Module}}Item) => {
currentRecord.value = record
detailVisible.value = true
}
const handleDetailEdit = () => {
detailVisible.value = false
formDialogVisible.value = true
}
const handleDetailDelete = () => {
detailVisible.value = false
if (currentRecord.value) {
handleDelete(currentRecord.value)
}
}
const handleFormSuccess = () => {
fetchData()
}
const handleDelete = (record: {{Module}}Item) => {
Modal.confirm({
title: '确认删除',
content: `确认删除 "${record.name}" 吗?`,
onOk: async () => {
try {
const res = await delete{{Module}}(record.id)
if (res && res.code === 0) {
Message.success('删除成功')
fetchData()
}
} catch (error) {
console.error('删除失败:', error)
Message.error('删除失败')
}
},
})
}
fetchData()
</script>
<script lang="ts">
export default {
name: '{{ModuleName}}',
}
</script>
<style scoped lang="less">
.container {
margin-top: 20px;
}
</style>

View File

@@ -0,0 +1,22 @@
import type { FormItem } from '@/components/search-form/types'
export const searchFormConfig: FormItem[] = [
{
field: 'keyword',
label: '关键词',
type: 'input',
placeholder: '请输入名称',
span: 6,
},
{
field: 'enabled',
label: '启用状态',
type: 'select',
placeholder: '请选择启用状态',
options: [
{ label: '已启用', value: true },
{ label: '已禁用', value: false },
],
span: 6,
},
]