feat: 替换用户管理模块的模拟数据为真实接口,并优化额度设置功能
This commit is contained in:
parent
7c64f0c76a
commit
2ff5421c27
74
menu_init.sql
Normal file
74
menu_init.sql
Normal file
@ -0,0 +1,74 @@
|
||||
-- 完整菜单初始化SQL
|
||||
-- 创建时间: 2025-11-20
|
||||
-- 说明: 包含所有新增的菜单项和权限分配
|
||||
|
||||
-- ========================================
|
||||
-- 1. 工作台菜单
|
||||
-- ========================================
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(22, '工作台', 'menu', 'carbon:dashboard', '/workbench', 1, 0, 0, '/workbench', 1, NULL, datetime('now'), datetime('now'));
|
||||
|
||||
-- ========================================
|
||||
-- 2. 交易管理菜单
|
||||
-- ========================================
|
||||
-- 插入一级目录:交易管理
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(16, '交易管理', 'catalog', 'carbon:receipt', '/transaction', 3, 0, 0, 'Layout', 0, '/transaction/invoice', datetime('now'), datetime('now'));
|
||||
|
||||
-- 插入二级菜单:开票记录
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(17, '开票记录', 'menu', 'carbon:document', 'invoice', 1, 16, 0, '/transaction/invoice', 0, NULL, datetime('now'), datetime('now'));
|
||||
|
||||
-- ========================================
|
||||
-- 3. 估值管理菜单
|
||||
-- ========================================
|
||||
-- 插入一级目录:估值管理
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(18, '估值管理', 'catalog', 'carbon:calculator', '/valuation', 4, 0, 0, 'Layout', 0, '/valuation/audit', datetime('now'), datetime('now'));
|
||||
|
||||
-- 插入二级菜单:审核列表
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(19, '审核列表', 'menu', 'carbon:task-approved', 'audit', 1, 18, 0, '/valuation/audit', 0, NULL, datetime('now'), datetime('now'));
|
||||
|
||||
-- ========================================
|
||||
-- 4. 用户管理菜单
|
||||
-- ========================================
|
||||
-- 插入一级目录:用户管理
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(20, '用户管理', 'catalog', 'carbon:user-multiple', '/user-management', 5, 0, 0, 'Layout', 0, '/user-management/user-list', datetime('now'), datetime('now'));
|
||||
|
||||
-- 插入二级菜单:用户列表
|
||||
INSERT INTO menu (id, name, menu_type, icon, path, "order", parent_id, is_hidden, component, keepalive, redirect, created_at, updated_at)
|
||||
VALUES
|
||||
(21, '用户列表', 'menu', 'carbon:user', 'user-list', 1, 20, 0, '/user-management/user-list', 0, NULL, datetime('now'), datetime('now'));
|
||||
|
||||
-- ========================================
|
||||
-- 角色权限分配
|
||||
-- ========================================
|
||||
|
||||
-- 为管理员角色(role_id=1)分配所有菜单权限
|
||||
INSERT INTO role_menu (role_id, menu_id)
|
||||
VALUES
|
||||
(1, 22), -- 工作台
|
||||
(1, 16), -- 交易管理
|
||||
(1, 17), -- 开票记录
|
||||
(1, 18), -- 估值管理
|
||||
(1, 19), -- 审核列表
|
||||
(1, 20), -- 用户管理
|
||||
(1, 21); -- 用户列表
|
||||
|
||||
-- 为普通用户角色(role_id=2)分配基础菜单权限
|
||||
INSERT INTO role_menu (role_id, menu_id)
|
||||
VALUES
|
||||
(2, 22), -- 工作台
|
||||
(2, 16), -- 交易管理
|
||||
(2, 17), -- 开票记录
|
||||
(2, 18), -- 估值管理
|
||||
(2, 19); -- 审核列表
|
||||
-- 注意:普通用户不分配用户管理权限
|
||||
@ -5,4 +5,4 @@ VITE_PUBLIC_PATH = '/'
|
||||
VITE_USE_PROXY = true
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = '/api/v1'
|
||||
VITE_BASE_API = 'http://139.224.70.152:9990/api/v1'
|
||||
|
||||
@ -276,120 +276,6 @@ const mockValuationDetails = valuationRecords.map((record) => ({
|
||||
...record,
|
||||
}))
|
||||
|
||||
const mockAppUsers = [
|
||||
{
|
||||
id: 11111111,
|
||||
phone: '15021982682',
|
||||
wechat: 'f1498480844',
|
||||
created_at: '2024-01-15T10:30:00Z',
|
||||
notes: '测试用户1',
|
||||
remaining_count: 1,
|
||||
user_type: '体验用户',
|
||||
},
|
||||
{
|
||||
id: 11111112,
|
||||
phone: '13800138002',
|
||||
wechat: 'wx_limming2024',
|
||||
created_at: '2024-02-20T14:20:00Z',
|
||||
notes: '付费用户',
|
||||
remaining_count: 5,
|
||||
user_type: '付费用户',
|
||||
},
|
||||
{
|
||||
id: 11111113,
|
||||
phone: '13800138003',
|
||||
wechat: null,
|
||||
created_at: '2024-03-10T08:45:00Z',
|
||||
notes: null,
|
||||
remaining_count: 0,
|
||||
user_type: '体验用户',
|
||||
},
|
||||
{
|
||||
id: 11111114,
|
||||
phone: '13800138004',
|
||||
wechat: 'chenjun_vip',
|
||||
created_at: '2024-04-05T11:30:00Z',
|
||||
notes: 'VIP用户',
|
||||
remaining_count: 10,
|
||||
user_type: 'VIP',
|
||||
},
|
||||
{
|
||||
id: 11111115,
|
||||
phone: '13800138005',
|
||||
wechat: 'liuxia888',
|
||||
created_at: '2024-05-12T16:15:00Z',
|
||||
notes: '体验用户',
|
||||
remaining_count: 3,
|
||||
user_type: '体验用户',
|
||||
},
|
||||
{
|
||||
id: 11111116,
|
||||
phone: '13800138006',
|
||||
wechat: null,
|
||||
created_at: '2024-06-18T09:00:00Z',
|
||||
notes: '新注册用户',
|
||||
remaining_count: 2,
|
||||
user_type: '体验用户',
|
||||
},
|
||||
{
|
||||
id: 11111117,
|
||||
phone: '13800138007',
|
||||
wechat: 'zhaolei2024',
|
||||
created_at: '2024-07-22T12:45:00Z',
|
||||
notes: null,
|
||||
remaining_count: 0,
|
||||
user_type: '体验用户',
|
||||
},
|
||||
{
|
||||
id: 11111118,
|
||||
phone: '13800138008',
|
||||
wechat: 'sunmei_user',
|
||||
created_at: '2024-08-30T15:20:00Z',
|
||||
notes: '活跃用户',
|
||||
remaining_count: 7,
|
||||
user_type: 'VIP',
|
||||
},
|
||||
]
|
||||
|
||||
const defaultInvoiceHeaders = [
|
||||
{
|
||||
company_name: '成都文创科技有限公司',
|
||||
tax_number: '91510100MA7XYZ1234',
|
||||
register_address: '四川省成都市高新区天府三街666号',
|
||||
register_phone: '028-66666666',
|
||||
bank_name: '招商银行成都分行',
|
||||
bank_account: '6225 6666 8888 0000',
|
||||
email: 'finance@scwenchuang.com',
|
||||
},
|
||||
{
|
||||
company_name: '天府文化发展有限公司',
|
||||
tax_number: '91510100678912345K',
|
||||
register_address: '四川省成都市武侯区科华北路88号',
|
||||
register_phone: '028-12345678',
|
||||
bank_name: '中国工商银行成都分行',
|
||||
bank_account: '6212 8888 0000 9999',
|
||||
email: 'invoice@tfculture.com',
|
||||
},
|
||||
]
|
||||
|
||||
const defaultOperationLogs = [
|
||||
{
|
||||
time: '2025-10-31 18:30:30',
|
||||
operator: 'admin',
|
||||
records: ['剩余估值次数:0 -> 1', '类型:付费估值', '备注:新用户'],
|
||||
},
|
||||
{
|
||||
time: '2025-10-31 18:30:30',
|
||||
operator: 'admin',
|
||||
records: ['剩余估值次数:2 -> 1', '类型:付费估值', '备注:退款'],
|
||||
},
|
||||
{
|
||||
time: '2025-10-31 18:30:30',
|
||||
operator: 'admin',
|
||||
records: ['用户备注:111111111111 -> 22222222222222222222'],
|
||||
},
|
||||
]
|
||||
|
||||
export default {
|
||||
login: (data) => request.post('/base/access_token', data, { noNeedToken: true }),
|
||||
getUserInfo: () => request.get('/base/userinfo'),
|
||||
@ -430,73 +316,10 @@ export default {
|
||||
// auditlog
|
||||
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
||||
// app users (客户端用户管理) - 使用现有的后端接口
|
||||
getAppUserList: (params = {}) => {
|
||||
// 模拟分页和搜索
|
||||
let filteredUsers = [...mockAppUsers]
|
||||
|
||||
// 手机号搜索
|
||||
if (params.phone) {
|
||||
filteredUsers = filteredUsers.filter(user =>
|
||||
user.phone.includes(params.phone)
|
||||
)
|
||||
}
|
||||
|
||||
// 微信号搜索
|
||||
if (params.wechat) {
|
||||
filteredUsers = filteredUsers.filter(user =>
|
||||
user.wechat && user.wechat.includes(params.wechat)
|
||||
)
|
||||
}
|
||||
|
||||
// 注册时间筛选(日期范围)
|
||||
if (params.created_at && Array.isArray(params.created_at) && params.created_at.length === 2) {
|
||||
const [startTime, endTime] = params.created_at
|
||||
filteredUsers = filteredUsers.filter(user => {
|
||||
if (!user.created_at) return false
|
||||
const userTime = new Date(user.created_at).getTime()
|
||||
return userTime >= startTime && userTime <= endTime
|
||||
})
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const page = Number(params.page) || 1
|
||||
const pageSize = Number(params.page_size) || 10
|
||||
const startIndex = (page - 1) * pageSize
|
||||
const endIndex = startIndex + pageSize
|
||||
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
|
||||
|
||||
// 返回 Promise 模拟异步请求
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
data: paginatedUsers,
|
||||
total: filteredUsers.length,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
})
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
getAppUserById: (params = {}) =>
|
||||
new Promise((resolve) => {
|
||||
const id = Number(params.id)
|
||||
const user = mockAppUsers.find((item) => item.id === id) || {}
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
baseInfo: {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
wechat: user.wechat,
|
||||
register_time: user.created_at,
|
||||
notes: user.notes,
|
||||
remaining_count: user.remaining_count,
|
||||
user_type: user.user_type || '体验用户',
|
||||
},
|
||||
invoiceHeaders: defaultInvoiceHeaders,
|
||||
operationLogs: defaultOperationLogs,
|
||||
})
|
||||
}, 300)
|
||||
}),
|
||||
getAppUserList: (params = {}) => request.get('/app-user-admin/list', { params }),
|
||||
updateAppUserQuota: (data = {}) => request.post('/app-user-admin/quota', data),
|
||||
getAppUserQuotaLogs: ({ user_id, ...params } = {}) =>
|
||||
request.get(`/app-user-admin/${user_id}/quota-logs`, { params }),
|
||||
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
||||
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
||||
deleteAppUser: (params = {}) => request.delete('/app-user/delete', { params }),
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import {
|
||||
NModal,
|
||||
NButton,
|
||||
NSelect,
|
||||
NInput,
|
||||
NDivider,
|
||||
} from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { NModal, NButton, NSelect, NInput, NDivider, NInputNumber } from 'naive-ui'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@ -25,44 +19,37 @@ const emit = defineEmits(['update:visible', 'save'])
|
||||
|
||||
// 本地状态
|
||||
const limitForm = ref({
|
||||
remainingCount: 0,
|
||||
type: '免费体验',
|
||||
experienceCount: 1,
|
||||
notes: ''
|
||||
targetCount: 0,
|
||||
quotaType: '免费体验',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
// 类型选项
|
||||
const typeOptions = [
|
||||
{ label: '免费体验', value: '免费体验' },
|
||||
{ label: '付费用户', value: '付费用户' },
|
||||
{ label: 'VIP用户', value: 'VIP用户' }
|
||||
{ label: '付费评估', value: '付费评估' }
|
||||
]
|
||||
|
||||
const currentRemaining = computed(() => props.userData?.remaining_count ?? 0)
|
||||
|
||||
// 监听用户数据变化,初始化表单
|
||||
watch(() => props.userData, (newData) => {
|
||||
if (newData && Object.keys(newData).length > 0) {
|
||||
limitForm.value = {
|
||||
remainingCount: newData.remaining_count || 0,
|
||||
type: newData.user_type || '免费体验',
|
||||
experienceCount: newData.experience_count || 1,
|
||||
notes: newData.notes || ''
|
||||
targetCount: newData.remaining_count || 0,
|
||||
quotaType: newData.user_type || '免费体验',
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 体验次数增减
|
||||
function handleExperienceCountChange(delta) {
|
||||
const newCount = limitForm.value.experienceCount + delta
|
||||
if (newCount >= 0) {
|
||||
limitForm.value.experienceCount = newCount
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
function handleSave() {
|
||||
const data = {
|
||||
user_id: props.userData.id,
|
||||
...limitForm.value
|
||||
target_count: Number(limitForm.value.targetCount || 0),
|
||||
op_type: limitForm.value.quotaType,
|
||||
remark: limitForm.value.remark
|
||||
}
|
||||
emit('save', data)
|
||||
}
|
||||
@ -72,10 +59,9 @@ function handleCancel() {
|
||||
emit('update:visible', false)
|
||||
// 重置表单
|
||||
limitForm.value = {
|
||||
remainingCount: 0,
|
||||
type: '免费体验',
|
||||
experienceCount: 1,
|
||||
notes: ''
|
||||
targetCount: 0,
|
||||
quotaType: '免费体验',
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -96,7 +82,7 @@ function handleCancel() {
|
||||
<!-- 剩余估值次数 -->
|
||||
<div class="form-row">
|
||||
<span class="label">剩余估值次数:</span>
|
||||
<span class="value">{{ limitForm.remainingCount }}</span>
|
||||
<span class="value">{{ currentRemaining }}</span>
|
||||
</div>
|
||||
|
||||
<NDivider style="margin: 16px 0;" />
|
||||
@ -105,31 +91,20 @@ function handleCancel() {
|
||||
<div class="form-row">
|
||||
<span class="label">类型:</span>
|
||||
<NSelect
|
||||
v-model:value="limitForm.type"
|
||||
v-model:value="limitForm.quotaType"
|
||||
:options="typeOptions"
|
||||
style="width: 120px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 体验次数 -->
|
||||
<!-- 目标次数 -->
|
||||
<div class="form-row">
|
||||
<span class="label">体验次数:</span>
|
||||
<div class="count-control">
|
||||
<NButton
|
||||
size="small"
|
||||
@click="handleExperienceCountChange(-1)"
|
||||
:disabled="limitForm.experienceCount <= 0"
|
||||
>
|
||||
-
|
||||
</NButton>
|
||||
<span class="count-value">{{ limitForm.experienceCount }}</span>
|
||||
<NButton
|
||||
size="small"
|
||||
@click="handleExperienceCountChange(1)"
|
||||
>
|
||||
+
|
||||
</NButton>
|
||||
</div>
|
||||
<span class="label">估值次数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="limitForm.targetCount"
|
||||
:min="0"
|
||||
style="width: 160px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 备注 -->
|
||||
@ -137,7 +112,7 @@ function handleCancel() {
|
||||
<span class="label">备注:</span>
|
||||
</div>
|
||||
<NInput
|
||||
v-model:value="limitForm.notes"
|
||||
v-model:value="limitForm.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="4"
|
||||
@ -184,19 +159,6 @@ function handleCancel() {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.count-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.count-value {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@ -45,17 +45,14 @@ const invoiceColumns = [
|
||||
]
|
||||
|
||||
const logColumns = [
|
||||
{ title: '操作时间', key: 'time', width: 160 },
|
||||
{ title: '操作人', key: 'operator', width: 100 },
|
||||
{ title: 'ID', key: 'id', width: 80 },
|
||||
{ title: '操作人', key: 'operator_name', width: 120 },
|
||||
{ title: '操作类型', key: 'op_type', width: 120, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '操作记录',
|
||||
key: 'records',
|
||||
render: (row) =>
|
||||
h(
|
||||
'div',
|
||||
{ class: 'log-record' },
|
||||
row.records?.map((item, idx) => h('div', { key: idx }, item))
|
||||
),
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
ellipsis: { tooltip: true },
|
||||
render: (row) => row.remark || '-',
|
||||
},
|
||||
]
|
||||
|
||||
@ -209,10 +206,6 @@ function handleClose() {
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.log-record div + div {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@ -1,16 +1,6 @@
|
||||
<script setup>
|
||||
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
|
||||
import {
|
||||
NButton,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
NTag,
|
||||
NPopconfirm,
|
||||
NDatePicker,
|
||||
} from 'naive-ui'
|
||||
import { NButton, NForm, NFormItem, NInput, NDatePicker } from 'naive-ui'
|
||||
|
||||
import CommonPage from '@/components/page/CommonPage.vue'
|
||||
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
||||
@ -142,7 +132,7 @@ const columns = [
|
||||
icon: renderIcon('material-symbols:info', { size: 16 }),
|
||||
}
|
||||
),
|
||||
[[vPermission, 'get/api/v1/app_user/detail']]
|
||||
[[vPermission, 'get/api/v1/app-user-admin/list']]
|
||||
),
|
||||
withDirectives(
|
||||
h(
|
||||
@ -158,7 +148,7 @@ const columns = [
|
||||
icon: renderIcon('material-symbols:settings', { size: 16 }),
|
||||
}
|
||||
),
|
||||
[[vPermission, 'post/api/v1/app_user/set_limit']]
|
||||
[[vPermission, 'post/api/v1/app-user-admin/quota']]
|
||||
),
|
||||
]
|
||||
},
|
||||
@ -170,23 +160,26 @@ async function handleViewDetail(row) {
|
||||
detailModalVisible.value = true
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const detail = await api.getAppUserById({ id: row.id })
|
||||
const baseInfoFromServer = detail?.baseInfo || {}
|
||||
const { data: logs = [] } = await api.getAppUserQuotaLogs({
|
||||
user_id: row.id,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
})
|
||||
userDetail.value = {
|
||||
baseInfo: {
|
||||
...baseInfoFromServer,
|
||||
id: row.id,
|
||||
phone: row.phone,
|
||||
wechat: row.wechat,
|
||||
register_time: row.created_at ? formatDate(row.created_at) : '-',
|
||||
notes: row.notes,
|
||||
remaining_count: row.remaining_count,
|
||||
user_type: row.user_type || '-',
|
||||
},
|
||||
invoiceHeaders: detail?.invoiceHeaders || [],
|
||||
operationLogs: detail?.operationLogs || [],
|
||||
invoiceHeaders: [],
|
||||
operationLogs: logs,
|
||||
}
|
||||
} catch (error) {
|
||||
$message.error('获取用户详情失败')
|
||||
$message.error(error?.message || '获取用户详情失败')
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
@ -201,13 +194,17 @@ function handleSetLimit(row) {
|
||||
// 保存次数设置
|
||||
async function handleSaveLimitSetting(data) {
|
||||
try {
|
||||
// 这里调用API保存次数设置
|
||||
// await api.setUserLimit(data)
|
||||
await api.updateAppUserQuota({
|
||||
user_id: data.user_id,
|
||||
target_count: data.target_count,
|
||||
op_type: data.op_type,
|
||||
remark: data.remark,
|
||||
})
|
||||
$message.success('次数设置保存成功')
|
||||
limitModalVisible.value = false
|
||||
$table.value?.handleSearch()
|
||||
} catch (error) {
|
||||
$message.error('保存失败: ' + error.message)
|
||||
$message.error(error?.message || '保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,4 +5,5 @@ VITE_PUBLIC_PATH = '/'
|
||||
VITE_USE_PROXY = true
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
VITE_BASE_API = 'http://139.224.70.152:9990/api/v1'
|
||||
# VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user