Commit 5dbff90d by zhangxingmin

Merge remote-tracking branch 'origin/dev' into prod

parents 8e7c07e3 6823a5ab
# 使用 Node.js 18 基础镜像 (Debian bullseye) # 使用 Node.js 18 基础镜像 (Debian bookworm)
FROM docker.m.daocloud.io/library/node:18-bullseye AS build FROM docker.m.daocloud.io/library/node:18-bookworm AS build
# 设置环境变量 # 设置环境变量
ENV npm_config_canvas_binary_host_mirror="https://npmmirror.com/mirrors/canvas"
ENV npm_config_sharp_binary_host="https://npmmirror.com/mirrors/sharp"
ENV npm_config_sharp_libvips_binary_host="https://npmmirror.com/mirrors/sharp-libvips"
# 添加 esbuild 镜像源
ENV ESBUILD_BINARY_HOST="https://npmmirror.com/mirrors/esbuild" ENV ESBUILD_BINARY_HOST="https://npmmirror.com/mirrors/esbuild"
# 安装系统依赖 (使用阿里云镜像源)
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
python3 \
pkg-config \
libcairo2-dev \
libpango1.0-dev \
libjpeg-dev \
libgif-dev \
librsvg2-dev \
libvips-dev \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录 # 设置工作目录
WORKDIR /app WORKDIR /app
...@@ -30,16 +11,12 @@ WORKDIR /app ...@@ -30,16 +11,12 @@ WORKDIR /app
COPY package.json ./ COPY package.json ./
# 1. 显式安装 esbuild 二进制 # 1. 显式安装 esbuild 二进制
RUN npm install @esbuild/linux-x64 --verbose --registry=https://registry.npmmirror.com RUN npm install @esbuild/linux-x64 --registry=https://registry.npmmirror.com
# 2. 安装其他原生模块
RUN npm install canvas@2.11.2 --verbose --ignore-scripts
RUN npm install sharp@0.32.6 --verbose --ignore-scripts
# 3. 安装项目依赖(移除 --no-optional) # 2. 安装项目依赖
RUN npm install --verbose --registry=https://registry.npmmirror.com RUN npm install --registry=https://registry.npmmirror.com
# 4. 验证 esbuild 安装 # 3. 验证 esbuild 安装
RUN ls -la node_modules/esbuild/bin && \ RUN ls -la node_modules/esbuild/bin && \
./node_modules/.bin/esbuild --version ./node_modules/.bin/esbuild --version
...@@ -52,7 +29,7 @@ FROM docker.m.daocloud.io/library/nginx:alpine ...@@ -52,7 +29,7 @@ FROM docker.m.daocloud.io/library/nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
# 设置时区 # 设置时区(若 v3.22 源不可用,可改为 v3.21 或 latest)
RUN echo "https://mirrors.aliyun.com/alpine/v3.22/main/" > /etc/apk/repositories && \ RUN echo "https://mirrors.aliyun.com/alpine/v3.22/main/" > /etc/apk/repositories && \
echo "https://mirrors.aliyun.com/alpine/v3.22/community/" >> /etc/apk/repositories && \ echo "https://mirrors.aliyun.com/alpine/v3.22/community/" >> /etc/apk/repositories && \
apk update && \ apk update && \
......
import request from '@/utils/request'
// 审计日志分页查询
export function getAuditLogPage(data) {
return request({
url: '/csf/api/auditlog/log/page',
method: 'post',
data
})
}
// 审计详情查询
export function getAuditLogDetail(requestId) {
return request({
url: '/csf/api/auditlog/log/detail?requestId=' + requestId,
method: 'get'
})
}
...@@ -9,6 +9,16 @@ export function getPolicyFollowList(data) { ...@@ -9,6 +9,16 @@ export function getPolicyFollowList(data) {
}) })
} }
// 按当前筛选条件导出新单跟进清单(xlsx 文件流)
export function exportPolicyFollow(data) {
return request({
url: '/csf/api/policy_follow/export',
method: 'post',
data: data,
responseType: 'blob'
})
}
// 获取预计来佣列表 // 获取预计来佣列表
// /csf/api/commission/list/page/commission_expected // /csf/api/commission/list/page/commission_expected
export function getExpectedCommissionList(data) { export function getExpectedCommissionList(data) {
......
...@@ -1560,7 +1560,7 @@ const handleCurrentChange = val => { ...@@ -1560,7 +1560,7 @@ const handleCurrentChange = val => {
const loadTableData = async () => { const loadTableData = async () => {
const searchParams = (await searchFormRef.value.getFormData()) || {} const searchParams = (await searchFormRef.value.getFormData()) || {}
loading.value = true loading.value = true
if (searchParams.payoutDate.length > 0) { if (searchParams.payoutDate && searchParams.payoutDate.length > 0) {
searchParams.payoutDateStart = `${searchParams.payoutDate[0]}-01` searchParams.payoutDateStart = `${searchParams.payoutDate[0]}-01`
searchParams.payoutDateEnd = `${searchParams.payoutDate[1]}-01` searchParams.payoutDateEnd = `${searchParams.payoutDate[1]}-01`
} else { } else {
......
...@@ -529,7 +529,7 @@ const searchConfig = ref([ ...@@ -529,7 +529,7 @@ const searchConfig = ref([
}, },
{ {
type: 'select', type: 'select',
prop: 'fortuneName', prop: 'commissionType',
label: '入账项目', label: '入账项目',
dictType: 'csf_commission_type' dictType: 'csf_commission_type'
}, },
...@@ -783,8 +783,6 @@ const loadTableData = async () => { ...@@ -783,8 +783,6 @@ const loadTableData = async () => {
const searchParams = searchFormRef.value.getFormData() || {} const searchParams = searchFormRef.value.getFormData() || {}
loading.value = true loading.value = true
try { try {
console.log('searchParams.entryDate', searchParams.entryDate)
if (searchParams.entryDate && searchParams.entryDate.length > 0) { if (searchParams.entryDate && searchParams.entryDate.length > 0) {
searchParams.commissionDateStart = `${searchParams.entryDate[0]}-01` searchParams.commissionDateStart = `${searchParams.entryDate[0]}-01`
searchParams.commissionDateEnd = `${searchParams.entryDate[1]}-01` searchParams.commissionDateEnd = `${searchParams.entryDate[1]}-01`
......
<template>
<div class="app-container">
<CommonPage
:operationBtnList="operationBtnList"
:visibleDefaultButtons="visibleDefaultButtons"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig"/>
</template>
<template #table>
<el-table :data="tableData" v-loading="loading" border>
<el-table-column prop="operTime" label="操作时间" width="170">
<template #default="scope">
<span>{{ parseTime(scope.row.operTime) }}</span>
</template>
</el-table-column>
<el-table-column prop="module" label="业务模块" width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="businessType" label="业务类型" width="100">
<template #default="scope">
<span>{{ BUSINESS_TYPE_MAP[scope.row.businessType] || scope.row.businessType }}</span>
</template>
</el-table-column>
<el-table-column prop="content" label="日志内容" min-width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="operName" label="操作人" width="100"/>
<el-table-column prop="deptName" label="部门" width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<el-tag :type="scope.row.status === 0 ? 'success' : 'danger'" size="small">
{{ scope.row.status === 0 ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="operIp" label="操作IP" width="140"/>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</template>
</CommonPage>
<!-- 审计详情弹窗 -->
<el-dialog
title="审计详情"
v-model="dialogVisible"
width="1000px"
append-to-body
:close-on-click-modal="false"
>
<div v-loading="detailLoading">
<!-- 操作日志基本信息 -->
<el-descriptions v-if="detail.operLog" title="基本信息" :column="2" border style="margin-bottom: 20px">
<el-descriptions-item label="业务模块">{{ detail.operLog.module }}</el-descriptions-item>
<el-descriptions-item label="业务类型">
{{ BUSINESS_TYPE_MAP[detail.operLog.businessType] || detail.operLog.businessType }}
</el-descriptions-item>
<el-descriptions-item label="操作人">{{ detail.operLog.operName }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ detail.operLog.deptName }}</el-descriptions-item>
<el-descriptions-item label="操作时间">{{ parseTime(detail.operLog.operTime) }}</el-descriptions-item>
<el-descriptions-item label="操作IP">{{ detail.operLog.operIp }}</el-descriptions-item>
<el-descriptions-item label="请求URL" :span="2">{{ detail.operLog.operUrl }}</el-descriptions-item>
<el-descriptions-item label="日志内容" :span="2">{{ detail.operLog.content }}</el-descriptions-item>
<el-descriptions-item label="操作状态">
<el-tag :type="detail.operLog.status === 0 ? 'success' : 'danger'" size="small">
{{ detail.operLog.status === 0 ? '成功' : '失败' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="请求ID">{{ detail.operLog.requestId }}</el-descriptions-item>
<el-descriptions-item label="请求参数" :span="2">
<div class="json-content">{{ detail.operLog.operParam || '-' }}</div>
</el-descriptions-item>
<el-descriptions-item label="返回结果" :span="2">
<div class="json-content">{{ detail.operLog.jsonResult || '-' }}</div>
</el-descriptions-item>
<el-descriptions-item v-if="detail.operLog.status === 1" label="错误信息" :span="2">
<span style="color: #f56c6c">{{ detail.operLog.errorMsg }}</span>
</el-descriptions-item>
</el-descriptions>
<!-- 数据快照 -->
<template v-if="detail.snapshots && detail.snapshots.length > 0">
<h4 class="section-title">数据快照(共 {{ detail.snapshots.length }} 条)</h4>
<el-table :data="detail.snapshots" border size="small" style="margin-bottom: 20px">
<el-table-column prop="tableName" label="表名" width="150"/>
<el-table-column prop="recordId" label="记录ID" width="120"/>
<el-table-column label="变更前数据" min-width="280">
<template #default="scope">
<pre v-if="scope.row.beforeJson" class="json-pre">{{ formatJson(scope.row.beforeJson) }}</pre>
<span v-else class="empty-hint">- (新增)</span>
</template>
</el-table-column>
<el-table-column label="变更后数据" min-width="280">
<template #default="scope">
<pre v-if="scope.row.afterJson" class="json-pre">{{ formatJson(scope.row.afterJson) }}</pre>
<span v-else class="empty-hint">- (删除)</span>
</template>
</el-table-column>
</el-table>
</template>
<!-- 字段变更明细 -->
<template v-if="detail.changes && detail.changes.length > 0">
<h4 class="section-title">字段变更明细(共 {{ detail.changes.length }} 条)</h4>
<el-table :data="detail.changes" border size="small">
<el-table-column prop="tableName" label="表名" width="150"/>
<el-table-column prop="fieldComment" label="字段名" width="140"/>
<el-table-column label="变更类型" width="80">
<template #default="scope">
<el-tag
:type="changeTypeTag(scope.row.changeType)"
size="small"
>{{ scope.row.changeType }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="oldValue" label="旧值" min-width="200" :show-overflow-tooltip="true"/>
<el-table-column prop="newValue" label="新值" min-width="200" :show-overflow-tooltip="true"/>
</el-table>
</template>
</div>
<template #footer>
<el-button @click="dialogVisible = false">关 闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue'
import CommonPage from '@/components/commonPage'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
import {getAuditLogPage, getAuditLogDetail} from '@/api/monitor/auditlog'
const {proxy} = getCurrentInstance()
// 业务类型映射
const BUSINESS_TYPE_MAP = {
0: '其它', 1: '新增', 2: '修改', 3: '删除',
4: '授权', 5: '导出', 6: '导入', 7: '强退',
8: '生成代码', 9: '清空数据'
}
// 搜索表单配置
const searchFormRef = ref(null)
const searchConfig = ref([
{type: 'input', prop: 'module', label: '业务模块', placeholder: '请输入业务模块'},
{type: 'input', prop: 'operName', label: '操作人', placeholder: '请输入操作人'},
{
type: 'select',
prop: 'status',
label: '操作状态',
placeholder: '请选择',
options: [
{label: '成功', value: 0},
{label: '失败', value: 1}
]
},
{
type: 'daterange',
prop: 'operTimeRange',
label: '操作时间',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
])
// 表格数据
const tableData = ref([])
const loading = ref(false)
// 分页
const currentPage = ref(1)
const pageSize = ref(10)
const pageTotal = ref(0)
const handleSizeChange = val => {
pageSize.value = val
getList()
}
const handleCurrentChange = val => {
currentPage.value = val
getList()
}
// 获取列表数据
const getList = async () => {
loading.value = true
try {
const formData = searchFormRef.value?.getFormData() || {}
const params = {
...formData,
startTime: formData.operTimeRange ? formData.operTimeRange[0] : undefined,
endTime: formData.operTimeRange ? formData.operTimeRange[1] : undefined,
operTimeRange: undefined,
pageNo: currentPage.value,
pageSize: pageSize.value
}
const response = await getAuditLogPage(params)
tableData.value = response.data.records || []
pageTotal.value = response.data.total || 0
} finally {
loading.value = false
}
}
getList()
// 查询
const handleQuery = () => {
currentPage.value = 1
getList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetForm()
currentPage.value = 1
getList()
}
const visibleDefaultButtons = ref(['reset', 'query'])
const operationBtnList = ref([
{key: 'reset', direction: 'right', click: handleReset},
{key: 'query', direction: 'right', click: handleQuery}
])
// ========== 详情弹窗 ==========
const dialogVisible = ref(false)
const detailLoading = ref(false)
const detail = reactive({
operLog: null,
snapshots: [],
changes: []
})
const handleView = async (row) => {
dialogVisible.value = true
detail.operLog = null
detail.snapshots = []
detail.changes = []
if (!row.requestId) return
detailLoading.value = true
try {
const response = await getAuditLogDetail(row.requestId)
if (response.data) {
detail.operLog = response.data.operLog || null
detail.snapshots = response.data.snapshots || []
detail.changes = response.data.changes || []
}
} finally {
detailLoading.value = false
}
}
const formatJson = (jsonStr) => {
try {
return JSON.stringify(JSON.parse(jsonStr), null, 2)
} catch {
return jsonStr
}
}
const changeTypeTag = (type) => {
switch (type) {
case 'ADD':
return 'success'
case 'MODIFY':
return 'warning'
case 'DELETE':
return 'danger'
default:
return 'info'
}
}
</script>
<style scoped>
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin: 0 0 12px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
.json-content {
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 12px;
color: #606266;
background: #f5f7fa;
padding: 8px;
border-radius: 4px;
}
.json-pre {
margin: 0;
padding: 6px 8px;
background: #f5f7fa;
border-radius: 4px;
font-size: 12px;
line-height: 1.5;
max-height: 200px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
.empty-hint {
color: #c0c4cc;
font-size: 12px;
}
</style>
...@@ -728,6 +728,7 @@ function getAppointmentInfo(appointmentBizId, changeTab) { ...@@ -728,6 +728,7 @@ function getAppointmentInfo(appointmentBizId, changeTab) {
activeName.value = 'appointmentInfo' activeName.value = 'appointmentInfo'
} }
} }
console.log('详情请求完')
} }
}) })
} }
...@@ -1068,17 +1069,7 @@ const handleSubmit = async type => { ...@@ -1068,17 +1069,7 @@ const handleSubmit = async type => {
submitAppointmentObj.value.source = 2 submitAppointmentObj.value.source = 2
} }
submitLoading.value = true submitLoading.value = true
// 编辑预约单
// editAppointmentDetail(submitAppointmentObj.value).then(res => {
// if (res.code == 200) {
// submitLoading.value = false
// editStatus.value = true
// getAppointmentInfo(idsObj.value.appointmentBizId)
// proxy.$message.success('修改预约单成功')
// } else {
// submitLoading.value = false
// }
// })
try { try {
const res = await editAppointmentDetail(submitAppointmentObj.value) const res = await editAppointmentDetail(submitAppointmentObj.value)
if (res.code == 200) { if (res.code == 200) {
...@@ -1130,17 +1121,7 @@ const handleSubmit = async type => { ...@@ -1130,17 +1121,7 @@ const handleSubmit = async type => {
// 编辑状态下预约暂存 // 编辑状态下预约暂存
if (type == 'editStorage') { if (type == 'editStorage') {
submitLoading.value = true submitLoading.value = true
// 暂存预约单
// appointmentEditStorage(submitAppointmentObj.value).then(res => {
// if (res.code == 200) {
// submitLoading.value = false
// proxy.$message.success('预约暂存提交成功')
// editStatus.value = true
// getAppointmentInfo(idsObj.value.appointmentBizId)
// } else {
// submitLoading.value = false
// }
// })
try { try {
const res = await appointmentEditStorage(submitAppointmentObj.value) const res = await appointmentEditStorage(submitAppointmentObj.value)
if (res.code == 200) { if (res.code == 200) {
...@@ -1274,8 +1255,6 @@ watch( ...@@ -1274,8 +1255,6 @@ watch(
}) })
} else { } else {
activeName.value = '' activeName.value = ''
getAppointmentInfo(idsObj.value.appointmentBizId)
tabsList.value = [ tabsList.value = [
{ {
label: '签约信息', label: '签约信息',
...@@ -1347,10 +1326,12 @@ watch( ...@@ -1347,10 +1326,12 @@ watch(
key: 'fnaBizId' key: 'fnaBizId'
} }
] ]
getAppointmentInfo(idsObj.value.appointmentBizId, true)
} }
nextTick(() => {
activeName.value = 'appointmentInfo' // nextTick(() => {
}) // activeName.value = 'appointmentInfo'
// })
} }
} }
) )
...@@ -1366,6 +1347,7 @@ const getPolicyDetail = tabName => { ...@@ -1366,6 +1347,7 @@ const getPolicyDetail = tabName => {
...followRes.data, ...followRes.data,
...infoRes.data ...infoRes.data
} }
if (route.query.source == 'policyList') { if (route.query.source == 'policyList') {
appointmentSummeryInfo.value.apiAppointmentInfoDto = JSON.parse( appointmentSummeryInfo.value.apiAppointmentInfoDto = JSON.parse(
JSON.stringify( JSON.stringify(
......
...@@ -1183,6 +1183,8 @@ const processFormData = async () => { ...@@ -1183,6 +1183,8 @@ const processFormData = async () => {
} }
// 编辑状态下回显值 // 编辑状态下回显值
if (props.idsObj.appointmentBizId) { if (props.idsObj.appointmentBizId) {
console.log('预约详情', props.apiAppointmentInfoDto)
setFormValue(props.apiAppointmentInfoDto, processedData) setFormValue(props.apiAppointmentInfoDto, processedData)
return return
} else { } else {
......
...@@ -149,6 +149,7 @@ import useUserStore from '@/store/modules/user' ...@@ -149,6 +149,7 @@ import useUserStore from '@/store/modules/user'
import { safeDownload } from '@/utils/safeDownload' import { safeDownload } from '@/utils/safeDownload'
import { import {
getPolicyFollowList, getPolicyFollowList,
exportPolicyFollow,
getExpectedCommissionList, getExpectedCommissionList,
changePolicyStatus, changePolicyStatus,
policyFollowReport, policyFollowReport,
...@@ -341,8 +342,29 @@ const handleAdd = () => { ...@@ -341,8 +342,29 @@ const handleAdd = () => {
const handleImport = () => { const handleImport = () => {
ElMessage.info('点击导入按钮') ElMessage.info('点击导入按钮')
} }
const handleExport = () => { // 查询表单 → 请求体(列表查询与导出共用,保证筛选口径一致)
ElMessage.info('点击导出按钮') const buildQueryParams = () => {
const formData = searchFormRef.value?.getFormData() || {}
return {
...formData,
signDateStart: formData.signDate?.[0] || undefined,
signDateEnd: formData.signDate?.[1] || undefined,
signDate: undefined
}
}
const exporting = ref(false)
const handleExport = async () => {
exporting.value = true
try {
const response = await exportPolicyFollow(buildQueryParams())
const now = new Date()
const pad = n => String(n).padStart(2, '0')
const day = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
await safeDownload(response, `新单跟进清单_${day}.xlsx`)
} finally {
exporting.value = false
}
} }
const handleReset = () => { const handleReset = () => {
// 重置搜索表单 // 重置搜索表单
...@@ -356,9 +378,14 @@ const handleQuery = async () => { ...@@ -356,9 +378,14 @@ const handleQuery = async () => {
currentPage.value = 1 currentPage.value = 1
loadTableData() loadTableData()
} }
const visibleDefaultButtons = ref(['reset', 'query']) const visibleDefaultButtons = ref(['export', 'reset', 'query'])
// 按钮配置 const operationBtnList = computed(() => [
const operationBtnList = ref([ {
key: 'export',
direction: 'right',
loading: exporting.value,
click: handleExport
},
{ {
key: 'reset', key: 'reset',
direction: 'right', direction: 'right',
...@@ -489,10 +516,7 @@ const loadTableData = async () => { ...@@ -489,10 +516,7 @@ const loadTableData = async () => {
// console.log(searchParams.value) // console.log(searchParams.value)
try { try {
const params = { const params = {
...searchParams.value, ...buildQueryParams(),
signDateStart: searchParams.value?.signDate[0] || undefined,
signDateEnd: searchParams.value?.signDate[1] || undefined,
signDate: undefined,
pageNo: currentPage.value, pageNo: currentPage.value,
pageSize: pageSize.value pageSize: pageSize.value
} }
......
...@@ -691,7 +691,7 @@ const basicPlanFormConfig = ref([ ...@@ -691,7 +691,7 @@ const basicPlanFormConfig = ref([
insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId || ''], insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId || ''],
pageNo: 1, pageNo: 1,
pageSize: 20, pageSize: 20,
productName: 'productName' productName: ''
}), }),
placeholder: '请选择产品名称', placeholder: '请选择产品名称',
debounceWait: 500, debounceWait: 500,
...@@ -1279,7 +1279,8 @@ const viewRecordDetail = e => { ...@@ -1279,7 +1279,8 @@ const viewRecordDetail = e => {
type: 'edit', type: 'edit',
status: newOrderData.value.status, status: newOrderData.value.status,
source: 'newOrder', source: 'newOrder',
appointmentBizId: newOrderData.value.appointmentBizId appointmentBizId: newOrderData.value.appointmentBizId,
appointmentNo: newOrderData.value.appointmentNo
} }
}) })
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment