在资产管理、SKU管理和分类管理中新增操作日志查看功能,支持查看各实体的操作历史记录,同时新增操作日志API接口定义包含查询参数和日志信息类型
This commit is contained in:
@@ -191,3 +191,32 @@ export function stockOperation(data: StockOperationParams) {
|
|||||||
data,
|
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>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="180" align="center">
|
<el-table-column label="操作" width="220" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" text type="primary" @click="onEditSku(scope.row)">编辑</el-button>
|
<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 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>
|
<el-button size="small" text type="danger" @click="onDeleteSku(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -209,6 +210,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<OperationLogDialog ref="operationLogRef" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -217,6 +219,7 @@ import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
|||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions, getStockFormFields, stockOperation } from '/@/api/assets/asset';
|
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions, getStockFormFields, stockOperation } from '/@/api/assets/asset';
|
||||||
import { createFormDiff } from '/@/utils/diffUtils';
|
import { createFormDiff } from '/@/utils/diffUtils';
|
||||||
|
import OperationLogDialog from '../../component/operationLogDialog.vue';
|
||||||
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
|
||||||
|
|
||||||
interface SpecValueItem {
|
interface SpecValueItem {
|
||||||
@@ -249,6 +252,7 @@ const editLoading = ref(false);
|
|||||||
const skuFormVisible = ref(false);
|
const skuFormVisible = ref(false);
|
||||||
const isEditSku = ref(false);
|
const isEditSku = ref(false);
|
||||||
const editSkuId = ref('');
|
const editSkuId = ref('');
|
||||||
|
const operationLogRef = ref();
|
||||||
|
|
||||||
// 库存弹窗相关
|
// 库存弹窗相关
|
||||||
const stockFormVisible = ref(false);
|
const stockFormVisible = ref(false);
|
||||||
@@ -529,6 +533,11 @@ const onEditSku = async (row: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 查看日志
|
||||||
|
const onViewLog = (row: any) => {
|
||||||
|
operationLogRef.value?.openDialog(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
// 删除 SKU
|
// 删除 SKU
|
||||||
const onDeleteSku = (row: any) => {
|
const onDeleteSku = (row: any) => {
|
||||||
ElMessageBox.confirm(`确定要删除SKU "${row.skuName}" 吗?`, '提示', {
|
ElMessageBox.confirm(`确定要删除SKU "${row.skuName}" 吗?`, '提示', {
|
||||||
|
|||||||
@@ -61,10 +61,11 @@
|
|||||||
<el-table-column prop="offlineTime" label="下线时间" width="170" show-overflow-tooltip />
|
<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="createdAt" label="创建时间" width="170" show-overflow-tooltip />
|
||||||
<el-table-column prop="updatedAt" 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">
|
<template #default="scope">
|
||||||
<el-button size="small" text type="primary" @click="onEdit(scope.row)">修改</el-button>
|
<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="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>
|
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<EditAsset ref="editAssetRef" @getAssetList="getAssetList" />
|
<EditAsset ref="editAssetRef" @getAssetList="getAssetList" />
|
||||||
<SkuDialog ref="skuDialogRef" />
|
<SkuDialog ref="skuDialogRef" />
|
||||||
|
<OperationLogDialog ref="operationLogRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -100,6 +102,7 @@ import { ElMessageBox, ElMessage } from 'element-plus';
|
|||||||
import { listAssets, updateAssetStatus, deleteAsset } from '/@/api/assets/asset';
|
import { listAssets, updateAssetStatus, deleteAsset } from '/@/api/assets/asset';
|
||||||
import EditAsset from './component/editAsset.vue';
|
import EditAsset from './component/editAsset.vue';
|
||||||
import SkuDialog from './component/skuDialog.vue';
|
import SkuDialog from './component/skuDialog.vue';
|
||||||
|
import OperationLogDialog from '../component/operationLogDialog.vue';
|
||||||
|
|
||||||
interface AssetRow {
|
interface AssetRow {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -119,6 +122,7 @@ interface AssetRow {
|
|||||||
|
|
||||||
const editAssetRef = ref();
|
const editAssetRef = ref();
|
||||||
const skuDialogRef = ref();
|
const skuDialogRef = ref();
|
||||||
|
const operationLogRef = ref();
|
||||||
|
|
||||||
const tableData = reactive({
|
const tableData = reactive({
|
||||||
data: [] as AssetRow[],
|
data: [] as AssetRow[],
|
||||||
@@ -231,6 +235,11 @@ const onAddSku = (row: AssetRow) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 查看日志
|
||||||
|
const onViewLog = (row: AssetRow) => {
|
||||||
|
operationLogRef.value?.openDialog(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
// 分页大小改变
|
// 分页大小改变
|
||||||
const onSizeChange = (size: number) => {
|
const onSizeChange = (size: number) => {
|
||||||
tableData.param.pageSize = size;
|
tableData.param.pageSize = size;
|
||||||
|
|||||||
@@ -44,16 +44,18 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
<el-table-column label="操作" width="250" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" class="op-btn-add" text type="primary" @click="onOpenAddCategory(scope.row)">新增</el-button>
|
<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="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>
|
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
<EditCategory ref="editCategoryRef" @getCategoryList="getCategoryList" />
|
<EditCategory ref="editCategoryRef" @getCategoryList="getCategoryList" />
|
||||||
|
<OperationLogDialog ref="operationLogRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -67,6 +69,7 @@ export default {
|
|||||||
import { ref, reactive, onMounted } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
import EditCategory from './component/editCategory.vue';
|
import EditCategory from './component/editCategory.vue';
|
||||||
|
import OperationLogDialog from '../component/operationLogDialog.vue';
|
||||||
import { getCategoryTree, deleteCategory, updateCategoryStatus,listCategories } from '/@/api/assets/category';
|
import { getCategoryTree, deleteCategory, updateCategoryStatus,listCategories } from '/@/api/assets/category';
|
||||||
|
|
||||||
interface CategoryRow {
|
interface CategoryRow {
|
||||||
@@ -82,6 +85,7 @@ interface CategoryRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editCategoryRef = ref();
|
const editCategoryRef = ref();
|
||||||
|
const operationLogRef = ref();
|
||||||
const tableData = reactive({
|
const tableData = reactive({
|
||||||
data: [] as CategoryRow[],
|
data: [] as CategoryRow[],
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -169,6 +173,11 @@ const onStatusChange = (row: CategoryRow) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 查看日志
|
||||||
|
const onViewLog = (row: CategoryRow) => {
|
||||||
|
operationLogRef.value?.openDialog(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCategoryList();
|
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