This commit is contained in:
ygx
2026-03-28 17:51:21 +08:00
parent 877e3115c4
commit da710158e5
19 changed files with 2720 additions and 2374 deletions

View File

@@ -1,46 +1,92 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
/** 资产分类数据类型 */
export interface AssetCategory {
id: number
name: string
code: string
description?: string
icon?: string
color?: string
parent_id?: number
parent?: AssetCategory
level: number
path?: string
enabled: boolean
sort: number
asset_count: number
remarks?: string
created_by?: string
updated_by?: string
created_at?: string
updated_at?: string
children?: AssetCategory[]
}
/** 分页列表参数 */
export interface CategoryListParams {
page?: number
page_size?: number
sort?: string
order?: 'asc' | 'desc'
keyword?: string
}
/** 创建/更新分类参数 */
export interface CategoryFormData {
id?: number
name: string
code: string
description?: string
icon?: string
color?: string
parent_id?: number | null
enabled?: boolean
sort?: number
remarks?: string
}
/** 获取资产分类列表(分页) */
export const fetchCategoryList = (data?: {
page?: number;
page_size?: number;
keyword?: string;
parent_id?: number;
}) => {
return request.post("/Assets/v1/category/list", data || {});
};
export const fetchCategoryList = (params?: CategoryListParams) => {
return request.post('/Assets/v1/category/list', params || {})
}
/** 获取资产分类详情 */
export const fetchCategoryDetail = (id: number) => {
return request.get(`/Assets/v1/category/detail/${id}`);
};
return request.get(`/Assets/v1/category/detail/${id}`)
}
/** 创建资产分类 */
export const createCategory = (data: any) => {
return request.post("/Assets/v1/category/create", data);
};
export const createCategory = (data: CategoryFormData) => {
return request.post('/Assets/v1/category/create', data)
}
/** 更新资产分类 */
export const updateCategory = (data: any) => {
return request.put("/Assets/v1/category/update", data);
};
export const updateCategory = (data: CategoryFormData) => {
return request.put('/Assets/v1/category/update', data)
}
/** 删除资产分类 */
export const deleteCategory = (id: number) => {
return request.delete(`/Assets/v1/category/delete/${id}`);
};
return request.delete(`/Assets/v1/category/delete/${id}`)
}
/** 获取所有资产分类(用于下拉选择) */
export const fetchAllCategories = () => {
return request.get("/Assets/v1/category/all");
};
export const fetchAllCategories = (keyword?: string) => {
return request.get('/Assets/v1/category/all', { params: { keyword } })
}
/** 获取资产分类树形结构 */
export const fetchCategoryTree = () => {
return request.get("/Assets/v1/category/tree");
};
export const fetchCategoryTree = (keyword?: string) => {
return request.get('/Assets/v1/category/tree', { params: { keyword } })
}
/** 获取指定分类的子分类列表 */
export const fetchCategoryChildren = (id: number) => {
return request.get(`/Assets/v1/category/children/${id}`);
};
return request.get(`/Assets/v1/category/children/${id}`)
}
/** 根据父级分类ID获取分类列表 */
export const fetchCategoryByParent = (parentId: number) => {
return request.get(`/Assets/v1/category/parent/${parentId}`)
}

View File

