feat
This commit is contained in:
133
src/api/ops/goview.ts
Normal file
133
src/api/ops/goview.ts
Normal 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
396
src/views/ops/pages/feedback/template/index.vue
Normal file
396
src/views/ops/pages/feedback/template/index.vue
Normal 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>
|
||||||
Reference in New Issue
Block a user