diff --git a/admin-ui/src/api/ai/dept.js b/admin-ui/src/api/ai/dept.js index 415856b..c8a3b98 100644 --- a/admin-ui/src/api/ai/dept.js +++ b/admin-ui/src/api/ai/dept.js @@ -44,3 +44,11 @@ export function delDept(deptId) { method: 'delete' }) } + +export function chargeRefundDept(data) { + return request({ + url: '/ai/dept/charge-refund', + method: 'post', + data: data + }) +} diff --git a/admin-ui/src/api/ai/user.js b/admin-ui/src/api/ai/user.js index 389afbc..4077d9f 100644 --- a/admin-ui/src/api/ai/user.js +++ b/admin-ui/src/api/ai/user.js @@ -56,18 +56,18 @@ export function changeUserStatus(id, status) { }) } -export function changeBalance(id, balance) { - const data = { - id, - balance - } - return request({ - url: '/ai/user/changeBalance', - method: 'put', - data: data - }) -} - +// 修改余额接口已关闭(与后端 AiUserController.changeBalance 一致停用) +// export function changeBalance(id, balance) { +// const data = { +// id, +// balance +// } +// return request({ +// url: '/ai/user/changeBalance', +// method: 'put', +// data: data +// }) +// } // 用户状态修改 export function updatePassword(id, newPassword) { @@ -90,3 +90,21 @@ export function assignAiUserDept(data) { data }) } + +/** 部门积分下放至用户 */ +export function issueDeptScore(data) { + return request({ + url: '/ai/user/dept-score/issue', + method: 'put', + data + }) +} + +/** 用户积分回收至部门 */ +export function reclaimDeptScore(data) { + return request({ + url: '/ai/user/dept-score/reclaim', + method: 'put', + data + }) +} diff --git a/admin-ui/src/api/system/dept.js b/admin-ui/src/api/system/dept.js index fc943cd..546b482 100644 --- a/admin-ui/src/api/system/dept.js +++ b/admin-ui/src/api/system/dept.js @@ -49,4 +49,22 @@ export function delDept(deptId) { url: '/system/dept/' + deptId, method: 'delete' }) +} + +// 部门充值/退款 +export function chargeRefundDept(data) { + return request({ + url: '/system/dept/charge-refund', + method: 'post', + data: data + }) +} + +// 部门积分更正 +export function editScore(data) { + return request({ + url: '/system/dept/edit-score', + method: 'post', + data: data + }) } \ No newline at end of file diff --git a/admin-ui/src/utils/westernNumberFormat.js b/admin-ui/src/utils/westernNumberFormat.js new file mode 100644 index 0000000..fd216df --- /dev/null +++ b/admin-ui/src/utils/westernNumberFormat.js @@ -0,0 +1,76 @@ +/** + * 西式千分位(en-US):金额最多两位小数,积分为整数。 + * 用于输入/粘贴后即时格式化展示。 + */ + +export const WESTERN_MONEY_MAX = 10000000 +export const WESTERN_INT_MAX = 100000000 + +const MONEY_MAX = WESTERN_MONEY_MAX +const INT_MAX = WESTERN_INT_MAX + +/** 只保留数字与至多一个小数点,小数位最多 2 位 */ +export function sanitizeMoneyDigits(raw) { + let s = String(raw == null ? '' : raw).replace(/,/g, '').replace(/[^\d.]/g, '') + const firstDot = s.indexOf('.') + if (firstDot !== -1) { + s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '').slice(0, 2) + } + return s +} + +/** 将规范化后的金额字符串格式化为 9,999,999.99 形式(输入过程中允许末尾为小数点) */ +export function formatMoneyWesternDisplay(sanitized) { + if (!sanitized) return '' + const s = sanitized + const dot = s.indexOf('.') + const intRaw = dot === -1 ? s : s.slice(0, dot) + const decRaw = dot === -1 ? '' : s.slice(dot + 1).slice(0, 2) + const intNum = intRaw === '' ? 0 : parseInt(intRaw, 10) + const intFmt = (isNaN(intNum) ? 0 : intNum).toLocaleString('en-US') + if (dot === -1) return intFmt + if (s.endsWith('.') && decRaw === '') return intFmt + '.' + return intFmt + '.' + decRaw +} + +/** 规范化字符串 -> 金额数值(undefined 表示空) */ +export function moneyStringToNumber(sanitized) { + if (sanitized === '' || sanitized === '.' || sanitized == null) return undefined + const n = parseFloat(sanitized) + if (isNaN(n)) return undefined + const rounded = Math.round(n * 100) / 100 + if (rounded > MONEY_MAX) return MONEY_MAX + if (rounded < 0) return 0 + return rounded +} + +/** 失焦时金额固定两位小数展示 */ +export function formatMoneyWesternFinal(n) { + if (n === undefined || n === null || isNaN(n)) return '' + const v = Math.min(MONEY_MAX, Math.max(0, Math.round(Number(n) * 100) / 100)) + return v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) +} + +/** 积分:仅数字,并限制范围 */ +export function sanitizeIntDigits(raw) { + let d = String(raw == null ? '' : raw).replace(/,/g, '').replace(/\D/g, '') + if (d === '') return '' + let n = parseInt(d, 10) + if (isNaN(n)) return '' + n = Math.min(INT_MAX, Math.max(0, n)) + return String(n) +} + +export function formatIntWesternDisplay(digits) { + if (!digits) return '' + const n = parseInt(digits, 10) + if (isNaN(n)) return '' + return n.toLocaleString('en-US') +} + +export function intStringToNumber(digits) { + if (!digits) return undefined + const n = parseInt(digits, 10) + if (isNaN(n)) return undefined + return n +} diff --git a/admin-ui/src/views/ai/user/index.vue b/admin-ui/src/views/ai/user/index.vue index 1465e0d..3f35b1b 100644 --- a/admin-ui/src/views/ai/user/index.vue +++ b/admin-ui/src/views/ai/user/index.vue @@ -106,8 +106,10 @@ + @@ -135,7 +137,7 @@ - + @@ -286,10 +345,11 @@ import { delUser, addUser, updateUser, - changeBalance, changeUserStatus, updatePassword, - assignAiUserDept + assignAiUserDept, + issueDeptScore, + reclaimDeptScore } from "@/api/ai/user"; import { listDept } from "@/api/ai/dept"; import Treeselect from "@riophae/vue-treeselect"; @@ -320,7 +380,27 @@ export default { // 是否显示弹出层 open: false, openUpdatePassword: false, - openUpdateBalance: false, + deptScoreOpen: false, + deptScoreMode: "issue", + deptScoreForm: { + userId: null, + username: "", + amount: undefined, + remark: "" + }, + deptScoreRules: { + amount: [ + { required: true, message: "请输入积分数量", trigger: "blur" }, + { + type: "number", + min: 1, + max: 100000000, + message: "请输入 1~100000000 的整数", + trigger: "blur" + } + ], + remark: [{ max: 50, message: "备注最多50个字", trigger: "blur" }] + }, assignDeptOpen: false, deptOptions: [], assignForm: { @@ -337,7 +417,7 @@ export default { phone: null, password: null, openid: null, - status: null, + status: "0", email: null, birthday: null, invitationCode: null, @@ -417,9 +497,9 @@ export default { case "updatePassword": this.updatePassword(row); break; - case "updateBalance": - this.updateBalance(row); - break; + // case "updateBalance": + // this.updateBalance(row); + // break; default: break; } @@ -437,15 +517,62 @@ export default { }); return; }, - updateBalance(row) { - this.reset(); - const id = row.id || this.ids; - getUser(id).then(response => { - this.form = response.data; - this.openUpdateBalance = true; - this.title = "修改余额"; + openDeptScoreDialog(row, mode) { + if (!row.deptId) { + this.$modal.msgWarning("请先分配归属部门后再操作积分"); + return; + } + this.deptScoreMode = mode; + this.deptScoreForm = { + userId: row.id, + username: row.username || row.userId || "", + amount: undefined, + remark: "" + }; + this.deptScoreOpen = true; + this.$nextTick(() => { + if (this.$refs.deptScoreFormRef) { + this.$refs.deptScoreFormRef.clearValidate(); + } }); }, + submitDeptScore() { + this.$refs.deptScoreFormRef.validate(valid => { + if (!valid) { + return; + } + const raw = this.deptScoreForm.amount; + const amount = typeof raw === "number" ? Math.trunc(raw) : parseInt(String(raw), 10); + if (!Number.isFinite(amount) || amount < 1 || amount > 100000000) { + this.$modal.msgWarning("请输入 1~100000000 的整数积分"); + return; + } + const payload = { + userId: this.deptScoreForm.userId, + amount, + remark: this.deptScoreForm.remark || undefined + }; + const req = this.deptScoreMode === "issue" ? issueDeptScore : reclaimDeptScore; + req(payload).then(() => { + this.$modal.msgSuccess("操作成功"); + this.deptScoreOpen = false; + this.getList(); + }); + }); + }, + cancelDeptScore() { + this.deptScoreOpen = false; + this.deptScoreForm = { userId: null, username: "", amount: undefined, remark: "" }; + }, + // updateBalance(row) { + // this.reset(); + // const id = row.id || this.ids; + // getUser(id).then(response => { + // this.form = response.data; + // this.openUpdateBalance = true; + // this.title = "修改余额"; + // }); + // }, /** 查询ai-用户信息列表 */ getList() { this.loading = true; @@ -475,10 +602,10 @@ export default { this.open = false; this.reset(); }, - cancelBalance() { - this.openUpdateBalance = false; - this.reset(); - }, + // cancelBalance() { + // this.openUpdateBalance = false; + // this.reset(); + // }, cancelPassword() { this.openUpdatePassword = false; this.reset(); @@ -567,16 +694,16 @@ export default { } }); }, - handleChangeBalance() { - changeBalance; - this.$refs["form"].validate(valid => { - changeBalance(this.form.id, this.form.balance).then(response => { - this.$modal.msgSuccess("修改成功"); - this.openUpdateBalance = false; - this.getList(); - }); - }); - }, + // handleChangeBalance() { + // this.$refs["form"].validate(valid => { + // if (!valid) return; + // changeBalance(this.form.id, this.form.balance).then(() => { + // this.$modal.msgSuccess("修改成功"); + // this.openUpdateBalance = false; + // this.getList(); + // }); + // }); + // }, /** 删除按钮操作 */ handleDelete(row) { const ids = row && row.id != null ? row.id : this.ids; diff --git a/admin-ui/src/views/system/dept/index.vue b/admin-ui/src/views/system/dept/index.vue index b28b1c5..ff3f9ad 100644 --- a/admin-ui/src/views/system/dept/index.vue +++ b/admin-ui/src/views/system/dept/index.vue @@ -57,19 +57,38 @@ :tree-props="{children: 'children', hasChildren: 'hasChildren'}" > - + + + + - + @@ -241,10 +345,23 @@ color: #909399; line-height: 1.5; } +.edit-score-input-number { + width: 100%; +}