在资产管理、SKU管理和分类管理中新增操作日志查看功能,支持查看各实体的操作历史记录,同时新增操作日志API接口定义包含查询参数和日志信息类型
This commit is contained in:
@@ -191,3 +191,32 @@ export function stockOperation(data: StockOperationParams) {
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 操作日志查询参数
|
||||
export interface LogQueryParams {
|
||||
collection_id: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
// 操作日志信息
|
||||
export interface OperationLogInfo {
|
||||
id: string;
|
||||
service_name: string;
|
||||
collection: string;
|
||||
collection_id: string[];
|
||||
operation: string;
|
||||
creator: string;
|
||||
createdAt: string;
|
||||
data: { FieldName: string; FieldValue: any }[] | null;
|
||||
ip_address: string;
|
||||
}
|
||||
|
||||
// 查询操作日志
|
||||
export function listLogs(params: LogQueryParams) {
|
||||
return newService({
|
||||
url: '/assets/log/listLogs',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,10 +78,11 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<el-table-column label="操作" width="220" align="center">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text type="primary" @click="onEditSku(scope.row)">编辑</el-button>
|
||||
<el-button v-if="!scope.row.unlimitedStock" size="small" text type="success" @click="onGenerateStock(scope.row)">生成库存</el-button>
|
||||
<el-button size="small" text type="info" @click="onViewLog(scope.row)">日志</el-button>
|
||||
<el-button size="small" text type="danger" @click="onDeleteSku(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -209,6 +210,7 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
<OperationLogDialog ref="operationLogRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -217,6 +219,7 @@ import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions, getStockFormFields, stockOperation } from '/@/api/assets/asset';
|
||||
import { createFormDiff } from '/@/utils/diffUtils';
|
||||
import OperationLogDialog from '../../component/operationLogDialog.vue';
|
||||
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
||||
|
||||
interface SpecValueItem {
|
||||
@@ -249,6 +252,7 @@ const editLoading = ref(false);
|
||||
const skuFormVisible = ref(false);
|
||||
const isEditSku = ref(false);
|
||||
const editSkuId = ref('');
|
||||
const operationLogRef = ref();
|
||||
|
||||
// 库存弹窗相关
|
||||
const stockFormVisible = ref(false);
|
||||
@@ -529,6 +533,11 @@ const onEditSku = async (row: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 查看日志
|
||||
const onViewLog = (row: any) => {
|
||||
operationLogRef.value?.openDialog(row.id);
|
||||
};
|
||||
|
||||
// 删除 SKU
|
||||
const onDeleteSku = (row: any) => {
|
||||
ElMessageBox.confirm(`确定要删除SKU "${row.skuName}" 吗?`, '提示', {
|
||||
|
||||
@@ -61,10 +61,11 @@
|
||||
<el-table-column prop="offlineTime" label="下线时间" width="170" show-overflow-tooltip />
|
||||
<el-table-column prop="createdAt" label="创建时间" width="170" show-overflow-tooltip />
|
||||
<el-table-column prop="updatedAt" label="修改时间" width="170" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="250" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text type="primary" @click="onEdit(scope.row)">修改</el-button>
|
||||
<el-button size="small" text type="success" @click="onAddSku(scope.row)">规格管理</el-button>
|
||||
<el-button size="small" text type="info" @click="onViewLog(scope.row)">日志</el-button>
|
||||
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -85,6 +86,7 @@
|
||||
</div>
|
||||
<EditAsset ref="editAssetRef" @getAssetList="getAssetList" />
|
||||
<SkuDialog ref="skuDialogRef" />
|
||||
<OperationLogDialog ref="operationLogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -100,6 +102,7 @@ import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { listAssets, updateAssetStatus, deleteAsset } from '/@/api/assets/asset';
|
||||
import EditAsset from './component/editAsset.vue';
|
||||
import SkuDialog from './component/skuDialog.vue';
|
||||
import OperationLogDialog from '../component/operationLogDialog.vue';
|
||||
|
||||
interface AssetRow {
|
||||
id: string;
|
||||
@@ -119,6 +122,7 @@ interface AssetRow {
|
||||
|
||||
const editAssetRef = ref();
|
||||
const skuDialogRef = ref();
|
||||
const operationLogRef = ref();
|
||||
|
||||
const tableData = reactive({
|
||||
data: [] as AssetRow[],
|
||||
@@ -231,6 +235,11 @@ const onAddSku = (row: AssetRow) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 查看日志
|
||||
const onViewLog = (row: AssetRow) => {
|
||||
operationLogRef.value?.openDialog(row.id);
|
||||
};
|
||||
|
||||
// 分页大小改变
|
||||
const onSizeChange = (size: number) => {
|
||||
tableData.param.pageSize = size;
|
||||
|
||||
@@ -44,16 +44,18 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" class="op-btn-add" text type="primary" @click="onOpenAddCategory(scope.row)">新增</el-button>
|
||||
<el-button size="small" text type="primary" @click="onOpenEditCategory(scope.row)">修改</el-button>
|
||||
<el-button size="small" text type="info" @click="onViewLog(scope.row)">日志</el-button>
|
||||
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<EditCategory ref="editCategoryRef" @getCategoryList="getCategoryList" />
|
||||
<OperationLogDialog ref="operationLogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -67,6 +69,7 @@ export default {
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import EditCategory from './component/editCategory.vue';
|
||||
import OperationLogDialog from '../component/operationLogDialog.vue';
|
||||
import { getCategoryTree, deleteCategory, updateCategoryStatus,listCategories } from '/@/api/assets/category';
|
||||
|
||||
interface CategoryRow {
|
||||
@@ -82,6 +85,7 @@ interface CategoryRow {
|
||||
}
|
||||
|
||||
const editCategoryRef = ref();
|
||||
const operationLogRef = ref();
|
||||
const tableData = reactive({
|
||||
data: [] as CategoryRow[],
|
||||
loading: false,
|
||||
@@ -169,6 +173,11 @@ const onStatusChange = (row: CategoryRow) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 查看日志
|
||||
const onViewLog = (row: CategoryRow) => {
|
||||
operationLogRef.value?.openDialog(row.id);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getCategoryList();
|
||||
});
|
||||
|
||||
149
src/views/assets/component/operationLogDialog.vue
Normal file
149
src/views/assets/component/operationLogDialog.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="操作日志" width="800px" :close-on-click-modal="false" append-to-body>
|
||||
<el-table :data="logList" style="width: 100%" v-loading="loading" border max-height="500">
|
||||
<el-table-column prop="createdAt" label="操作时间" width="170" />
|
||||
<el-table-column prop="operation" label="操作类型" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getOperationTagType(scope.row.operation)">{{ getOperationLabel(scope.row.operation) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="操作人" width="120" />
|
||||
<el-table-column prop="data" label="变更内容" min-width="200">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.data && scope.row.data.length > 0">
|
||||
<div v-for="(item, index) in scope.row.data" :key="index" class="change-item">
|
||||
<span class="field-name">{{ item.FieldName }}:</span>
|
||||
<span class="field-value">{{ formatFieldValue(item.FieldValue) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ip_address" label="IP地址" width="140" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<div class="mt15" style="text-align: right" v-if="total > 0">
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="onSizeChange"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { listLogs, OperationLogInfo } from '/@/api/assets/asset';
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const loading = ref(false);
|
||||
const logList = ref<OperationLogInfo[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const queryParams = reactive({
|
||||
collection_id: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (collectionId: string) => {
|
||||
queryParams.collection_id = collectionId;
|
||||
queryParams.pageNum = 1;
|
||||
dialogVisible.value = true;
|
||||
fetchLogs();
|
||||
};
|
||||
|
||||
// 获取日志列表
|
||||
const fetchLogs = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res: any = await listLogs(queryParams);
|
||||
if (res.code === 0 && res.data) {
|
||||
logList.value = res.data.logs || [];
|
||||
total.value = res.data.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误已由拦截器处理
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 操作类型标签
|
||||
const getOperationTagType = (operation: string) => {
|
||||
switch (operation) {
|
||||
case 'insert':
|
||||
return 'success';
|
||||
case 'update':
|
||||
return 'warning';
|
||||
case 'delete':
|
||||
return 'danger';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
// 操作类型文本
|
||||
const getOperationLabel = (operation: string) => {
|
||||
switch (operation) {
|
||||
case 'insert':
|
||||
return '新增';
|
||||
case 'update':
|
||||
return '修改';
|
||||
case 'delete':
|
||||
return '删除';
|
||||
default:
|
||||
return operation;
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化字段值
|
||||
const formatFieldValue = (value: any) => {
|
||||
if (value === null || value === undefined) return '-';
|
||||
if (typeof value === 'boolean') return value ? '是' : '否';
|
||||
if (typeof value === 'object') return JSON.stringify(value);
|
||||
return String(value);
|
||||
};
|
||||
|
||||
// 分页
|
||||
const onSizeChange = () => {
|
||||
queryParams.pageNum = 1;
|
||||
fetchLogs();
|
||||
};
|
||||
|
||||
const onCurrentChange = () => {
|
||||
fetchLogs();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.change-item {
|
||||
margin-bottom: 4px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.field-name {
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.field-value {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
.no-data {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user