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 @@
-
+
修改密码
+ 下放积分
+ 回收积分
+
-
+
+
+
+
+ {{ deptScoreForm.username }}
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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'}"
>
-
+
+
+
+ {{ scope.row.balance != null ? scope.row.balance : '—' }}
+
+
-
+
{{ parseTime(scope.row.createTime) }}
+ 充值/退款
+ 积分更正
取 消
+
+
+
+
+
+ 充值
+ 退款
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -241,10 +345,23 @@
color: #909399;
line-height: 1.5;
}
+.edit-score-input-number {
+ width: 100%;
+}