Files
intc-vue3/src/views/health/activity/index.vue
2026-07-13 09:33:40 +08:00

597 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container">
<div class="search-con">
<div class="title">查询条件</div>
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入活动名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable>
<el-option v-for="dict in activity_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="活动地点" prop="place">
<el-input v-model="queryParams.place" placeholder="请输入活动地点" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-form>
<div class="search-btn-con">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button type="info" icon="Refresh" @click="resetQuery">重置</el-button>
</div>
</div>
<div class="main-con">
<div class="title-con">
<div class="title">基本信息</div>
<div class="operate-btn-con">
<el-button @click="handleAdd" icon="Plus" v-hasPermi="['health:activity:add']">新增</el-button>
<el-button :disabled="multiple" icon="Delete" @click="handleDelete" v-hasPermi="['health:activity:remove']">删除</el-button>
<!-- <el-button @click="handleExport" icon="Download" v-hasPermi="['health:activity:export']">导出</el-button> -->
</div>
</div>
<div class="content-con" v-loading="loading" height="calc(100% - 0.65rem)">
<el-table v-loading="loading" :data="activityList" @selection-change="handleSelectionChange" height="calc(100% - 0.65rem)">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" :index="indexMethod" width="50"></el-table-column>
<el-table-column label="活动名称" align="center" prop="name" />
<el-table-column label="类型" width="100" align="center" prop="type">
<template #default="scope">
<dict-tag :options="activity_type" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="活动地点" align="center" prop="place" />
<el-table-column label="开始时间" align="center" prop="startTime" width="160"> </el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="160"> </el-table-column>
<el-table-column label="活动量" width="100" align="center" prop="activityVolume">
<template #default="scope">
<dict-tag :options="activity_exercise" :value="scope.row.activityVolume" />
</template>
</el-table-column>
<el-table-column label="活动时长" align="center" prop="exerciseTimeStr" width="150"> </el-table-column>
<el-table-column label="成员" align="center" prop="partner" />
<el-table-column label="总费用(元)" width="120" align="center" prop="totalCost" />
<el-table-column label="附件" align="center" prop="attachment" width="120">
<template #default="scope">
<el-popover v-if="parseAttachments(scope.row.attachment).length" placement="left" width="280" trigger="click">
<template #reference>
<el-button link type="primary">查看{{ parseAttachments(scope.row.attachment).length }}</el-button>
</template>
<div class="attachment-popover-list">
<el-link
v-for="(file, index) in parseAttachments(scope.row.attachment)"
:key="file"
type="primary"
:href="getAttachmentUrl(file)"
target="_blank"
:underline="false"
>
{{ getAttachmentName(file, index) }}
</el-link>
</div>
</el-popover>
<span v-else class="attachment-empty"></span>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<div class="ctrl-btn d-flex">
<el-tooltip v-for="item in operateList" :key="item.id" class="item" effect="dark" :content="item.title" placement="top">
<el-button :icon="item.icon" v-hasPermi="item.hasPermi" circle @click="handleOperate(item.id, scope.row)"></el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
small
background
layout="total,sizes, prev, pager, next"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<!-- 添加或修改活动记录对话框 -->
<el-dialog :title="title" v-model="open" width="880px" append-to-body>
<el-form ref="activityRef" :model="form" :inline="true" :rules="rules" label-width="120px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name" placeholder="请输入活动名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type" placeholder="请选择类型">
<el-option v-for="dict in activity_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动地点" prop="place">
<el-input v-model="form.place" placeholder="请输入活动地点" />
</el-form-item>
<el-form-item label="成员" prop="partner">
<el-input v-model="form.partner" placeholder="请输入成员" />
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker clearable v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择开始时间"> </el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker clearable v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择结束时间"> </el-date-picker>
</el-form-item>
<!-- <el-form-item label="活动时长" prop="exerciseTime">
<el-input v-model="form.exerciseTime" placeholder="请输入活动时长" />
</el-form-item> -->
<el-form-item label="活动量" prop="activityVolume">
<el-select v-model="form.activityVolume" placeholder="请选择活动量">
<el-option v-for="dict in activity_exercise" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="总费用" prop="totalCost">
<el-input v-model="form.totalCost" type="number" placeholder="请输入总费用">
<template #suffix></template>
</el-input>
</el-form-item>
<el-form-item label="费用明细" style="width: 792px" prop="costDetail">
<el-input v-model="form.costDetail" type="textarea" placeholder="请输入费用明细" />
</el-form-item>
<el-form-item label="饮食" style="width: 792px" prop="foods">
<el-input v-model="form.foods" type="textarea" placeholder="请输入饮食" />
</el-form-item>
<el-form-item label="收获" style="width: 792px" prop="harvest">
<el-input v-model="form.harvest" type="textarea" placeholder="请输入收获" />
</el-form-item>
<el-form-item label="附件" style="width: 792px" prop="attachment">
<div v-if="title === '查看活动记录'" class="attachment-readonly-list attachment-form-list">
<el-link
v-for="(file, index) in parseAttachments(form.attachment)"
:key="file"
type="primary"
:href="getAttachmentUrl(file)"
target="_blank"
:underline="false"
>
{{ getAttachmentName(file, index) }}
</el-link>
<span v-if="!parseAttachments(form.attachment).length" class="attachment-empty">暂无附件</span>
</div>
<file-upload v-else v-model="form.attachment" :limit="9" :file-size="10" :file-type="attachmentFileTypes" />
</el-form-item>
<el-form-item label="备注" style="width: 792px" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template v-if="title !== '查看活动记录'" #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 费用汇总对话框 -->
<el-dialog title="费用汇总" v-model="summaryDialogVisible" width="1200px" append-to-body>
<div v-loading="summaryLoading">
<el-table :data="expenseList" @selection-change="handleExpenseSelectionChange" max-height="500">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="账户名称" align="center" width="180" prop="accountName" />
<el-table-column label="交易时间" align="center" width="160" prop="createTime" />
<el-table-column label="交易金额" align="center" width="100" prop="amount" />
<el-table-column label="交易类别" align="center" width="140" prop="dealCategory">
<template #default="scope">
<dict-tag :options="deal_category" :value="scope.row.dealCategory" />
</template>
</el-table-column>
<el-table-column label="交易子类别" align="center" width="140" prop="childCategoryName" />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
</el-table>
<div style="margin-top: 16px; text-align: right; color: #606266; font-size: 14px">
已选 <span style="color: #409eff; font-weight: bold">{{ selectedExpenses.length }}</span> 合计金额<span
style="color: #f56c6c; font-weight: bold; font-size: 16px"
>{{ calculateTotalAmount() }}</span
>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="confirmSummary" :disabled="selectedExpenses.length === 0">确认汇总</el-button>
<el-button @click="summaryDialogVisible = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Activity">
import { listActivity, getActivity, delActivity, addActivity, updateActivity } from '@/api/health/activity'
import { listAccountDealRecord } from '@/api/invest/accountDealRecord'
// eslint-disable-next-line no-unused-vars
import dayjs from 'dayjs'
import { require } from '@/utils/require'
const { proxy } = getCurrentInstance()
const { activity_type, activity_exercise, deal_category, daily_expenses } = proxy.useDict(
'activity_type',
'activity_exercise',
'deal_category',
'daily_expenses'
)
const activityList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref('')
const summaryDialogVisible = ref(false)
const summaryLoading = ref(false)
const expenseList = ref([])
const selectedExpenses = ref([])
const currentActivityId = ref(null)
const summaryTotal = ref(0)
const attachmentFileTypes = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xls', 'xlsx']
const operateList = ref([
{ id: 'view', icon: 'View', title: '查看', hasPermi: ['health:activity:query'] },
{ id: 'edit', icon: 'Edit', title: '修改', hasPermi: ['health:activity:edit'] },
{ id: 'summary', icon: 'Money', title: '费用汇总', hasPermi: ['health:activity:edit'] },
{ id: 'delete', icon: 'Delete', title: '删除', hasPermi: ['health:activity:remove'] }
])
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
type: null,
place: null,
activityVolume: null,
startTime: null,
endTime: null
},
rules: {
name: [{ required: true, message: '活动名称不能为空', trigger: 'blur' }],
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
activityVolume: [{ required: true, message: '活动量不能为空', trigger: 'change' }],
place: [{ required: true, message: '活动地点不能为空', trigger: 'blur' }],
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
partner: [{ required: true, message: '成员不能为空', trigger: 'blur' }]
}
})
const handleOperate = (operate, row) => {
switch (operate) {
case 'view':
handleView(row)
break
case 'edit':
handleUpdate(row)
break
case 'summary':
handleCostSummary(row)
break
case 'delete':
handleDelete(row)
break
default:
break
}
}
const { queryParams, form, rules } = toRefs(data)
function parseAttachments(attachment) {
if (!attachment) {
return []
}
return String(attachment)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
function getAttachmentUrl(file) {
if (!file) {
return ''
}
if (/^(https?:)?\/\//.test(file) || file.startsWith('/') || file.startsWith('blob:') || file.startsWith('data:')) {
return file
}
return `/${file}`
}
function getAttachmentName(file, index) {
const purePath = String(file || '').split('?')[0]
const name = purePath.slice(purePath.lastIndexOf('/') + 1)
if (!name) {
return `附件${index + 1}`
}
try {
return decodeURIComponent(name)
} catch {
return name
}
}
/** 查询活动记录列表 */
function getList() {
loading.value = true
listActivity(queryParams.value).then((response) => {
activityList.value = response.rows
total.value = response.total
loading.value = false
})
}
// 取消按钮
function cancel() {
open.value = false
reset()
}
// 表单重置
function reset() {
form.value = {
id: null,
name: null,
type: null,
place: null,
activityVolume: null,
exerciseTime: null,
startTime: null,
endTime: null,
harvest: null,
foods: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
delFlag: null,
remark: null,
totalCost: 0,
partner: null,
costDetail: null,
attachment: null
}
proxy.resetForm('activityRef')
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm('queryRef')
handleQuery()
}
// 分页
const handleCurrentChange = (val) => {
queryParams.value.pageNum = val
getList()
}
//每页显示条数改变
const handleSizeChange = (val) => {
queryParams.value.pageSize = val
getList()
}
// 序号翻页递增
const indexMethod = (index) => {
const nowPage = queryParams.value.pageNum //当前第几页,根据组件取值即可
const nowLimit = queryParams.value.pageSize //当前每页显示几条,根据组件取值即可
return index + 1 + (nowPage - 1) * nowLimit // 这里可以理解成一个公式
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.id)
single.value = selection.length !== 1
multiple.value = !selection.length
}
// 查看
const handleView = (row) => {
title.value = '查看活动记录'
form.value = row
open.value = true
}
/** 新增按钮操作 */
function handleAdd() {
reset()
form.value.startTime = dayjs(new Date().getTime()).format('YYYY-MM-DD HH:mm:ss')
form.value.endTime = dayjs(new Date().getTime()).format('YYYY-MM-DD HH:mm:ss')
open.value = true
title.value = '添加活动记录'
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset()
const _id = row.id || ids.value
getActivity(_id).then((response) => {
form.value = response.data
open.value = true
title.value = '修改活动记录'
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs.activityRef.validate((valid) => {
if (valid) {
if (form.value.id != null) {
updateActivity(form.value).then((response) => {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
})
} else {
addActivity(form.value).then((response) => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value
proxy.$modal
.confirm('是否确认删除选中的数据项?')
.then(function () {
return delActivity(_ids)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download(
'health/activity/export',
{
...queryParams.value
},
`activity_${new Date().getTime()}.xlsx`
)
}
// 费用汇总按钮操作
function handleCostSummary(row) {
currentActivityId.value = row.id
summaryDialogVisible.value = true
summaryLoading.value = true
// 构建查询参数
const queryExpenseParams = {
pageNum: 1,
pageSize: 1000,
dealCategory: '1', // 日常支出
startTime: row.startTime ? dayjs(row.startTime).format('YYYY-MM-DD') : '',
endTime: row.endTime ? dayjs(row.endTime).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD')
}
// 查询日常支出列表
listAccountDealRecord(queryExpenseParams)
.then((response) => {
expenseList.value = response.rows || []
summaryLoading.value = false
})
.catch(() => {
summaryLoading.value = false
})
}
// 多选框选中数据(费用汇总)
function handleExpenseSelectionChange(selection) {
selectedExpenses.value = selection
}
// 计算选中费用总额
function calculateTotalAmount() {
return selectedExpenses.value
.reduce((sum, item) => {
return sum + (parseFloat(item.amount) || 0)
}, 0)
.toFixed(2)
}
// 确认汇总
function confirmSummary() {
if (selectedExpenses.value.length === 0) {
proxy.$modal.msgWarning('请至少选择一条费用记录')
return
}
// 获取活动详情
getActivity(currentActivityId.value).then((response) => {
const activity = response.data
// 计算总金额
const totalAmount = parseFloat(calculateTotalAmount())
const currentTotal = parseFloat(activity.totalCost) || 0
// 判断活动是否跨天
const startDate = activity.startTime ? dayjs(activity.startTime).format('YYYY-MM-DD') : ''
const endDate = activity.endTime ? dayjs(activity.endTime).format('YYYY-MM-DD') : ''
const isMultiDay = startDate !== endDate
// 构建费用明细
let detailsWithDate = ''
if (isMultiDay) {
// 多天活动:按日期分组
const groupedByDate = {}
selectedExpenses.value.forEach((item) => {
const date = dayjs(item.createTime).format('MM-DD')
if (!groupedByDate[date]) {
groupedByDate[date] = []
}
const category = item.childCategoryName || ''
groupedByDate[date].push(`${category} ${item.amount}${item.remark ? '(' + item.remark + ')' : ''}`)
})
// 按日期组装明细
detailsWithDate = Object.keys(groupedByDate)
.sort()
.map((date) => `${date} ${groupedByDate[date].join('、')}`)
.join('')
} else {
// 单天活动:不带日期
detailsWithDate = selectedExpenses.value
.map((item) => {
const category = item.childCategoryName || ''
return `${category} ${item.amount}${item.remark ? '(' + item.remark + ')' : ''}`
})
.join('')
}
// 更新活动费用
const updateData = {
...activity,
totalCost: (currentTotal + totalAmount).toFixed(2),
costDetail: activity.costDetail ? `${activity.costDetail}${detailsWithDate}` : detailsWithDate
}
updateActivity(updateData).then(() => {
proxy.$modal.msgSuccess('费用汇总成功')
summaryDialogVisible.value = false
selectedExpenses.value = []
getList()
})
})
}
getList()
</script>
<style scoped lang="scss">
.attachment-popover-list,
.attachment-readonly-list {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
line-height: 1.4;
}
.attachment-form-list {
min-height: 32px;
padding: 4px 0;
}
.attachment-empty {
color: #909399;
}
</style>