feat: 用户积分管理

This commit is contained in:
2026-07-27 11:03:04 +08:00
parent a6a2471111
commit dd7c35eb50
10 changed files with 800 additions and 0 deletions
@@ -0,0 +1,279 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="工号">
<el-input clearable placeholder="请输入工号" v-model="pageForm.account"></el-input>
</search-item>
<search-item label="姓名">
<el-input clearable placeholder="请输入姓名" v-model="pageForm.realName"></el-input>
</search-item>
<search-item label="手机号">
<el-input clearable placeholder="请输入手机号" v-model="pageForm.mobile"></el-input>
</search-item>
<search-item label="会员">
<el-select clearable placeholder="请选择" v-model="pageForm.member">
<el-option :value="true" label="是"></el-option>
<el-option :value="false" label="否"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="用户积分管理">
<el-button @click="openBatch('grant')" icon="el-icon-plus" size="small" type="primary">批量增加积分</el-button>
<el-button @click="openBatch('deduction')" icon="el-icon-minus" size="small" type="danger">批量扣减积分</el-button>
<el-button @click="deleteSelected" icon="el-icon-delete" size="small" type="danger">删除积分记录</el-button>
</table-tool>
<el-table :data="tableData" @selection-change="handleSelectionChange" border size="small" style="width:100%" v-loading="tableLoading">
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column :index="indexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="工号" prop="account" width="120"></el-table-column>
<el-table-column align="center" label="姓名" prop="realName" width="120"></el-table-column>
<el-table-column align="center" label="性别" prop="gender" width="70"></el-table-column>
<el-table-column align="center" label="联系方式" prop="mobile" width="130"></el-table-column>
<el-table-column align="center" label="组织名称" min-width="180" prop="unitName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="总分配积分" prop="totalPoints" width="120"></el-table-column>
<el-table-column align="center" label="当前可用积分" prop="availablePoints" width="130"></el-table-column>
<el-table-column align="center" label="在途锁定积分" prop="lockPoints" width="130"></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="190">
<template slot-scope="{row}">
<el-button @click="openLog(row)" size="mini" type="text">查看</el-button>
<el-button @click="openChange(row, 'grant')" size="mini" type="text">增加</el-button>
<el-button @click="openChange(row, 'deduction')" size="mini" type="text">扣减</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="pageForm.pageNumber" :page-size="pageForm.pageSize" :page-sizes="[10,20,30,50]" :total="pageForm.totalCount" @current-change="pageChange" @size-change="sizeChange" layout="total, sizes, prev, pager, next" style="margin-top:16px;text-align:right"></el-pagination>
</el-card>
<el-dialog :close-on-click-modal="false" :title="changeType === 'grant' ? '增加积分' : '扣减积分'" :visible.sync="changeDialogVisible" width="520px">
<el-form :model="changeForm" label-width="110px" ref="changeForm">
<el-form-item label="用户">
<el-input disabled :value="currentRow ? currentRow.realName + '(' + currentRow.account + ')' : ''"></el-input>
</el-form-item>
<el-form-item label="积分">
<el-input-number :min="0.01" :precision="2" :step="1" controls-position="right" style="width:100%" v-model="changeForm.points"></el-input-number>
</el-form-item>
<el-form-item label="备注">
<el-input clearable placeholder="请输入备注" v-model="changeForm.remark"></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="changeDialogVisible=false">取消</el-button>
<el-button :loading="submitLoading" @click="submitChange" type="primary">保存</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" :title="batchType === 'grant' ? '批量增加积分' : '批量扣减积分'" :visible.sync="batchDialogVisible" width="560px">
<el-alert :closable="false" :title="'将对已选择的 ' + multipleSelection.length + ' 个用户执行操作'" show-icon type="warning"></el-alert>
<el-form :model="batchForm" class="mt10" label-width="110px">
<el-form-item label="积分">
<el-input-number :min="0.01" :precision="2" :step="1" controls-position="right" style="width:100%" v-model="batchForm.points"></el-input-number>
</el-form-item>
<el-form-item label="备注">
<el-input clearable placeholder="请输入备注" v-model="batchForm.remark"></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="batchDialogVisible=false">取消</el-button>
<el-button :loading="submitLoading" @click="submitBatch" type="primary">确认</el-button>
</span>
</el-dialog>
<el-dialog :title="logTitle" :visible.sync="logDialogVisible" width="820px">
<el-table :data="logData" border size="small" v-loading="logLoading">
<el-table-column :index="logIndexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="变更积分" prop="changeAmount" width="120"></el-table-column>
<el-table-column align="center" label="变更类型" prop="changeType" width="120">
<template slot-scope="{row}">
<el-tag :type="row.changeType === 'GRANT' ? 'success' : 'danger'" size="small">{{ row.changeType === 'GRANT' ? '增加' : '扣减' }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="备注" min-width="180" prop="remark" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="变更时间" prop="createdAt" width="170">
<template slot-scope="{row}">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
</el-table>
<el-pagination :current-page="logPage.pageNumber" :page-size="logPage.pageSize" :total="logPage.totalCount" @current-change="logPageChange" layout="total, prev, pager, next" style="margin-top:16px;text-align:right"></el-pagination>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
tableData: [],
tableLoading: false,
submitLoading: false,
multipleSelection: [],
currentRow: null,
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, account: "", realName: "", mobile: "", member: null},
changeDialogVisible: false,
changeType: "grant",
changeForm: {points: 1, remark: ""},
batchDialogVisible: false,
batchType: "grant",
batchForm: {points: 1, remark: ""},
logDialogVisible: false,
logLoading: false,
logTitle: "积分详情",
logData: [],
logPage: {pageNumber: 1, pageSize: 10, totalCount: 0, userId: ""}
}
},
methods: {
indexMethod(index) {
return index + 1 + (this.pageForm.pageNumber - 1) * this.pageForm.pageSize
},
logIndexMethod(index) {
return index + 1 + (this.logPage.pageNumber - 1) * this.logPage.pageSize
},
formatTime(value) {
if (!value) return ""
return new Date(value).toLocaleString()
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageData()
},
async pageData() {
this.tableLoading = true
const res = await this.$axios.post("/platform/zhgh/points-mall/points/pageData", this.pageForm)
this.tableLoading = false
if (res.code === 0) {
this.tableData = res.data.list || []
this.pageForm.totalCount = res.data.totalCount || 0
} else {
this.$message.warning(res.msg)
}
},
pageChange(value) {
this.pageForm.pageNumber = value
this.pageData()
},
sizeChange(value) {
this.pageForm.pageSize = value
this.pageForm.pageNumber = 1
this.pageData()
},
handleSelectionChange(value) {
this.multipleSelection = value
},
openChange(row, type) {
if (type === "deduction" && Number(row.availablePoints || 0) <= 0) {
this.$message.warning("可用积分为0,请勿继续扣减")
return
}
this.currentRow = row
this.changeType = type
this.changeForm = {points: 1, remark: ""}
this.changeDialogVisible = true
},
async submitChange() {
if (!this.changeForm.points || this.changeForm.points <= 0) {
this.$message.warning("请输入有效的积分数量")
return
}
this.submitLoading = true
const url = this.changeType === "grant" ? "/doGrant" : "/doDeduction"
const res = await this.$axios.post("/platform/zhgh/points-mall/points" + url, {
userId: this.currentRow.userId,
points: this.changeForm.points,
remark: this.changeForm.remark
})
this.submitLoading = false
if (res.code === 0) {
this.$message.success(res.msg)
this.changeDialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg)
}
},
openBatch(type) {
if (!this.multipleSelection.length) {
this.$message.warning("请至少选择一个用户")
return
}
this.batchType = type
this.batchForm = {points: 1, remark: ""}
this.batchDialogVisible = true
},
async submitBatch() {
if (!this.batchForm.points || this.batchForm.points <= 0) {
this.$message.warning("请输入有效的积分数量")
return
}
this.submitLoading = true
const url = this.batchType === "grant" ? "/batchGrant" : "/batchDeduction"
const res = await this.$axios.post("/platform/zhgh/points-mall/points" + url, {
userIds: this.multipleSelection.map(item => item.userId).join(","),
points: this.batchForm.points,
remark: this.batchForm.remark
})
this.submitLoading = false
if (res.code === 0) {
this.$message.success(res.msg)
this.batchDialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg)
}
},
deleteSelected() {
const rows = this.multipleSelection.filter(item => item.id)
if (!rows.length) {
this.$message.warning("请选择已有积分记录的数据")
return
}
this.$confirm("确定删除选中的积分记录吗?", "提示", {
type: "warning",
callback: async action => {
if (action === "confirm") {
const res = await this.$axios.post("/platform/zhgh/points-mall/points/doDelete", {ids: rows.map(item => item.id).join(",")})
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.warning(res.msg)
}
}
}
})
},
openLog(row) {
this.currentRow = row
this.logTitle = "积分详情 - " + row.realName + "(" + row.account + ")"
this.logPage = {pageNumber: 1, pageSize: 10, totalCount: 0, userId: row.userId}
this.logDialogVisible = true
this.logPageData()
},
async logPageData() {
this.logLoading = true
const res = await this.$axios.post("/platform/zhgh/points-mall/points/logPage", this.logPage)
this.logLoading = false
if (res.code === 0) {
this.logData = res.data.list || []
this.logPage.totalCount = res.data.totalCount || 0
} else {
this.$message.warning(res.msg)
}
},
logPageChange(value) {
this.logPage.pageNumber = value
this.logPageData()
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->