This commit is contained in:
ygx
2026-03-28 11:08:24 +08:00
parent 78405c9ec2
commit 641581e3d4
6 changed files with 1367 additions and 270 deletions

133
src/api/ops/goview.ts Normal file
View File

@@ -0,0 +1,133 @@
import { request } from '@/api/request'
// GoView 项目类型定义
export interface GoViewProject {
id: string
identity: string
projectName: string
state: -1 | 1 // -1 未发布, 1 已发布
createTime: string
updateTime?: string
createUserId: number
isDelete: 0 | 1
indexImage: string
backgroundImage: string
remarks: string
}
export interface GoViewProjectData {
id: string
projectId: string
content: string // JSON 格式字符串
createTime: string
createUserId: string
}
export interface GoViewFile {
id: string
fileName: string
fileSize: number
fileSuffix: string
virtualKey: string
relativePath: string
absolutePath: string
createTime: string
}
export interface OssInfo {
BucketName: string
bucketURL: string
}
// API 响应类型
export interface GoViewResponse<T = any> {
code: number
msg: string
data: T
}
// 项目列表响应
export interface ProjectListResponse {
list: GoViewProject[]
total: number
}
/** 获取 OSS 信息 */
export const fetchOssInfo = () => {
return request.get<GoViewResponse<OssInfo>>('/Visual/v1/sys/getOssInfo')
}
/** 获取项目列表 */
export const fetchProjectList = (params?: {
page?: number
size?: number
state?: -1 | 1
}) => {
return request.get<GoViewResponse<ProjectListResponse>>('/Visual/v1/project/list', { params })
}
/** 获取项目数据(公开接口,无需认证) */
export const fetchProjectData = (projectId: string) => {
return request.get<GoViewResponse<GoViewProjectData>>('/Visual/v1/project/getData', {
params: { projectId },
})
}
/** 创建项目 */
export const createProject = (data: {
projectName: string
state: -1 | 1
createTime?: string
createUserId?: number
isDelete?: 0 | 1
indexImage?: string
backgroundImage?: string
remarks?: string
}) => {
return request.post<GoViewResponse<GoViewProject>>('/Visual/v1/project/create', data)
}
/** 编辑项目 */
export const updateProject = (data: {
identity: string
projectName?: string
indexImage?: string
backgroundImage?: string
remarks?: string
}) => {
return request.post<GoViewResponse<GoViewProject>>('/Visual/v1/project/edit', data)
}
/** 删除项目 */
export const deleteProject = (ids: string) => {
return request.delete<GoViewResponse<Record<string, never>>>('/Visual/v1/project/delete', {
params: { ids },
})
}
/** 发布/取消发布项目 */
export const publishProject = (data: { identity: string; state: -1 | 1 }) => {
return request.put<GoViewResponse<{ identity: string; state: number }>>(
'/Visual/v1/project/publish',
data
)
}
/** 上传文件 */
export const uploadFile = (file: File) => {
const formData = new FormData()
formData.append('object', file)
return request.post<GoViewResponse<GoViewFile>>('/Visual/v1/project/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
}
/** 保存项目数据 */
export const saveProjectData = (projectId: string, content: string) => {
const formData = new URLSearchParams()
formData.append('projectId', projectId)
formData.append('content', content)
return request.post<GoViewResponse<GoViewProjectData>>('/Visual/v1/project/save/data', formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,396 @@
<template>
<div class="container">
<search-table
:form-model="formModel"
:form-items="formItems"
:data="tableData"
:columns="columns"
:loading="loading"
:pagination="pagination"
title="工单模板管理"
@update:form-model="handleFormModelUpdate"
@search="handleSearch"
@reset="handleReset"
@page-change="handlePageChange"
@refresh="handleRefresh"
>
<!-- 工具栏额外按钮 -->
<template #toolbar-extra>
<a-button type="primary" @click="handleCreate">
<template #icon>
<icon-plus />
</template>
新建模板
</a-button>
</template>
<!-- 状态 -->
<template #status="{ record }">
<a-tag :color="record.status === 'active' ? 'green' : 'gray'">
{{ record.status === 'active' ? '启用' : '禁用' }}
</a-tag>
</template>
<!-- 类型 -->
<template #type="{ record }">
<a-tag>{{ getTypeText(record.type) }}</a-tag>
</template>
<!-- 优先级 -->
<template #priority="{ record }">
<a-tag :color="getPriorityColor(record.priority)">
{{ getPriorityText(record.priority) }}
</a-tag>
</template>
<!-- 触发类型 -->
<template #trigger_type="{ record }">
<a-tag>{{ getTriggerTypeText(record.trigger_type) }}</a-tag>
</template>
<!-- 自动分配 -->
<template #auto_assign="{ record }">
<a-tag :color="record.auto_assign ? 'green' : 'gray'">
{{ record.auto_assign ? '是' : '否' }}
</a-tag>
</template>
<!-- 自动接单 -->
<template #auto_accept="{ record }">
<a-tag :color="record.auto_accept ? 'green' : 'gray'">
{{ record.auto_accept ? '是' : '否' }}
</a-tag>
</template>
<!-- 创建时间 -->
<template #created_at="{ record }">
{{ formatDate(record.created_at) }}
</template>
<!-- 最后修改 -->
<template #updated_at="{ record }">
{{ formatDate(record.updated_at) }}
</template>
<!-- 操作 -->
<template #actions="{ record }">
<a-space size="small">
<a-button type="text" size="small" @click="handleEdit(record)">
编辑
</a-button>
<a-button
v-if="record.status === 'inactive'"
type="text"
size="small"
@click="handleActivate(record)"
>
启用
</a-button>
<a-button
v-if="record.status === 'active'"
type="text"
size="small"
@click="handleDeactivate(record)"
>
禁用
</a-button>
<a-button
v-if="record.status === 'active'"
type="text"
size="small"
@click="handleCreateTicket(record)"
>
按模板创建
</a-button>
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
删除
</a-button>
</a-space>
</template>
</search-table>
<!-- 新建/编辑模板弹窗 -->
<template-form-dialog
v-model:visible="formDialogVisible"
:template-id="currentTemplateId"
@success="handleFormSuccess"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { IconPlus } from '@arco-design/web-vue/es/icon'
import type { FormItem } from '@/components/search-form/types'
import SearchTable from '@/components/search-table/index.vue'
import { searchFormConfig } from './config/search-form'
import { columns as columnsConfig } from './config/columns'
import {
fetchTemplates,
deleteTemplate,
activateTemplate,
deactivateTemplate,
createTicketByTemplate,
} from '@/api/ops/template'
import TemplateFormDialog from './components/TemplateFormDialog.vue'
// 状态管理
const loading = ref(false)
const tableData = ref<any[]>([])
// 表单模型
const formModel = ref({
status: '',
})
// 分页
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
})
// 表单项配置
const formItems = computed<FormItem[]>(() => searchFormConfig)
// 表格列配置
const columns = computed(() => columnsConfig)
// 表单弹窗
const formDialogVisible = ref(false)
const currentTemplateId = ref<number | undefined>(undefined)
// 获取模板列表
const fetchTemplatesData = async () => {
loading.value = true
try {
const params: any = {
page: pagination.current,
page_size: pagination.pageSize,
}
if (formModel.value.status) {
params.status = formModel.value.status
}
const res = await fetchTemplates(params)
tableData.value = res.details?.items || []
pagination.total = res.details?.total || 0
} catch (error) {
console.error('获取模板列表失败:', error)
Message.error('获取模板列表失败')
tableData.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
// 格式化日期
const formatDate = (dateStr: string) => {
if (!dateStr) return '-'
const date = new Date(dateStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
// 获取类型文本
const getTypeText = (type: string) => {
const typeMap: Record<string, string> = {
incident: '事件',
request: '请求',
change: '变更',
problem: '问题',
consultation: '咨询',
}
return typeMap[type] || '-'
}
// 获取优先级文本
const getPriorityText = (priority: string) => {
const priorityMap: Record<string, string> = {
low: '低',
medium: '中',
high: '高',
critical: '紧急',
}
return priorityMap[priority] || '-'
}
// 获取优先级颜色
const getPriorityColor = (priority: string) => {
const colorMap: Record<string, string> = {
low: 'blue',
medium: 'green',
high: 'orange',
critical: 'red',
}
return colorMap[priority] || 'gray'
}
// 获取触发类型文本
const getTriggerTypeText = (triggerType: string) => {
const typeMap: Record<string, string> = {
manual: '手动',
scheduled: '定时',
event: '事件',
api: 'API',
}
return typeMap[triggerType] || '-'
}
// 搜索
const handleSearch = () => {
pagination.current = 1
fetchTemplatesData()
}
// 处理表单模型更新
const handleFormModelUpdate = (value: any) => {
formModel.value = {
...formModel.value,
...value,
}
}
// 重置
const handleReset = () => {
formModel.value = {
status: '',
}
pagination.current = 1
fetchTemplatesData()
}
// 分页变化
const handlePageChange = (current: number) => {
pagination.current = current
fetchTemplatesData()
}
// 刷新
const handleRefresh = () => {
fetchTemplatesData()
Message.success('数据已刷新')
}
// 新建模板
const handleCreate = () => {
currentTemplateId.value = undefined
formDialogVisible.value = true
}
// 编辑模板
const handleEdit = (record: any) => {
currentTemplateId.value = record.id
formDialogVisible.value = true
}
// 启用模板
const handleActivate = async (record: any) => {
try {
const res = await activateTemplate(record.id)
if (res.code === 0) {
Message.success('启用成功')
fetchTemplatesData()
} else {
Message.error(res.msg || '启用失败')
}
} catch (error) {
console.error('启用模板失败:', error)
Message.error('启用模板失败')
}
}
// 禁用模板
const handleDeactivate = async (record: any) => {
try {
const res = await deactivateTemplate(record.id)
if (res.code === 0) {
Message.success('禁用成功')
fetchTemplatesData()
} else {
Message.error(res.msg || '禁用失败')
}
} catch (error) {
console.error('禁用模板失败:', error)
Message.error('禁用模板失败')
}
}
// 删除模板
const handleDelete = (record: any) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除模板「${record.template_name}」吗?删除后将无法恢复,且可能影响历史工单的引用。`,
okText: '删除',
okButtonProps: { status: 'danger' },
cancelText: '取消',
onOk: async () => {
try {
const res = await deleteTemplate(record.id)
if (res.code === 0) {
Message.success('删除成功')
fetchTemplatesData()
} else {
Message.error(res.msg || '删除失败')
}
} catch (error) {
console.error('删除模板失败:', error)
Message.error('删除模板失败')
}
},
})
}
// 按模板创建工单
const handleCreateTicket = async (record: any) => {
try {
const res = await createTicketByTemplate(record.id)
if (res.code === 0) {
Message.success('工单创建成功')
// 可以在这里跳转到工单详情页
// if (res.details?.id) {
// router.push(`/feedback/ticket/${res.details.id}`)
// }
fetchTemplatesData()
} else {
Message.error(res.msg || '工单创建失败')
}
} catch (error: any) {
console.error('按模板创建工单失败:', error)
Message.error(error.msg || '工单创建失败')
}
}
// 表单提交成功
const handleFormSuccess = () => {
fetchTemplatesData()
}
// 初始化加载数据
onMounted(() => {
fetchTemplatesData()
})
</script>
<script lang="ts">
export default {
name: 'TemplateList',
}
</script>
<style scoped lang="less">
.container {
margin-top: 20px;
}
</style>