@@ -32,9 +32,10 @@ export const deleteSupplier = (id: number) => {
};
/** 获取所有供应商(用于下拉选择) */
export const fetchAllSuppliers = (keyword?: string, status?: string) => {
export const fetchAllSuppliers = (keyword?: string, status?: string, pageSize?: number) => {
const params: any = {};
if (keyword) params.keyword = keyword;
if (status) params.status = status;
if (pageSize) params.page_size = pageSize;
return request.get("/Assets/v1/supplier/all", { params });
};

View File

@@ -96,7 +96,7 @@ const OPS: AppRouteRecordRaw = {
// {
// path: 'assets/device',
// name: 'AssetsDevice',
// component: () => import('@/views/ops/pages/assets/device/index.vue'),
// component: () => import('@/views/ops/pages/assets/device/list/index.vue'),
// meta: {
// locale: '设备管理',
// requiresAuth: true,
@@ -104,9 +104,20 @@ const OPS: AppRouteRecordRaw = {
// },
// },
// {
// path: 'assets/device/form',
// name: 'AssetsDeviceForm',
// component: () => import('@/views/ops/pages/assets/device/form/index.vue'),
// meta: {
// locale: '新增/编辑设备',
// requiresAuth: true,
// roles: ['*'],
// hideInMenu: true,
// },
// },
// {
// path: 'assets/supplier',
// name: 'AssetsSupplier',
// component: () => import('@/views/ops/pages/assets/supplier/index.vue'),
// component: () => import('@/views/ops/pages/assets/supplier/list/index.vue'),
// meta: {
// locale: '供应商管理',
// requiresAuth: true,
@@ -114,6 +125,17 @@ const OPS: AppRouteRecordRaw = {
// },
// },
// {
// path: 'assets/supplier/form',
// name: 'AssetsSupplierForm',
// component: () => import('@/views/ops/pages/assets/supplier/form/index.vue'),
// meta: {
// locale: '新增/编辑供应商',
// requiresAuth: true,
// roles: ['*'],
// hideInMenu: true,
// },
// },
// {
// path: 'assets/classify',
// name: 'AssetsClassify',
// component: () => import('@/views/ops/pages/assets/classify/index.vue'),

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,6 @@
<a-checkbox checked="rememberPassword" :model-value="loginConfig.rememberPassword" @change="setRememberPassword as any">
{{ $t('login.form.rememberPassword') }}
</a-checkbox>
<a-link>{{ $t('login.form.forgetPassword') }}</a-link>
</div>
<a-button type="primary" html-type="submit" long :loading="loading">
{{ $t('login.form.login') }}

View File

@@ -1,126 +1,131 @@
<template>
<a-modal
:visible="visible"
v-model:visible="dialogVisible"
title="分类详情"
width="600px"
@cancel="handleCancel"
@update:visible="handleVisibleChange"
:width="600"
:footer="false"
unmount-on-close
>
<a-descriptions :data="detailData" layout="vertical" bordered :column="2" v-if="category">
<a-descriptions-item label="分类名称" :span="1">
{{ category.name }}
<a-spin :loading="loading" style="width: 100%">
<a-descriptions
v-if="categoryDetail"
:column="2"
bordered
size="medium"
>
<a-descriptions-item label="分类名称">
{{ categoryDetail.name }}
</a-descriptions-item>
<a-descriptions-item label="分类编码" :span="1">
{{ category.code }}
<a-descriptions-item label="分类编码">
{{ categoryDetail.code }}
</a-descriptions-item>
<a-descriptions-item label="父级分类" :span="1">
{{ category.parent?.name || '无(一级分类)' }}
</a-descriptions-item>
<a-descriptions-item label="层级" :span="1">
{{ category.level }}
</a-descriptions-item>
<a-descriptions-item label="排序" :span="1">
{{ category.sort }}
</a-descriptions-item>
<a-descriptions-item label="是否启用" :span="1">
<a-tag :color="category.enabled ? 'green' : 'red'">
{{ category.enabled ? '是' : '否' }}
<a-descriptions-item label="层级">
<a-tag :color="getLevelColor(categoryDetail.level)">
{{ categoryDetail.level }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="颜色标识" :span="2">
<div class="color-display">
<div
class="color-block"
:style="{ backgroundColor: category.color || '#ccc' }"
></div>
<span>{{ category.color || '-' }}</span>
</div>
<a-descriptions-item label="父级分类">
{{ categoryDetail.parent?.name || '-' }}
</a-descriptions-item>
<a-descriptions-item label="图标路径" :span="2">
{{ category.icon || '-' }}
<a-descriptions-item label="资产数量">
<a-badge :count="categoryDetail.asset_count" :max-count="999" />
</a-descriptions-item>
<a-descriptions-item label="排序">
{{ categoryDetail.sort }}
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag :color="categoryDetail.enabled ? 'green' : 'red'">
{{ categoryDetail.enabled ? '启用' : '禁用' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="颜色">
<div v-if="categoryDetail.color" style="display: flex; align-items: center; gap: 8px">
<div
:style="{
width: '20px',
height: '20px',
borderRadius: '4px',
backgroundColor: categoryDetail.color,
}"
/>
<span>{{ categoryDetail.color }}</span>
</div>
<span v-else>-</span>
</a-descriptions-item>
<a-descriptions-item label="分类路径">
{{ categoryDetail.path || '-' }}
</a-descriptions-item>
<a-descriptions-item label="描述" :span="2">
{{ category.description || '-' }}
{{ categoryDetail.description || '-' }}
</a-descriptions-item>
<a-descriptions-item label="备注" :span="2">
{{ category.remarks || '-' }}
{{ categoryDetail.remarks || '-' }}
</a-descriptions-item>
<a-descriptions-item label="创建时间" :span="1">
{{ formatDate(category.created_at) }}
<a-descriptions-item label="创建">
{{ categoryDetail.created_by || '-' }}
</a-descriptions-item>
<a-descriptions-item label="更新时间" :span="1">
{{ formatDate(category.updated_at) }}
<a-descriptions-item label="更新">
{{ categoryDetail.updated_by || '-' }}
</a-descriptions-item>
<a-descriptions-item label="创建人" :span="1">
{{ category.created_by || '-' }}
<a-descriptions-item label="创建时间">
{{ formatDateTime(categoryDetail.created_at) }}
</a-descriptions-item>
<a-descriptions-item label="更新人" :span="1">
{{ category.updated_by || '-' }}
<a-descriptions-item label="更新时间">
{{ formatDateTime(categoryDetail.updated_at) }}
</a-descriptions-item>
</a-descriptions>
<a-empty v-else description="暂无数据" />
<a-empty v-else-if="!loading" description="暂无数据" />
</a-spin>
</a-modal>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
interface Category {
id?: number
name?: string
code?: string
description?: string
icon?: string
color?: string
parent_id?: number
parent?: any
level?: number
sort?: number
enabled?: boolean
remarks?: string
created_at?: string
updated_at?: string
created_by?: string
updated_by?: string
}
import { ref, computed, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import { fetchCategoryDetail } from '@/api/ops/assetCategory'
import type { AssetCategory } from '@/api/ops/assetCategory'
interface Props {
visible: boolean
category: Category | null
categoryId?: number
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'update:visible', visible: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 详情数据
const detailData = computed(() => {
if (!props.category) return []
return [
{ label: '分类名称', value: props.category.name },
{ label: '分类编码', value: props.category.code },
{ label: '父级分类', value: props.category.parent?.name || '无(一级分类)' },
{ label: '层级', value: props.category.level },
{ label: '排序', value: props.category.sort },
{ label: '是否启用', value: props.category.enabled ? '是' : '否' },
{ label: '颜色标识', value: props.category.color || '-' },
{ label: '图标路径', value: props.category.icon || '-' },
{ label: '描述', value: props.category.description || '-' },
{ label: '备注', value: props.category.remarks || '-' },
{ label: '创建时间', value: formatDate(props.category.created_at) },
{ label: '更新时间', value: formatDate(props.category.updated_at) },
{ label: '创建人', value: props.category.created_by || '-' },
{ label: '更新人', value: props.category.updated_by || '-' },
]
const props = withDefaults(defineProps<Props>(), {
visible: false,
categoryId: undefined,
})
// 格式化日期
const formatDate = (dateStr?: string) => {
const emit = defineEmits<Emits>()
const loading = ref(false)
const categoryDetail = ref<AssetCategory | null>(null)
const dialogVisible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
})
// 获取层级颜色
const getLevelColor = (level: number) => {
const colors: Record<number, string> = {
1: 'blue',
2: 'green',
3: 'orange',
4: 'purple',
5: 'cyan',
}
return colors[level] || 'gray'
}
// 格式化日期时间
const formatDateTime = (dateStr?: string) => {
if (!dateStr) return '-'
try {
const date = new Date(dateStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
@@ -128,19 +133,45 @@ const formatDate = (dateStr?: string) => {
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
} catch {
return dateStr
}
}
// 取消
const handleCancel = () => {
emit('update:visible', false)
// 加载分类详情
const loadCategoryDetail = async () => {
if (!props.categoryId) return
loading.value = true
try {
const res = await fetchCategoryDetail(props.categoryId)
if (res.code === 0) {
categoryDetail.value = res.details
} else {
Message.error(res.message || '获取分类详情失败')
categoryDetail.value = null
}
} catch (error) {
console.error('获取分类详情失败:', error)
Message.error('获取分类详情失败')
categoryDetail.value = null
} finally {
loading.value = false
}
}
// 处理对话框可见性变化
const handleVisibleChange = (visible: boolean) => {
emit('update:visible', visible)
// 监听 visible 变化
watch(
() => props.visible,
(val) => {
if (val && props.categoryId) {
loadCategoryDetail()
} else {
categoryDetail.value = null
}
}
)
</script>
<script lang="ts">
@@ -148,18 +179,3 @@ export default {
name: 'CategoryDetailDialog',
}
</script>
<style scoped lang="less">
.color-display {
display: flex;
align-items: center;
gap: 8px;
.color-block {
width: 24px;
height: 24px;
border-radius: 4px;
border: 1px solid #d9d9d9;
}
}
</style>

View File

@@ -1,137 +1,96 @@
<template>
<a-modal
:visible="visible"
:title="isEdit ? '编辑分类' : '新分类'"
width="600px"
@ok="handleOk"
v-model:visible="dialogVisible"
:title="isEdit ? '编辑分类' : '新分类'"
:width="520"
:mask-closable="false"
unmount-on-close
@cancel="handleCancel"
@update:visible="handleVisibleChange"
:confirm-loading="submitting"
>
<a-form :model="form" layout="vertical" ref="formRef">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="分类名称"
field="name"
:rules="[{ required: true, message: '请输入分类名称' }]"
@ok="handleOk"
>
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical">
<a-form-item field="name" label="分类名称" required>
<a-input
v-model="form.name"
v-model="formData.name"
placeholder="请输入分类名称"
:max-length="200"
:max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="分类编码"
field="code"
:rules="[{ required: true, message: '请输入分类编码' }]"
>
<a-form-item field="code" label="分类编码" required>
<a-input
v-model="form.code"
v-model="formData.code"
placeholder="请输入分类编码"
:max-length="100"
:max-length="50"
:disabled="isEdit"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="showParentSelector !== false"
label="父级分类"
field="parent_id"
>
<a-select
v-model="form.parent_id"
placeholder="请选择父级分类(不选则为一级分类)"
<a-form-item field="parent_id" label="父级分类">
<a-tree-select
v-model="formData.parent_id"
:data="categoryTreeData"
placeholder="请选择父级分类(不选则为顶级分类)"
allow-clear
allow-search
:disabled="fixedParentId !== undefined"
:filter-option="filterParentOption"
>
<a-option
v-for="item in parentCategories"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
:field-names="{
key: 'id',
title: 'name',
children: 'children',
}"
:load-more="handleLoadMore"
/>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="排序" field="sort">
<a-form-item field="sort" label="排序">
<a-input-number
v-model="form.sort"
placeholder="请输入排序"
v-model="formData.sort"
placeholder="请输入排序"
:min="0"
:max="9999"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否启用" field="enabled">
<a-switch v-model="form.enabled" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="颜色标识" field="color">
<div class="color-picker-wrapper">
<a-form-item field="color" label="颜色">
<a-input
v-model="form.color"
placeholder="请选择颜色标识"
readonly
@click="showColorPicker = !showColorPicker"
v-model="formData.color"
placeholder="请输入颜色值,如 #1890ff"
:max-length="20"
>
<template #prefix>
<div
class="color-preview"
:style="{ backgroundColor: form.color || '#ccc' }"
></div>
:style="{
width: '16px',
height: '16px',
borderRadius: '2px',
backgroundColor: formData.color || '#1890ff',
}"
/>
</template>
</a-input>
<div v-if="showColorPicker" class="color-picker-dropdown">
<div class="color-picker-header">选择颜色</div>
<div class="color-picker-grid">
<div
v-for="color in presetColors"
:key="color"
class="color-item"
:style="{ backgroundColor: color }"
@click="selectColor(color)"
></div>
</div>
<div class="color-picker-custom">
<span>自定义颜色:</span>
<input
type="color"
v-model="form.color"
@change="showColorPicker = false"
/>
</div>
</div>
</div>
</a-form-item>
<a-form-item label="描述" field="description">
<a-form-item field="enabled" label="是否启用">
<a-switch v-model="formData.enabled" />
</a-form-item>
<a-form-item field="description" label="描述">
<a-textarea
v-model="form.description"
placeholder="请输入描述"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
v-model="formData.description"
placeholder="请输入分类描述"
:max-length="200"
:auto-size="{ minRows: 2, maxRows: 4 }"
show-word-limit
/>
</a-form-item>
<a-form-item label="备注" field="remarks">
<a-form-item field="remarks" label="备注">
<a-textarea
v-model="form.remarks"
v-model="formData.remarks"
placeholder="请输入备注"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
:max-length="200"
:auto-size="{ minRows: 2, maxRows: 4 }"
show-word-limit
/>
</a-form-item>
</a-form>
@@ -139,167 +98,176 @@
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { ref, computed, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import { createCategory, updateCategory } from '@/api/ops/assetCategory'
interface Category {
id?: number
name?: string
code?: string
description?: string
icon?: string
color?: string
parent_id?: number
sort?: number
enabled?: boolean
remarks?: string
}
import type { FormInstance } from '@arco-design/web-vue/es/form'
import {
createCategory,
updateCategory,
fetchCategoryTree,
fetchCategoryChildren,
} from '@/api/ops/assetCategory'
import type { AssetCategory, CategoryFormData } from '@/api/ops/assetCategory'
interface Props {
visible: boolean
category: Category | null
parentCategories: any[]
fixedParentId?: number
showParentSelector?: boolean
category?: AssetCategory | null
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'update:visible', visible: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
visible: false,
category: null,
})
const emit = defineEmits<Emits>()
const formRef = ref()
const submitting = ref(false)
const showColorPicker = ref(false)
const formRef = ref<FormInstance>()
const categoryTreeData = ref<any[]>([])
const loading = ref(false)
// 预设颜色
const presetColors = [
'#FF0000', '#FF4500', '#FF8C00', '#FFD700', '#FFFF00',
'#9ACD32', '#32CD32', '#00FF00', '#00FA9A', '#00CED1',
'#1E90FF', '#0000FF', '#8A2BE2', '#9400D3', '#FF00FF',
'#FF1493', '#DC143C', '#B22222', '#8B0000', '#800000',
]
const isEdit = computed(() => !!props.category?.id)
// 表单数据
const form = ref({
const dialogVisible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
})
const defaultFormData = (): CategoryFormData => ({
name: '',
code: '',
description: '',
icon: '',
color: '',
parent_id: undefined as number | undefined,
sort: 0,
color: '#1890ff',
parent_id: null,
enabled: true,
sort: 0,
remarks: '',
})
// 是否为编辑模式
const isEdit = computed(() => !!props.category?.id)
const formData = ref<CategoryFormData>(defaultFormData())
// 父级分类下拉搜索过滤
const filterParentOption = (input: string, option: any) => {
return option.label.toLowerCase().includes(input.toLowerCase())
const rules = {
name: [
{ required: true, message: '分类名称不能为空' },
{ max: 50, message: '分类名称不能超过50个字符' },
],
code: [
{ required: true, message: '分类编码不能为空' },
{ max: 50, message: '分类编码不能超过50个字符' },
{
match: /^[A-Z0-9_]+$/,
message: '分类编码只能包含大写字母、数字和下划线',
},
],
color: [
{
match: /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,
message: '请输入有效的颜色值,如 #1890ff',
},
],
}
// 选择颜色
const selectColor = (color: string) => {
form.value.color = color
showColorPicker.value = false
// 加载分类树
const loadCategoryTree = async () => {
try {
const res = await fetchCategoryTree()
categoryTreeData.value = res.details || []
} catch (error) {
console.error('加载分类树失败:', error)
categoryTreeData.value = []
}
}
// 监听对话框显示状态
// 懒加载子节点
const handleLoadMore = async (node: any) => {
if (node.children && node.children.length > 0) return
try {
const res = await fetchCategoryChildren(node.id)
node.children = res.details || []
} catch (error) {
console.error('加载子分类失败:', error)
node.children = []
}
}
// 重置表单
const resetForm = () => {
formData.value = defaultFormData()
formRef.value?.resetFields()
}
// 监听 visible 变化
watch(
() => props.visible,
(newVal) => {
if (newVal) {
if (props.category && isEdit.value) {
// 编辑模式:填充表单
form.value = {
name: props.category.name || '',
code: props.category.code || '',
async (val) => {
if (val) {
await loadCategoryTree()
if (props.category) {
// 编辑模式,填充数据
formData.value = {
id: props.category.id,
name: props.category.name,
code: props.category.code,
description: props.category.description || '',
icon: props.category.icon || '',
color: props.category.color || '',
parent_id: props.category.parent_id,
color: props.category.color || '#1890ff',
parent_id: props.category.parent_id || null,
enabled: props.category.enabled,
sort: props.category.sort || 0,
enabled: props.category.enabled !== undefined ? props.category.enabled : true,
remarks: props.category.remarks || '',
}
} else {
// 新建模式:重置表单
form.value = {
name: '',
code: '',
description: '',
icon: '',
color: '',
parent_id: props.fixedParentId,
sort: 0,
enabled: true,
remarks: '',
resetForm()
}
}
showColorPicker.value = false
}
}
)
// 确认提交
const handleCancel = () => {
emit('update:visible', false)
resetForm()
}
const handleOk = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) return
submitting.value = true
try {
const data: any = {
name: form.value.name,
code: form.value.code,
description: form.value.description,
icon: form.value.icon,
color: form.value.color,
parent_id: form.value.parent_id,
sort: form.value.sort,
enabled: form.value.enabled,
remarks: form.value.remarks,
}
let res
if (isEdit.value && props.category?.id) {
// 编辑分类
data.id = props.category.id
res = await updateCategory(data)
} else {
// 新建分类
res = await createCategory(data)
}
loading.value = true
const submitData = { ...formData.value }
if (isEdit.value) {
// 编辑
const res = await updateCategory(submitData)
if (res.code === 0) {
Message.success(isEdit.value ? '编辑成功' : '创建成功')
Message.success('更新成功')
emit('success')
emit('update:visible', false)
resetForm()
} else {
Message.error(res.message || (isEdit.value ? '编辑失败' : '创建失败'))
Message.error(res.message || '更新失败')
}
} catch (error) {
Message.error(isEdit.value ? '编辑失败' : '创建失败')
console.error(error)
} finally {
submitting.value = false
}
}
// 取消
const handleCancel = () => {
} else {
// 新建
const res = await createCategory(submitData)
if (res.code === 0) {
Message.success('创建成功')
emit('success')
emit('update:visible', false)
resetForm()
} else {
Message.error(res.message || '创建失败')
}
}
} catch (error: any) {
console.error('提交失败:', error)
Message.error(error.message || '操作失败')
} finally {
loading.value = false
}
// 处理对话框可见性变化
const handleVisibleChange = (visible: boolean) => {
emit('update:visible', visible)
}
</script>
@@ -308,78 +276,3 @@ export default {
name: 'CategoryFormDialog',
}
</script>
<style scoped lang="less">
.color-picker-wrapper {
position: relative;
.color-preview {
width: 16px;
height: 16px;
border-radius: 2px;
border: 1px solid #d9d9d9;
}
.color-picker-dropdown {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 12px;
margin-top: 4px;
width: 280px;
.color-picker-header {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: #262626;
}
.color-picker-grid {
display: grid;
grid-template-columns: repeat(10, 1fr);
gap: 4px;
margin-bottom: 12px;
.color-item {
width: 20px;
height: 20px;
border-radius: 2px;
cursor: pointer;
border: 1px solid #d9d9d9;
&:hover {
transform: scale(1.1);
border-color: #1890ff;
}
}
}
.color-picker-custom {
display: flex;
align-items: center;
gap: 8px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
span {
font-size: 12px;
color: #595959;
}
input[type="color"] {
width: 60px;
height: 28px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
}
}
}
}
</style>

View File

@@ -0,0 +1,69 @@
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
export const columns: TableColumnData[] = [
{
title: '序号',
dataIndex: 'index',
slotName: 'index',
width: 80,
},
{
title: '分类名称',
dataIndex: 'name',
width: 180,
},
{
title: '分类编码',
dataIndex: 'code',
width: 150,
},
{
title: '层级',
dataIndex: 'level',
slotName: 'level',
width: 80,
},
{
title: '父级分类',
dataIndex: 'parent',
slotName: 'parent',
width: 150,
},
{
title: '资产数量',
dataIndex: 'asset_count',
slotName: 'asset_count',
width: 100,
},
{
title: '排序',
dataIndex: 'sort',
width: 80,
},
{
title: '状态',
dataIndex: 'enabled',
slotName: 'enabled',
width: 100,
},
{
title: '描述',
dataIndex: 'description',
ellipsis: true,
tooltip: true,
width: 200,
},
{
title: '创建时间',
dataIndex: 'created_at',
slotName: 'created_at',
width: 180,
},
{
title: '操作',
dataIndex: 'actions',
slotName: 'actions',
width: 320,
fixed: 'right' as const,
},
]

View File

@@ -0,0 +1,10 @@
import type { FormItem } from '@/components/search-form/types'
export const searchFormConfig: FormItem[] = [
{
field: 'keyword',
label: '关键词',
type: 'input',
placeholder: '请输入分类名称或编码',
},
]

View File

@@ -18,7 +18,10 @@
>
<template #toolbar-left>
<a-button type="primary" @click="handleCreate">
新增分类
<template #icon>
<icon-plus />
</template>
新建分类
</a-button>
</template>
@@ -27,29 +30,35 @@
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
</template>
<!-- 层级 -->
<template #level="{ record }">
<a-tag :color="getLevelColor(record.level)">
{{ record.level }}
</a-tag>
</template>
<!-- 父级分类 -->
<template #parent="{ record }">
{{ record.parent?.name || '-' }}
</template>
<!-- 颜色标识 -->
<template #color="{ record }">
<div class="color-cell">
<div
class="color-block"
:style="{ backgroundColor: record.color || '#ccc' }"
></div>
<span>{{ record.color || '-' }}</span>
</div>
<!-- 资产数量 -->
<template #asset_count="{ record }">
<a-badge :count="record.asset_count" :max-count="999" />
</template>
<!-- 是否启用 -->
<!-- 状态 -->
<template #enabled="{ record }">
<a-tag :color="record.enabled ? 'green' : 'red'">
{{ record.enabled ? '' : '' }}
{{ record.enabled ? '启用' : '禁用' }}
</a-tag>
</template>
<!-- 创建时间 -->
<template #created_at="{ record }">
{{ formatDateTime(record.created_at) }}
</template>
<!-- 操作 -->
<template #actions="{ record }">
<a-button type="text" size="small" @click="handleDetail(record)">
@@ -58,29 +67,31 @@
<a-button type="text" size="small" @click="handleEdit(record)">
编辑
</a-button>
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
删除
<a-button type="text" size="small" @click="handleAddChild(record)">
添加子分类
</a-button>
<a-button type="text" size="small" status="success" @click="handleAddChild(record)">
新增
<a-button
type="text"
size="small"
status="danger"
@click="handleDelete(record)"
>
删除
</a-button>
</template>
</search-table>
<!-- 分类表单对话框新增/编辑 -->
<!-- 分类表单对话框 -->
<category-form-dialog
v-model:visible="formVisible"
:category="editingCategory"
:parent-categories="parentCategories"
:fixed-parent-id="fixedParentId"
:show-parent-selector="showParentSelector"
@success="handleFormSuccess"
/>
<!-- 分类详情对话框 -->
<category-detail-dialog
v-model:visible="detailVisible"
:category="currentCategory"
:category-id="currentCategoryId"
/>
</div>
</template>
@@ -91,21 +102,21 @@ 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 {
fetchCategoryList,
deleteCategory,
fetchAllCategories,
fetchCategoryDetail,
} from '@/api/ops/assetCategory'
import type { AssetCategory } from '@/api/ops/assetCategory'
import CategoryFormDialog from './components/CategoryFormDialog.vue'
import CategoryDetailDialog from './components/CategoryDetailDialog.vue'
// 状态管理
const loading = ref(false)
const tableData = ref<any[]>([])
const tableData = ref<AssetCategory[]>([])
const formModel = ref({
keyword: '',
parent_id: '',
})
const pagination = reactive({
@@ -114,114 +125,46 @@ const pagination = reactive({
total: 0,
})
// 父级分类列表(用于下拉选择)
const parentCategories = ref<any[]>([])
// 表单项配置
const formItems = computed<FormItem[]>(() => [
{
field: 'keyword',
label: '关键词',
type: 'input',
placeholder: '请输入分类名称或编码',
},
{
field: 'parent_id',
label: '父级分类',
type: 'select',
placeholder: '请选择父级分类',
options: parentCategories.value.map((item) => ({
label: item.name,
value: item.id,
})),
allowClear: true,
allowSearch: true,
},
])
const formItems = computed<FormItem[]>(() => searchFormConfig)
// 表格列配置
const columns = computed(() => [
{
title: '序号',
dataIndex: 'index',
slotName: 'index',
width: 80,
align: 'center' as const,
},
{
title: '分类名称',
dataIndex: 'name',
ellipsis: true,
tooltip: true,
},
{
title: '分类编码',
dataIndex: 'code',
ellipsis: true,
tooltip: true,
},
{
title: '父级分类',
dataIndex: 'parent',
slotName: 'parent',
ellipsis: true,
tooltip: true,
},
{
title: '层级',
dataIndex: 'level',
width: 80,
align: 'center' as const,
},
{
title: '排序',
dataIndex: 'sort',
width: 80,
align: 'center' as const,
},
{
title: '颜色标识',
dataIndex: 'color',
slotName: 'color',
width: 120,
align: 'center' as const,
},
{
title: '是否启用',
dataIndex: 'enabled',
slotName: 'enabled',
width: 100,
align: 'center' as const,
},
{
title: '操作',
slotName: 'actions',
width: 300,
fixed: 'right' as const,
},
])
const columns = computed(() => columnsConfig)
// 当前选中的分类
const currentCategory = ref<any>(null)
const editingCategory = ref<any>(null)
const currentCategoryId = ref<number | undefined>(undefined)
const editingCategory = ref<AssetCategory | null>(null)
// 对话框可见性
const formVisible = ref(false)
const detailVisible = ref(false)
// 固定的父级分类ID用于新增子分类时
const fixedParentId = ref<number | undefined>(undefined)
// 获取层级颜色
const getLevelColor = (level: number) => {
const colors: Record<number, string> = {
1: 'blue',
2: 'green',
3: 'orange',
4: 'purple',
5: 'cyan',
}
return colors[level] || 'gray'
}
// 是否显示父级分类选择器
const showParentSelector = ref(true)
// 获取父级分类列表(用于下拉选择)
const fetchParentCategories = async () => {
// 格式化日期时间
const formatDateTime = (dateStr?: string) => {
if (!dateStr) return '-'
try {
const res = await fetchAllCategories()
parentCategories.value = res.details || []
} catch (error) {
console.error('获取父级分类列表失败:', error)
const date = new Date(dateStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
} catch {
return dateStr
}
}
@@ -230,11 +173,10 @@ const fetchCategories = async () => {
loading.value = true
try {
const params: any = {
const params = {
page: pagination.current,
page_size: pagination.pageSize,
keyword: formModel.value.keyword || undefined,
parent_id: formModel.value.parent_id || undefined,
}
const res = await fetchCategoryList(params)
@@ -266,7 +208,6 @@ const handleFormModelUpdate = (value: any) => {
const handleReset = () => {
formModel.value = {
keyword: '',
parent_id: '',
}
pagination.current = 1
fetchCategories()
@@ -284,53 +225,38 @@ const handleRefresh = () => {
Message.success('数据已刷新')
}
// 新分类
// 新分类
const handleCreate = () => {
editingCategory.value = null
fixedParentId.value = undefined
showParentSelector.value = false
formVisible.value = true
}
// 新增子分类
const handleAddChild = (record: any) => {
editingCategory.value = null
fixedParentId.value = record.id
showParentSelector.value = true
formVisible.value = true
}
// 编辑分类
const handleEdit = (record: any) => {
console.log('编辑分类:', record)
const handleEdit = (record: AssetCategory) => {
editingCategory.value = record
formVisible.value = true
}
// 添加子分类
const handleAddChild = (record: AssetCategory) => {
editingCategory.value = {
parent_id: record.id,
} as AssetCategory
formVisible.value = true
}
// 详情
const handleDetail = async (record: any) => {
console.log('查看详情:', record)
try {
const res = await fetchCategoryDetail(record.id)
if (res.code === 0) {
currentCategory.value = res.details
const handleDetail = (record: AssetCategory) => {
currentCategoryId.value = record.id
detailVisible.value = true
} else {
Message.error(res.message || '获取详情失败')
}
} catch (error) {
console.error('获取分类详情失败:', error)
Message.error('获取详情失败')
}
}
// 删除分类
const handleDelete = async (record: any) => {
console.log('删除分类:', record)
const handleDelete = async (record: AssetCategory) => {
try {
Modal.confirm({
title: '确认删除',
content: `确认删除分类 ${record.name} 吗?`,
content: `确认删除分类${record.name}吗?如果该分类下存在子分类或资产,将无法删除。`,
onOk: async () => {
const res = await deleteCategory(record.id)
if (res.code === 0) {
@@ -348,21 +274,18 @@ const handleDelete = async (record: any) => {
// 表单成功回调
const handleFormSuccess = () => {
formVisible.value = false
fetchCategories()
fetchParentCategories()
}
// 初始化加载数据
onMounted(() => {
fetchParentCategories()
fetchCategories()
})
</script>
<script lang="ts">
export default {
name: 'AssetClassify',
name: 'AssetCategory',
}
</script>
@@ -370,18 +293,4 @@ export default {
.container {
margin-top: 20px;
}
.color-cell {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
.color-block {
width: 20px;
height: 20px;
border-radius: 4px;
border: 1px solid #d9d9d9;
}
}
</style>

View File

@@ -1,687 +0,0 @@
<template>
<a-modal
:visible="visible"
:title="isEdit ? '编辑设备' : '新增设备'"
width="900px"
@ok="handleOk"
@cancel="handleCancel"
@update:visible="handleVisibleChange"
:confirm-loading="submitting"
>
<a-form :model="form" layout="vertical" ref="formRef">
<a-tabs v-model:active-key="activeTab">
<!-- 基本信息 -->
<a-tab-pane key="basic" title="基本信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="资产名称"
field="asset_name"
:rules="[{ required: true, message: '请输入资产名称' }]"
>
<a-input
v-model="form.asset_name"
placeholder="请输入资产名称"
:max-length="200"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="资产编号"
field="asset_code"
:rules="[{ required: true, message: '请输入资产编号' }]"
>
<a-input
v-model="form.asset_code"
placeholder="请输入资产编号"
:max-length="100"
:disabled="isEdit"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="资产分类" field="category_id">
<a-select
v-model="form.category_id"
placeholder="请选择资产分类"
allow-clear
allow-search
:loading="categoryLoading"
>
<a-option
v-for="item in categoryOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="规格型号" field="model">
<a-input
v-model="form.model"
placeholder="请输入规格型号"
:max-length="200"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="生产厂商" field="manufacturer">
<a-input
v-model="form.manufacturer"
placeholder="请输入生产厂商"
:max-length="200"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="序列号" field="serial_number">
<a-input
v-model="form.serial_number"
placeholder="请输入序列号"
:max-length="100"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="采购日期" field="purchase_date">
<a-date-picker
v-model="form.purchase_date"
placeholder="请选择采购日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="原值(元)" field="original_value">
<a-input-number
v-model="form.original_value"
placeholder="请输入原值"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="供应商" field="supplier_id">
<a-select
v-model="form.supplier_id"
placeholder="请选择供应商"
allow-clear
allow-search
:loading="supplierLoading"
>
<a-option
v-for="item in supplierOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="质保期" field="warranty_period">
<a-input
v-model="form.warranty_period"
placeholder="请输入质保期36个月"
:max-length="50"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="质保到期日期" field="warranty_expiry">
<a-date-picker
v-model="form.warranty_expiry"
placeholder="请选择质保到期日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="资产状态" field="status">
<a-select
v-model="form.status"
placeholder="请选择资产状态"
>
<a-option
v-for="item in assetStatusOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<!-- 使用信息 -->
<a-tab-pane key="usage" title="使用信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="使用部门" field="department">
<a-input
v-model="form.department"
placeholder="请输入使用部门"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="使用人" field="user">
<a-input
v-model="form.user"
placeholder="请输入使用人"
:max-length="50"
/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<!-- 位置信息 -->
<a-tab-pane key="location" title="位置信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="所属数据中心" field="datacenter_id">
<a-select
v-model="form.datacenter_id"
placeholder="请选择数据中心"
allow-clear
allow-search
:loading="datacenterLoading"
@change="handleDatacenterChange"
>
<a-option
v-for="item in datacenterOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="所属楼层" field="floor_id">
<a-select
v-model="form.floor_id"
placeholder="请选择楼层"
allow-clear
allow-search
:loading="floorLoading"
:disabled="!form.datacenter_id"
@change="handleFloorChange"
>
<a-option
v-for="item in floorOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="所属机柜" field="rack_id">
<a-select
v-model="form.rack_id"
placeholder="请选择机柜"
allow-clear
allow-search
:loading="rackLoading"
:disabled="!form.floor_id"
>
<a-option
v-for="item in rackOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="起始U位编号" field="unit_start">
<a-input-number
v-model="form.unit_start"
placeholder="起始U位"
:min="1"
:max="48"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="结束U位编号" field="unit_end">
<a-input-number
v-model="form.unit_end"
placeholder="结束U位"
:min="1"
:max="48"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<!-- 其他信息 -->
<a-tab-pane key="other" title="其他信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="二维码标签" field="qr_code">
<a-input
v-model="form.qr_code"
placeholder="请输入二维码标签"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="RFID标签" field="rfid_tag">
<a-input
v-model="form.rfid_tag"
placeholder="请输入RFID标签"
:max-length="100"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="资产标签号" field="asset_tag">
<a-input
v-model="form.asset_tag"
placeholder="请输入资产标签号"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="技术规格" field="specifications">
<a-input
v-model="form.specifications"
placeholder="请输入技术规格"
:max-length="500"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="资产描述" field="description">
<a-textarea
v-model="form.description"
placeholder="请输入资产描述"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="1000"
/>
</a-form-item>
<a-form-item label="备注" field="remarks">
<a-textarea
v-model="form.remarks"
placeholder="请输入备注"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
/>
</a-form-item>
</a-tab-pane>
</a-tabs>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, computed, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import {
createAsset,
updateAsset,
fetchCategoryOptions,
fetchSupplierOptions,
fetchDatacenterOptions,
fetchFloorOptions,
fetchRackOptions,
assetStatusOptions,
AssetForm,
} from '@/api/ops/asset'
interface Device {
id?: number
asset_name?: string
asset_code?: string
category_id?: number
model?: string
manufacturer?: string
serial_number?: string
purchase_date?: string
original_value?: number
supplier_id?: number
warranty_period?: string
warranty_expiry?: string
department?: string
user?: string
status?: string
location?: string
datacenter_id?: number
floor_id?: number
rack_id?: number
unit_start?: number
unit_end?: number
qr_code?: string
rfid_tag?: string
asset_tag?: string
specifications?: string
description?: string
remarks?: string
}
interface Props {
visible: boolean
device: Device | null
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref()
const submitting = ref(false)
const activeTab = ref('basic')
// 下拉选项
const categoryOptions = ref<any[]>([])
const supplierOptions = ref<any[]>([])
const datacenterOptions = ref<any[]>([])
const floorOptions = ref<any[]>([])
const rackOptions = ref<any[]>([])
// 加载状态
const categoryLoading = ref(false)
const supplierLoading = ref(false)
const datacenterLoading = ref(false)
const floorLoading = ref(false)
const rackLoading = ref(false)
// 是否为编辑模式
const isEdit = computed(() => !!props.device?.id)
// 表单数据
const form = ref<AssetForm>({
asset_name: '',
asset_code: '',
category_id: undefined,
model: '',
manufacturer: '',
serial_number: '',
purchase_date: '',
original_value: undefined,
supplier_id: undefined,
warranty_period: '',
warranty_expiry: '',
department: '',
user: '',
status: 'in_use',
location: '',
datacenter_id: undefined,
floor_id: undefined,
rack_id: undefined,
unit_start: undefined,
unit_end: undefined,
qr_code: '',
rfid_tag: '',
asset_tag: '',
specifications: '',
description: '',
remarks: '',
})
// 加载下拉选项
const loadOptions = async () => {
// 加载资产分类
categoryLoading.value = true
try {
const res = await fetchCategoryOptions()
if (res.code === 0) {
categoryOptions.value = res.details || []
}
} catch (error) {
console.error('加载资产分类失败:', error)
} finally {
categoryLoading.value = false
}
// 加载供应商
supplierLoading.value = true
try {
const res = await fetchSupplierOptions()
if (res.code === 0) {
supplierOptions.value = res.details || []
}
} catch (error) {
console.error('加载供应商失败:', error)
} finally {
supplierLoading.value = false
}
// 加载数据中心
datacenterLoading.value = true
try {
const res = await fetchDatacenterOptions()
if (res.code === 0) {
datacenterOptions.value = res.details?.data || res.details || []
}
} catch (error) {
console.error('加载数据中心失败:', error)
} finally {
datacenterLoading.value = false
}
}
// 数据中心变化时加载楼层
const handleDatacenterChange = async (value: number | undefined) => {
form.value.floor_id = undefined
form.value.rack_id = undefined
floorOptions.value = []
rackOptions.value = []
if (value) {
floorLoading.value = true
try {
const res = await fetchFloorOptions(value)
if (res.code === 0) {
floorOptions.value = res.details || []
}
} catch (error) {
console.error('加载楼层失败:', error)
} finally {
floorLoading.value = false
}
}
}
// 楼层变化时加载机柜
const handleFloorChange = async (value: number | undefined) => {
form.value.rack_id = undefined
rackOptions.value = []
if (value) {
rackLoading.value = true
try {
const res = await fetchRackOptions({
datacenter_id: form.value.datacenter_id,
floor_id: value,
})
if (res.code === 0) {
rackOptions.value = res.details?.data || res.details || []
}
} catch (error) {
console.error('加载机柜失败:', error)
} finally {
rackLoading.value = false
}
}
}
// 监听 device 变化,初始化表单
watch(
() => props.device,
async (newVal) => {
if (newVal) {
form.value = { ...newVal } as AssetForm
// 如果有数据中心,加载楼层
if (newVal.datacenter_id) {
try {
const res = await fetchFloorOptions(newVal.datacenter_id)
if (res.code === 0) {
floorOptions.value = res.details || []
}
} catch (error) {
console.error('加载楼层失败:', error)
}
}
// 如果有楼层,加载机柜
if (newVal.floor_id) {
try {
const res = await fetchRackOptions({
datacenter_id: newVal.datacenter_id,
floor_id: newVal.floor_id,
})
if (res.code === 0) {
rackOptions.value = res.details?.data || res.details || []
}
} catch (error) {
console.error('加载机柜失败:', error)
}
}
} else {
// 重置表单
form.value = {
asset_name: '',
asset_code: '',
category_id: undefined,
model: '',
manufacturer: '',
serial_number: '',
purchase_date: '',
original_value: undefined,
supplier_id: undefined,
warranty_period: '',
warranty_expiry: '',
department: '',
user: '',
status: 'in_use',
location: '',
datacenter_id: undefined,
floor_id: undefined,
rack_id: undefined,
unit_start: undefined,
unit_end: undefined,
qr_code: '',
rfid_tag: '',
asset_tag: '',
specifications: '',
description: '',
remarks: '',
}
floorOptions.value = []
rackOptions.value = []
}
activeTab.value = 'basic'
},
{ immediate: true }
)
// 提交表单
const handleOk = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) {
return
}
submitting.value = true
const data = { ...form.value }
let res
if (isEdit.value) {
res = await updateAsset({ ...data, id: props.device?.id })
} else {
res = await createAsset(data)
}
if (res.code === 0) {
Message.success(isEdit.value ? '更新成功' : '创建成功')
emit('success')
} else {
Message.error(res.message || '操作失败')
}
} catch (error) {
console.error('提交表单失败:', error)
Message.error('操作失败')
} finally {
submitting.value = false
}
}
// 取消
const handleCancel = () => {
emit('update:visible', false)
}
// 处理对话框可见性变化
const handleVisibleChange = (visible: boolean) => {
emit('update:visible', visible)
}
// 初始化
onMounted(() => {
loadOptions()
})
</script>
<script lang="ts">
export default {
name: 'DeviceFormDialog',
}
</script>
<style scoped lang="less">
:deep(.arco-tabs-content) {
padding-top: 16px;
}
</style>

View File

@@ -0,0 +1,794 @@
<template>
<div class="device-form-page">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<a-button type="text" @click="handleBack">
<template #icon><icon-left /></template>
返回列表
</a-button>
<a-divider direction="vertical" />
<h2 class="page-title">{{ pageTitle }}</h2>
</div>
<div class="header-right">
<a-space>
<a-button @click="handleBack">取消</a-button>
<a-button type="primary" :loading="submitting" @click="handleSubmit">
{{ isEdit ? '保存' : '创建' }}
</a-button>
</a-space>
</div>
</div>
<!-- 表单内容 -->
<div class="page-content">
<a-spin :loading="loading" style="width: 100%">
<a-form ref="formRef" :model="form" layout="vertical">
<!-- 基本信息 -->
<a-card class="info-card" title="基本信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="资产名称" field="asset_name" required>
<a-input
v-model="form.asset_name"
placeholder="请输入资产名称"
:max-length="200"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="资产编号" field="asset_code" required>
<a-input
v-model="form.asset_code"
placeholder="请输入资产编号"
:max-length="100"
:disabled="isEdit"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="资产分类" field="category_id">
<a-select
v-model="form.category_id"
placeholder="请选择资产分类"
allow-clear
allow-search
:loading="categoryLoading"
>
<a-option
v-for="item in categoryOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="规格型号" field="model">
<a-input
v-model="form.model"
placeholder="请输入规格型号"
:max-length="200"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="生产厂商" field="manufacturer">
<a-input
v-model="form.manufacturer"
placeholder="请输入生产厂商"
:max-length="200"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="序列号" field="serial_number">
<a-input
v-model="form.serial_number"
placeholder="请输入序列号"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="采购日期" field="purchase_date">
<a-date-picker
v-model="form.purchase_date"
placeholder="请选择采购日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="原值(元)" field="original_value">
<a-input-number
v-model="form.original_value"
placeholder="请输入原值"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="供应商" field="supplier_id">
<a-select
v-model="form.supplier_id"
placeholder="请选择供应商"
allow-clear
allow-search
:loading="supplierLoading"
:filter-option="false"
@search="handleSupplierSearch"
@dropdown-visible-change="handleSupplierDropdownChange"
>
<a-option
v-for="item in supplierOptions"
:key="item.id"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="质保期" field="warranty_period">
<a-input
v-model="form.warranty_period"
placeholder="请输入质保期36个月"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="质保到期日期" field="warranty_expiry">
<a-date-picker
v-model="form.warranty_expiry"
placeholder="请选择质保到期日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="资产状态" field="status">
<a-select v-model="form.status" placeholder="请选择资产状态">
<a-option
v-for="item in assetStatusOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 使用信息 -->
<a-card class="info-card" title="使用信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="使用部门" field="department">
<a-input
v-model="form.department"
placeholder="请输入使用部门"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="使用人" field="user">
<a-input
v-model="form.user"
placeholder="请输入使用人"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 位置信息 -->
<a-card class="info-card" title="位置信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="所属数据中心" field="datacenter_id">
<a-select
v-model="form.datacenter_id"
placeholder="请选择数据中心"
allow-clear
allow-search
:loading="datacenterLoading"
:filter-option="false"
@search="handleDatacenterSearch"
@change="handleDatacenterChange"
>
<a-option
v-for="item in datacenterOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="所属楼层" field="floor_id">
<a-select
v-model="form.floor_id"
placeholder="请选择楼层"
allow-clear
allow-search
:loading="floorLoading"
:disabled="!form.datacenter_id"
:filter-option="false"
@search="handleFloorSearch"
@change="handleFloorChange"
>
<a-option
v-for="item in floorOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="所属机柜" field="rack_id">
<a-select
v-model="form.rack_id"
placeholder="请选择机柜"
allow-clear
allow-search
:loading="rackLoading"
:disabled="!form.floor_id"
:filter-option="false"
@search="handleRackSearch"
>
<a-option
v-for="item in rackOptions"
:key="item.id"
:value="item.id"
>
{{ item.name }} ({{ item.code }})
</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="3">
<a-form-item label="起始U位" field="unit_start">
<a-input-number
v-model="form.unit_start"
placeholder="起始U位"
:min="1"
:max="48"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="3">
<a-form-item label="结束U位" field="unit_end">
<a-input-number
v-model="form.unit_end"
placeholder="结束U位"
:min="1"
:max="48"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 其他信息 -->
<a-card class="info-card" title="其他信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="二维码标签" field="qr_code">
<a-input
v-model="form.qr_code"
placeholder="请输入二维码标签"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="RFID标签" field="rfid_tag">
<a-input
v-model="form.rfid_tag"
placeholder="请输入RFID标签"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="资产标签号" field="asset_tag">
<a-input
v-model="form.asset_tag"
placeholder="请输入资产标签号"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="技术规格" field="specifications">
<a-input
v-model="form.specifications"
placeholder="请输入技术规格"
:max-length="500"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="资产描述" field="description">
<a-textarea
v-model="form.description"
placeholder="请输入资产描述"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="1000"
show-word-limit
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="备注" field="remarks">
<a-textarea
v-model="form.remarks"
placeholder="请输入备注"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
show-word-limit
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
</a-form>
</a-spin>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { Message } from '@arco-design/web-vue'
import type { FormInstance } from '@arco-design/web-vue/es/form'
import {
createAsset,
updateAsset,
fetchAssetDetail,
fetchCategoryOptions,
assetStatusOptions,
AssetForm,
} from '@/api/ops/asset'
import { fetchAllSuppliers } from '@/api/ops/supplier'
import { fetchDatacenterList, fetchRackListByFloor } from '@/api/ops/rack'
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
const router = useRouter()
const route = useRoute()
// 页面状态
const isEdit = ref(false)
const deviceId = ref<number | null>(null)
const pageTitle = ref('新增设备')
const loading = ref(false)
const submitting = ref(false)
const formRef = ref<FormInstance>()
// 下拉选项
const categoryOptions = ref<any[]>([])
const supplierOptions = ref<any[]>([])
const datacenterOptions = ref<{ label: string; value: number }[]>([])
const floorOptions = ref<{ label: string; value: number }[]>([])
const rackOptions = ref<any[]>([])
// 加载状态
const categoryLoading = ref(false)
const supplierLoading = ref(false)
const datacenterLoading = ref(false)
const floorLoading = ref(false)
const rackLoading = ref(false)
// 搜索防抖定时器
let datacenterSearchTimer: number | undefined
let floorSearchTimer: number | undefined
let rackSearchTimer: number | undefined
// 表单数据
const form = ref<AssetForm>({
asset_name: '',
asset_code: '',
category_id: undefined,
model: '',
manufacturer: '',
serial_number: '',
purchase_date: '',
original_value: undefined,
supplier_id: undefined,
warranty_period: '',
warranty_expiry: '',
department: '',
user: '',
status: 'in_use',
location: '',
datacenter_id: undefined,
floor_id: undefined,
rack_id: undefined,
unit_start: undefined,
unit_end: undefined,
qr_code: '',
rfid_tag: '',
asset_tag: '',
specifications: '',
description: '',
remarks: '',
})
// 提取列表数据的辅助函数
const extractList = (res: any): any[] => {
const candidate =
res?.data?.data ?? res?.details?.data ?? res?.data ?? res?.details ?? []
return Array.isArray(candidate) ? candidate : []
}
// 加载下拉选项
const loadOptions = async () => {
// 加载资产分类
categoryLoading.value = true
try {
const res = await fetchCategoryOptions()
if (res.code === 0) {
categoryOptions.value = res.details || []
}
} catch (error) {
console.error('加载资产分类失败:', error)
} finally {
categoryLoading.value = false
}
// 加载全部数据中心(默认列出全部,支持模糊搜索)
await fetchDatacenters()
}
// 加载供应商选项默认20条搜索时全部
const loadSupplierOptions = async (keyword?: string) => {
supplierLoading.value = true
try {
const res = await fetchAllSuppliers(keyword, 'active', keyword ? undefined : 20)
if (res.code === 0) {
supplierOptions.value = res.details?.data || res.details || []
}
} catch (error) {
console.error('加载供应商失败:', error)
} finally {
supplierLoading.value = false
}
}
// 供应商搜索
const handleSupplierSearch = (keyword: string) => {
loadSupplierOptions(keyword)
}
// 供应商下拉框展开时加载
const handleSupplierDropdownChange = (visible: boolean) => {
if (visible && supplierOptions.value.length === 0) {
loadSupplierOptions()
}
}
// 加载数据中心列表
const fetchDatacenters = async (keyword?: string) => {
datacenterLoading.value = true
try {
const res: any = await fetchDatacenterList({ keyword })
const rows = extractList(res)
datacenterOptions.value = rows.map((d: any) => ({
label: d.name || d.code || String(d.id),
value: d.id,
}))
} catch (error) {
console.error('获取数据中心列表失败:', error)
datacenterOptions.value = []
} finally {
datacenterLoading.value = false
}
}
// 数据中心搜索(带防抖)
const handleDatacenterSearch = (keyword: string) => {
if (datacenterSearchTimer) {
window.clearTimeout(datacenterSearchTimer)
}
datacenterSearchTimer = window.setTimeout(() => {
fetchDatacenters(keyword?.trim() || undefined)
}, 300)
}
// 加载楼层列表
const fetchFloors = async (keyword?: string) => {
if (!form.value.datacenter_id) {
floorOptions.value = []
return
}
floorLoading.value = true
try {
const res: any = await fetchFloorListByDatacenter(form.value.datacenter_id, {
name: keyword || undefined,
})
const rows = extractList(res)
floorOptions.value = rows.map((floor: any) => ({
label: floor.name || floor.code || String(floor.id),
value: floor.id,
}))
} catch (error) {
console.error('获取楼层列表失败:', error)
floorOptions.value = []
} finally {
floorLoading.value = false
}
}
// 楼层搜索(带防抖)
const handleFloorSearch = (keyword: string) => {
if (!form.value.datacenter_id) return
if (floorSearchTimer) {
window.clearTimeout(floorSearchTimer)
}
floorSearchTimer = window.setTimeout(() => {
fetchFloors(keyword?.trim() || undefined)
}, 300)
}
// 加载机柜列表
const fetchRacks = async (keyword?: string) => {
if (!form.value.floor_id) {
rackOptions.value = []
return
}
rackLoading.value = true
try {
const res: any = await fetchRackListByFloor(form.value.floor_id, { name: keyword })
rackOptions.value = extractList(res)
} catch (error) {
console.error('获取机柜列表失败:', error)
rackOptions.value = []
} finally {
rackLoading.value = false
}
}
// 机柜搜索(带防抖)
const handleRackSearch = (keyword: string) => {
if (!form.value.floor_id) return
if (rackSearchTimer) {
window.clearTimeout(rackSearchTimer)
}
rackSearchTimer = window.setTimeout(() => {
fetchRacks(keyword?.trim() || undefined)
}, 300)
}
// 数据中心变化时加载楼层
const handleDatacenterChange = async (value: number | undefined) => {
form.value.floor_id = undefined
form.value.rack_id = undefined
floorOptions.value = []
rackOptions.value = []
if (value) {
await fetchFloors()
}
}
// 楼层变化时加载机柜
const handleFloorChange = async (value: number | undefined) => {
form.value.rack_id = undefined
rackOptions.value = []
if (value) {
await fetchRacks()
}
}
// 初始化页面
const initPage = async () => {
await loadOptions()
const id = route.query.id
if (id) {
isEdit.value = true
deviceId.value = Number(id)
pageTitle.value = '编辑设备'
await loadDeviceDetail()
}
}
// 加载设备详情
const loadDeviceDetail = async () => {
if (!deviceId.value) return
loading.value = true
try {
const res = await fetchAssetDetail(deviceId.value)
if (res.code === 0) {
form.value = { ...res.details } as AssetForm
// 如果有供应商,加载供应商选项
if (res.details.supplier_id) {
try {
const supplierRes = await fetchAllSuppliers()
if (supplierRes.code === 0) {
supplierOptions.value = supplierRes.details?.data || supplierRes.details || []
}
} catch (error) {
console.error('加载供应商失败:', error)
}
}
// 如果有数据中心,加载楼层
if (res.details.datacenter_id) {
try {
const floorRes: any = await fetchFloorListByDatacenter(res.details.datacenter_id)
const rows = extractList(floorRes)
floorOptions.value = rows.map((floor: any) => ({
label: floor.name || floor.code || String(floor.id),
value: floor.id,
}))
} catch (error) {
console.error('加载楼层失败:', error)
}
}
// 如果有楼层,加载机柜
if (res.details.floor_id) {
try {
const rackRes: any = await fetchRackListByFloor(res.details.floor_id)
rackOptions.value = extractList(rackRes)
} catch (error) {
console.error('加载机柜失败:', error)
}
}
} else {
Message.error(res.message || '获取详情失败')
router.push('/ops/assets/device')
}
} catch (error) {
console.error('获取设备详情失败:', error)
Message.error('获取详情失败')
router.push('/ops/assets/device')
} finally {
loading.value = false
}
}
// 返回上一级
const handleBack = () => {
router.push('/assets/device')
}
// 提交表单
const handleSubmit = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) {
return
}
submitting.value = true
const data = { ...form.value }
let res
if (isEdit.value && deviceId.value) {
res = await updateAsset({ ...data, id: deviceId.value })
} else {
res = await createAsset(data)
}
if (res.code === 0) {
Message.success(isEdit.value ? '更新成功' : '创建成功')
handleBack()
} else {
Message.error(res.message || '操作失败')
}
} catch (error) {
console.error('提交表单失败:', error)
Message.error('操作失败')
} finally {
submitting.value = false
}
}
// 初始化
onMounted(() => {
initPage()
})
</script>
<script lang="ts">
export default {
name: 'DeviceForm',
}
</script>
<style scoped lang="less">
.device-form-page {
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--color-fill-2);
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
flex-shrink: 0;
.header-left {
display: flex;
align-items: center;
.page-title {
margin: 0;
font-size: 18px;
font-weight: 500;
}
}
}
.page-content {
flex: 1;
padding: 20px;
overflow: auto;
}
.info-card {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
// 表单项样式优化
:deep(.arco-form-item) {
margin-bottom: 16px;
}
:deep(.arco-card) {
.arco-card-header {
border-bottom: 1px solid var(--color-border);
}
}
</style>

View File

@@ -20,9 +20,6 @@
<a-button type="primary" @click="handleCreate">
新增设备
</a-button>
<a-button @click="handleExport">
导出
</a-button>
</template>
<!-- 序号 -->
@@ -66,13 +63,6 @@
</template>
</search-table>
<!-- 设备表单对话框新增/编辑 -->
<device-form-dialog
v-model:visible="formVisible"
:device="editingDevice"
@success="handleFormSuccess"
/>
<!-- 设备详情对话框 -->
<device-detail-dialog
v-model:visible="detailVisible"
@@ -83,6 +73,7 @@
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Message, Modal } from '@arco-design/web-vue'
import type { FormItem } from '@/components/search-form/types'
import SearchTable from '@/components/search-table/index.vue'
@@ -94,8 +85,9 @@ import {
getAssetStatusColor,
assetStatusOptions,
} from '@/api/ops/asset'
import DeviceFormDialog from './components/DeviceFormDialog.vue'
import DeviceDetailDialog from './components/DeviceDetailDialog.vue'
import DeviceDetailDialog from '../components/DeviceDetailDialog.vue'
const router = useRouter()
//
const loading = ref(false)
@@ -204,17 +196,15 @@ const columns = computed(() => [
{
title: '操作',
slotName: 'actions',
width: 280,
width: 220,
fixed: 'right' as const,
},
])
//
const currentDevice = ref<any>(null)
const editingDevice = ref<any>(null)
//
const formVisible = ref(false)
const detailVisible = ref(false)
//
@@ -276,16 +266,17 @@ const handleRefresh = () => {
Message.success('数据已刷新')
}
//
// -
const handleCreate = () => {
editingDevice.value = null
formVisible.value = true
router.push('/assets/device/form')
}
//
// -
const handleEdit = (record: any) => {
editingDevice.value = record
formVisible.value = true
router.push({
path: '/assets/device/form',
query: { id: record.id },
})
}
//
@@ -353,12 +344,6 @@ const handleExport = async () => {
}
}
//
const handleFormSuccess = () => {
formVisible.value = false
fetchDevices()
}
//
const formatMoney = (value: number | string | null | undefined) => {
if (value === null || value === undefined || value === '') return '-'
@@ -389,7 +374,7 @@ onMounted(() => {
<script lang="ts">
export default {
name: 'DeviceManage',
name: 'DeviceList',
}
</script>

View File

@@ -1,464 +0,0 @@
<template>
<a-modal
:visible="visible"
:title="isEdit ? '编辑供应商' : '新增供应商'"
width="800px"
@ok="handleOk"
@cancel="handleCancel"
@update:visible="handleVisibleChange"
:confirm-loading="submitting"
>
<a-form :model="form" layout="vertical" ref="formRef">
<a-tabs v-model:active-key="activeTab">
<!-- 基本信息 -->
<a-tab-pane key="basic" title="基本信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="供应商名称"
field="name"
:rules="[{ required: true, message: '请输入供应商名称' }]"
>
<a-input
v-model="form.name"
placeholder="请输入供应商名称"
:max-length="200"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="供应商编码"
field="code"
:rules="[{ required: true, message: '请输入供应商编码' }]"
>
<a-input
v-model="form.code"
placeholder="请输入供应商编码"
:max-length="100"
:disabled="isEdit"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="简称" field="short_name">
<a-input
v-model="form.short_name"
placeholder="请输入简称"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="供应商类型"
field="supplier_type"
:rules="[{ required: true, message: '请选择供应商类型' }]"
>
<a-select
v-model="form.supplier_type"
placeholder="请选择供应商类型"
allow-clear
>
<a-option value="生产商">生产商</a-option>
<a-option value="代理商">代理商</a-option>
<a-option value="经销商">经销商</a-option>
<a-option value="服务商">服务商</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="供应商级别" field="supplier_level">
<a-select
v-model="form.supplier_level"
placeholder="请选择供应商级别"
allow-clear
>
<a-option value="A">A</a-option>
<a-option value="B">B</a-option>
<a-option value="C">C</a-option>
<a-option value="D">D</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="状态"
field="status"
:rules="[{ required: true, message: '请选择状态' }]"
>
<a-select
v-model="form.status"
placeholder="请选择状态"
>
<a-option value="active">合作中</a-option>
<a-option value="paused">暂停合作</a-option>
<a-option value="blacklist">黑名单</a-option>
<a-option value="inactive">停止合作</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<!-- 联系信息 -->
<a-tab-pane key="contact" title="联系信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="联系人"
field="contact_person"
:rules="[{ required: true, message: '请输入联系人' }]"
>
<a-input
v-model="form.contact_person"
placeholder="请输入联系人"
:max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="联系电话"
field="contact_phone"
:rules="[{ required: true, message: '请输入联系电话' }]"
>
<a-input
v-model="form.contact_phone"
placeholder="请输入联系电话"
:max-length="20"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="备用联系电话"
field="contact_mobile"
:rules="[{ required: true, message: '请输入备用联系电话' }]"
>
<a-input
v-model="form.contact_mobile"
placeholder="请输入备用联系电话"
:max-length="20"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="联系邮箱"
field="contact_email"
:rules="[
{ required: true, message: '请输入联系邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' },
]"
>
<a-input
v-model="form.contact_email"
placeholder="请输入联系邮箱"
:max-length="100"
/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<!-- 地址信息 -->
<a-tab-pane key="address" title="地址信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="省份" field="province">
<a-input
v-model="form.province"
placeholder="请输入省份"
:max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="城市" field="city">
<a-input
v-model="form.city"
placeholder="请输入城市"
:max-length="50"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="邮编" field="postal_code">
<a-input
v-model="form.postal_code"
placeholder="请输入邮编"
:max-length="10"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="详细地址" field="address">
<a-input
v-model="form.address"
placeholder="请输入详细地址"
:max-length="200"
/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<!-- 企业信息 -->
<a-tab-pane key="company" title="企业信息">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="法定代表人" field="legal_representative">
<a-input
v-model="form.legal_representative"
placeholder="请输入法定代表人"
:max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="注册资本" field="registered_capital">
<a-input
v-model="form.registered_capital"
placeholder="请输入注册资本"
:max-length="50"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="营业执照号" field="business_license">
<a-input
v-model="form.business_license"
placeholder="请输入营业执照号"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="税号" field="tax_id">
<a-input
v-model="form.tax_id"
placeholder="请输入税号"
:max-length="50"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="企业类型" field="company_type">
<a-input
v-model="form.company_type"
placeholder="请输入企业类型"
:max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="官网地址" field="website">
<a-input
v-model="form.website"
placeholder="请输入官网地址"
:max-length="200"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="供应商描述" field="description">
<a-textarea
v-model="form.description"
placeholder="请输入供应商描述"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
/>
</a-form-item>
</a-tab-pane>
</a-tabs>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { Message } from '@arco-design/web-vue'
import { createSupplier, updateSupplier } from '@/api/ops/supplier'
interface Supplier {
id?: number
name?: string
code?: string
short_name?: string
description?: string
contact_person?: string
contact_phone?: string
contact_email?: string
contact_mobile?: string
province?: string
city?: string
address?: string
postal_code?: string
website?: string
legal_representative?: string
registered_capital?: string
business_license?: string
tax_id?: string
company_type?: string
supplier_type?: string
supplier_level?: string
status?: string
}
interface Props {
visible: boolean
supplier: Supplier | null
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref()
const submitting = ref(false)
const activeTab = ref('basic')
// 是否为编辑模式
const isEdit = computed(() => !!props.supplier?.id)
// 表单数据
const form = ref<Supplier>({
name: '',
code: '',
short_name: '',
description: '',
contact_person: '',
contact_phone: '',
contact_email: '',
contact_mobile: '',
province: '',
city: '',
address: '',
postal_code: '',
website: '',
legal_representative: '',
registered_capital: '',
business_license: '',
tax_id: '',
company_type: '',
supplier_type: '',
supplier_level: '',
status: 'active',
})
// 监听 supplier 变化,初始化表单
watch(
() => props.supplier,
(newVal) => {
if (newVal) {
form.value = { ...newVal }
} else {
// 重置表单
form.value = {
name: '',
code: '',
short_name: '',
description: '',
contact_person: '',
contact_phone: '',
contact_email: '',
contact_mobile: '',
province: '',
city: '',
address: '',
postal_code: '',
website: '',
legal_representative: '',
registered_capital: '',
business_license: '',
tax_id: '',
company_type: '',
supplier_type: '',
supplier_level: '',
status: 'active',
}
}
activeTab.value = 'basic'
},
{ immediate: true }
)
// 提交表单
const handleOk = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) {
return
}
submitting.value = true
const data = { ...form.value }
let res
if (isEdit.value) {
res = await updateSupplier({ ...data, id: props.supplier?.id })
} else {
res = await createSupplier(data)
}
if (res.code === 0) {
Message.success(isEdit.value ? '更新成功' : '创建成功')
emit('success')
} else {
Message.error(res.message || '操作失败')
}
} catch (error) {
console.error('提交表单失败:', error)
Message.error('操作失败')
} finally {
submitting.value = false
}
}
// 取消
const handleCancel = () => {
emit('update:visible', false)
}
// 处理对话框可见性变化
const handleVisibleChange = (visible: boolean) => {
emit('update:visible', visible)
}
</script>
<script lang="ts">
export default {
name: 'SupplierFormDialog',
}
</script>
<style scoped lang="less">
:deep(.arco-tabs-content) {
padding-top: 16px;
}
</style>

View File

@@ -0,0 +1,490 @@
<template>
<div class="supplier-form-page">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<a-button type="text" @click="handleBack">
<template #icon><icon-left /></template>
返回列表
</a-button>
<a-divider direction="vertical" />
<h2 class="page-title">{{ pageTitle }}</h2>
</div>
<div class="header-right">
<a-space>
<a-button @click="handleBack">取消</a-button>
<a-button type="primary" :loading="submitting" @click="handleSubmit">
{{ isEdit ? '保存' : '创建' }}
</a-button>
</a-space>
</div>
</div>
<!-- 表单内容 -->
<div class="page-content">
<a-spin :loading="loading" style="width: 100%">
<a-form ref="formRef" :model="form" layout="vertical">
<!-- 基本信息 -->
<a-card class="info-card" title="基本信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="供应商名称" field="name" required>
<a-input
v-model="form.name"
placeholder="请输入供应商名称"
:max-length="200"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="供应商编码" field="code" required>
<a-input
v-model="form.code"
placeholder="请输入供应商编码"
:max-length="100"
:disabled="isEdit"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="简称" field="short_name">
<a-input
v-model="form.short_name"
placeholder="请输入简称"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="供应商类型" field="supplier_type" required>
<a-select
v-model="form.supplier_type"
placeholder="请选择供应商类型"
allow-clear
>
<a-option value="生产商">生产商</a-option>
<a-option value="代理商">代理商</a-option>
<a-option value="经销商">经销商</a-option>
<a-option value="服务商">服务商</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="供应商级别" field="supplier_level">
<a-select
v-model="form.supplier_level"
placeholder="请选择供应商级别"
allow-clear
>
<a-option value="A">A</a-option>
<a-option value="B">B</a-option>
<a-option value="C">C</a-option>
<a-option value="D">D</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="状态" field="status" required>
<a-select v-model="form.status" placeholder="请选择状态">
<a-option value="active">合作中</a-option>
<a-option value="paused">暂停合作</a-option>
<a-option value="blacklist">黑名单</a-option>
<a-option value="inactive">停止合作</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 联系信息 -->
<a-card class="info-card" title="联系信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="联系人" field="contact_person" required>
<a-input
v-model="form.contact_person"
placeholder="请输入联系人"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="联系电话" field="contact_phone" required>
<a-input
v-model="form.contact_phone"
placeholder="请输入联系电话"
:max-length="20"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="备用联系电话" field="contact_mobile" required>
<a-input
v-model="form.contact_mobile"
placeholder="请输入备用联系电话"
:max-length="20"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="联系邮箱" field="contact_email" required>
<a-input
v-model="form.contact_email"
placeholder="请输入联系邮箱"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 地址信息 -->
<a-card class="info-card" title="地址信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="省份" field="province">
<a-input
v-model="form.province"
placeholder="请输入省份"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="城市" field="city">
<a-input
v-model="form.city"
placeholder="请输入城市"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="邮编" field="postal_code">
<a-input
v-model="form.postal_code"
placeholder="请输入邮编"
:max-length="10"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="详细地址" field="address">
<a-input
v-model="form.address"
placeholder="请输入详细地址"
:max-length="200"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 企业信息 -->
<a-card class="info-card" title="企业信息">
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="法定代表人" field="legal_representative">
<a-input
v-model="form.legal_representative"
placeholder="请输入法定代表人"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="注册资本" field="registered_capital">
<a-input
v-model="form.registered_capital"
placeholder="请输入注册资本"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="营业执照号" field="business_license">
<a-input
v-model="form.business_license"
placeholder="请输入营业执照号"
:max-length="100"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="税号" field="tax_id">
<a-input
v-model="form.tax_id"
placeholder="请输入税号"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="企业类型" field="company_type">
<a-input
v-model="form.company_type"
placeholder="请输入企业类型"
:max-length="50"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="官网地址" field="website">
<a-input
v-model="form.website"
placeholder="请输入官网地址"
:max-length="200"
allow-clear
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="供应商描述" field="description">
<a-textarea
v-model="form.description"
placeholder="请输入供应商描述"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
show-word-limit
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
</a-form>
</a-spin>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { Message } from '@arco-design/web-vue'
import type { FormInstance } from '@arco-design/web-vue/es/form'
import {
createSupplier,
updateSupplier,
fetchSupplierDetail,
} from '@/api/ops/supplier'
interface Supplier {
id?: number
name?: string
code?: string
short_name?: string
description?: string
contact_person?: string
contact_phone?: string
contact_email?: string
contact_mobile?: string
province?: string
city?: string
address?: string
postal_code?: string
website?: string
legal_representative?: string
registered_capital?: string
business_license?: string
tax_id?: string
company_type?: string
supplier_type?: string
supplier_level?: string
status?: string
}
const router = useRouter()
const route = useRoute()
// 页面状态
const isEdit = ref(false)
const supplierId = ref<number | null>(null)
const pageTitle = ref('新增供应商')
const loading = ref(false)
const submitting = ref(false)
const formRef = ref<FormInstance>()
// 表单数据
const form = ref<Supplier>({
name: '',
code: '',
short_name: '',
description: '',
contact_person: '',
contact_phone: '',
contact_email: '',
contact_mobile: '',
province: '',
city: '',
address: '',
postal_code: '',
website: '',
legal_representative: '',
registered_capital: '',
business_license: '',
tax_id: '',
company_type: '',
supplier_type: '',
supplier_level: '',
status: 'active',
})
// 初始化页面
const initPage = async () => {
const id = route.query.id
if (id) {
isEdit.value = true
supplierId.value = Number(id)
pageTitle.value = '编辑供应商'
await loadSupplierDetail()
}
}
// 加载供应商详情
const loadSupplierDetail = async () => {
if (!supplierId.value) return
loading.value = true
try {
const res = await fetchSupplierDetail(supplierId.value)
if (res.code === 0) {
form.value = { ...res.details }
} else {
Message.error(res.message || '获取详情失败')
router.push('/assets/supplier')
}
} catch (error) {
console.error('获取供应商详情失败:', error)
Message.error('获取详情失败')
router.push('/assets/supplier')
} finally {
loading.value = false
}
}
// 返回上一级
const handleBack = () => {
router.push('/assets/supplier')
}
// 提交表单
const handleSubmit = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) {
return
}
submitting.value = true
const data = { ...form.value }
let res
if (isEdit.value && supplierId.value) {
res = await updateSupplier({ ...data, id: supplierId.value })
} else {
res = await createSupplier(data)
}
if (res.code === 0) {
Message.success(isEdit.value ? '更新成功' : '创建成功')
handleBack()
} else {
Message.error(res.message || '操作失败')
}
} catch (error) {
console.error('提交表单失败:', error)
Message.error('操作失败')
} finally {
submitting.value = false
}
}
// 初始化
onMounted(() => {
initPage()
})
</script>
<script lang="ts">
export default {
name: 'SupplierForm',
}
</script>
<style scoped lang="less">
.supplier-form-page {
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--color-fill-2);
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
flex-shrink: 0;
.header-left {
display: flex;
align-items: center;
.page-title {
margin: 0;
font-size: 18px;
font-weight: 500;
}
}
}
.page-content {
flex: 1;
padding: 20px;
overflow: auto;
}
.info-card {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
// 表单项样式优化
:deep(.arco-form-item) {
margin-bottom: 16px;
}
:deep(.arco-card) {
.arco-card-header {
border-bottom: 1px solid var(--color-border);
}
}
</style>

View File

@@ -58,13 +58,6 @@
</template>
</search-table>
<!-- 供应商表单对话框新增/编辑 -->
<supplier-form-dialog
v-model:visible="formVisible"
:supplier="editingSupplier"
@success="handleFormSuccess"
/>
<!-- 供应商详情对话框 -->
<supplier-detail-dialog
v-model:visible="detailVisible"
@@ -75,6 +68,7 @@
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Message, Modal } from '@arco-design/web-vue'
import type { FormItem } from '@/components/search-form/types'
import SearchTable from '@/components/search-table/index.vue'
@@ -83,8 +77,9 @@ import {
deleteSupplier,
fetchSupplierDetail,
} from '@/api/ops/supplier'
import SupplierFormDialog from './components/SupplierFormDialog.vue'
import SupplierDetailDialog from './components/SupplierDetailDialog.vue'
import SupplierDetailDialog from '../components/SupplierDetailDialog.vue'
const router = useRouter()
//
const statusOptions = [
@@ -200,10 +195,8 @@ const columns = computed(() => [
//
const currentSupplier = ref<any>(null)
const editingSupplier = ref<any>(null)
//
const formVisible = ref(false)
const detailVisible = ref(false)
//
@@ -265,16 +258,17 @@ const handleRefresh = () => {
Message.success('数据已刷新')
}
//
// -
const handleCreate = () => {
editingSupplier.value = null
formVisible.value = true
router.push('/assets/supplier/form')
}
//
// - query id
const handleEdit = (record: any) => {
editingSupplier.value = record
formVisible.value = true
router.push({
path: '/assets/supplier/form',
query: { id: record.id },
})
}
//
@@ -314,12 +308,6 @@ const handleDelete = async (record: any) => {
}
}
//
const handleFormSuccess = () => {
formVisible.value = false
fetchSuppliers()
}
//
const getStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
@@ -373,7 +361,7 @@ onMounted(() => {
<script lang="ts">
export default {
name: 'SupplierManage',
name: 'SupplierList',
}
</script>

View File

@@ -146,7 +146,7 @@
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { Message, Modal } from '@arco-design/web-vue'
import dayjs from 'dayjs'
import SearchTable from '@/components/search-table/index.vue'
import type { FormItem } from '@/components/search-form/types'
@@ -378,7 +378,16 @@ const handleView = (record: ReviewListItem) => {
}
// 审核通过
const handleApprove = async (record: ReviewListItem) => {
const handleApprove = (record: ReviewListItem) => {
const resourceTitle = getResourceTitle(record)
const resourceTypeText = getResourceTypeText(record.type)
Modal.confirm({
title: '确认审核通过',
content: `确定要通过该${resourceTypeText}的审核吗?\n标题${resourceTitle}`,
okText: '确认通过',
cancelText: '取消',
onOk: async () => {
try {
loading.value = true
const res: any = await approveReview({
@@ -397,6 +406,8 @@ const handleApprove = async (record: ReviewListItem) => {
} finally {
loading.value = false
}
},
})
}
// 拒绝

View File

@@ -216,7 +216,7 @@ const handleOk = async () => {
res = await createCategory(data)
}
if (res.code === 200) {
if (res.code === 0) {
Message.success(isEdit.value ? '编辑成功' : '创建成功')
emit('success')
emit('update:visible', false)

View File

@@ -6,6 +6,7 @@
:data="tableData"
:columns="columns"
:loading="loading"
:pagination="pagination"
title="分类管理"
search-button-text="查询"
reset-button-text="重置"
@@ -13,6 +14,8 @@
@search="handleSearch"
@reset="handleReset"
@refresh="handleRefresh"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #toolbar-left>
<a-button type="primary" @click="handleCreate">
@@ -66,12 +69,20 @@ import CategoryFormDialog from './components/CategoryFormDialog.vue'
// 状态管理
const loading = ref(false)
const tableData = ref<Category[]>([])
const allData = ref<Category[]>([]) // 存储全量数据
const tableData = ref<Category[]>([]) // 当前页数据
const formModel = ref({
keyword: '',
type: '',
})
// 分页状态
const pagination = ref({
current: 1,
pageSize: 20,
total: 0,
})
// 表单项配置
const formItems = computed<FormItem[]>(() => [
{
@@ -122,6 +133,12 @@ const columns = computed(() => [
width: 120,
align: 'center' as const,
},
{
title: '排序',
dataIndex: 'sort_order',
width: 80,
align: 'center' as const,
},
{
title: '备注信息',
dataIndex: 'remarks',
@@ -162,6 +179,14 @@ const getTypeColor = (type: string) => {
return colorMap[type] || 'gray'
}
// 更新表格数据(前端分页)
const updateTableData = () => {
const { current, pageSize } = pagination.value
const start = (current - 1) * pageSize
const end = start + pageSize
tableData.value = allData.value.slice(start, end)
}
// 获取分类列表
const fetchCategories = async () => {
loading.value = true
@@ -185,15 +210,24 @@ const fetchCategories = async () => {
)
}
tableData.value = data
// 保存全量数据并更新分页
allData.value = data
pagination.value.total = data.length
// 重置到第一页
pagination.value.current = 1
updateTableData()
} else {
Message.error(res.message || '获取分类列表失败')
allData.value = []
tableData.value = []
pagination.value.total = 0
}
} catch (error) {
console.error('获取分类列表失败:', error)
Message.error('获取分类列表失败')
allData.value = []
tableData.value = []
pagination.value.total = 0
} finally {
loading.value = false
}
@@ -224,6 +258,19 @@ const handleRefresh = () => {
Message.success('数据已刷新')
}
// 分页切换
const handlePageChange = (current: number) => {
pagination.value.current = current
updateTableData()
}
// 每页条数切换
const handlePageSizeChange = (pageSize: number) => {
pagination.value.current = 1
pagination.value.pageSize = pageSize
updateTableData()
}
// 新增分类
const handleCreate = () => {
editingCategory.value = null
@@ -244,7 +291,7 @@ const handleDelete = async (record: Category) => {
onOk: async () => {
try {
const res: any = await deleteCategory(record.id)
if (res.code === 200) {
if (res.code === 0) {
Message.success('删除成功')
fetchCategories()
} else {