326 lines
7.9 KiB
Vue
326 lines
7.9 KiB
Vue
<script setup>
|
|
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
|
|
import {
|
|
NButton,
|
|
NForm,
|
|
NFormItem,
|
|
NInput,
|
|
NSpace,
|
|
NSwitch,
|
|
NTag,
|
|
NPopconfirm,
|
|
NDatePicker,
|
|
} from 'naive-ui'
|
|
|
|
import CommonPage from '@/components/page/CommonPage.vue'
|
|
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
|
import CrudModal from '@/components/table/CrudModal.vue'
|
|
import CrudTable from '@/components/table/CrudTable.vue'
|
|
import LimitSettingModal from './LimitSettingModal.vue'
|
|
import UserDetailModal from './UserDetailModal.vue'
|
|
|
|
import { formatDate, renderIcon } from '@/utils'
|
|
import { useCRUD } from '@/composables'
|
|
import api from '@/api'
|
|
import TheIcon from '@/components/icon/TheIcon.vue'
|
|
|
|
defineOptions({ name: '用户管理' })
|
|
|
|
const $table = ref(null)
|
|
const queryItems = ref({})
|
|
const vPermission = resolveDirective('permission')
|
|
|
|
// 次数设置弹窗相关状态
|
|
const limitModalVisible = ref(false)
|
|
const currentUser = ref(null)
|
|
|
|
const detailModalVisible = ref(false)
|
|
const detailLoading = ref(false)
|
|
const userDetail = ref({
|
|
baseInfo: {},
|
|
invoiceHeaders: [],
|
|
operationLogs: [],
|
|
})
|
|
|
|
const {
|
|
modalVisible,
|
|
modalTitle,
|
|
modalAction,
|
|
modalLoading,
|
|
handleSave,
|
|
modalForm,
|
|
modalFormRef,
|
|
handleEdit,
|
|
handleDelete,
|
|
handleAdd,
|
|
} = useCRUD({
|
|
name: '用户',
|
|
initForm: { is_active: true },
|
|
doCreate: (data) => api.createAppUser({ phone: data.phone }),
|
|
doUpdate: (data) => api.updateAppUser(data),
|
|
doDelete: (data) => api.deleteAppUser({ id: data.id }),
|
|
refresh: () => $table.value?.handleSearch(),
|
|
})
|
|
|
|
onMounted(() => {
|
|
$table.value?.handleSearch()
|
|
})
|
|
|
|
const columns = [
|
|
{
|
|
title: 'ID',
|
|
key: 'id',
|
|
width: 100,
|
|
align: 'center',
|
|
ellipsis: { tooltip: true },
|
|
},
|
|
{
|
|
title: '手机号',
|
|
key: 'phone',
|
|
width: 140,
|
|
align: 'center',
|
|
ellipsis: { tooltip: true },
|
|
},
|
|
{
|
|
title: '微信号',
|
|
key: 'wechat',
|
|
width: 140,
|
|
align: 'center',
|
|
ellipsis: { tooltip: true },
|
|
render(row) {
|
|
return row.wechat || '-'
|
|
},
|
|
},
|
|
{
|
|
title: '注册时间',
|
|
key: 'created_at',
|
|
align: 'center',
|
|
width: 180,
|
|
ellipsis: { tooltip: true },
|
|
render(row) {
|
|
return row.created_at ? formatDate(row.created_at) : '-'
|
|
},
|
|
},
|
|
{
|
|
title: '备注',
|
|
key: 'notes',
|
|
align: 'center',
|
|
width: 120,
|
|
ellipsis: { tooltip: true },
|
|
render(row) {
|
|
return row.notes || '-'
|
|
},
|
|
},
|
|
{
|
|
title: '剩余体验次数',
|
|
key: 'remaining_count',
|
|
width: 120,
|
|
align: 'center',
|
|
render(row) {
|
|
return row.remaining_count !== undefined ? row.remaining_count : 0
|
|
},
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'actions',
|
|
width: 200,
|
|
align: 'center',
|
|
fixed: 'right',
|
|
render(row) {
|
|
return [
|
|
withDirectives(
|
|
h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
type: 'info',
|
|
style: 'margin-right: 8px;',
|
|
onClick: () => handleViewDetail(row),
|
|
},
|
|
{
|
|
default: () => '详情',
|
|
icon: renderIcon('material-symbols:info', { size: 16 }),
|
|
}
|
|
),
|
|
[[vPermission, 'get/api/v1/app_user/detail']]
|
|
),
|
|
withDirectives(
|
|
h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
type: 'primary',
|
|
style: 'margin-right: 8px;',
|
|
onClick: () => handleSetLimit(row),
|
|
},
|
|
{
|
|
default: () => '次数设置',
|
|
icon: renderIcon('material-symbols:settings', { size: 16 }),
|
|
}
|
|
),
|
|
[[vPermission, 'post/api/v1/app_user/set_limit']]
|
|
),
|
|
]
|
|
},
|
|
},
|
|
]
|
|
|
|
// 查看用户详情
|
|
async function handleViewDetail(row) {
|
|
detailModalVisible.value = true
|
|
detailLoading.value = true
|
|
try {
|
|
const detail = await api.getAppUserById({ id: row.id })
|
|
const baseInfoFromServer = detail?.baseInfo || {}
|
|
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,
|
|
},
|
|
invoiceHeaders: detail?.invoiceHeaders || [],
|
|
operationLogs: detail?.operationLogs || [],
|
|
}
|
|
} catch (error) {
|
|
$message.error('获取用户详情失败')
|
|
} finally {
|
|
detailLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 次数设置
|
|
function handleSetLimit(row) {
|
|
currentUser.value = row
|
|
limitModalVisible.value = true
|
|
}
|
|
|
|
// 保存次数设置
|
|
async function handleSaveLimitSetting(data) {
|
|
try {
|
|
// 这里调用API保存次数设置
|
|
// await api.setUserLimit(data)
|
|
$message.success('次数设置保存成功')
|
|
limitModalVisible.value = false
|
|
$table.value?.handleSearch()
|
|
} catch (error) {
|
|
$message.error('保存失败: ' + error.message)
|
|
}
|
|
}
|
|
|
|
const validateForm = {
|
|
phone: [
|
|
{
|
|
required: true,
|
|
message: '请输入手机号',
|
|
trigger: ['input', 'blur'],
|
|
},
|
|
{
|
|
trigger: ['blur'],
|
|
validator: (rule, value, callback) => {
|
|
const phoneRegex = /^1[3-9]\d{9}$/
|
|
if (!phoneRegex.test(value)) {
|
|
callback('请输入正确的手机号格式')
|
|
return
|
|
}
|
|
callback()
|
|
},
|
|
},
|
|
],
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<CommonPage show-footer title="用户列表">
|
|
<template #action>
|
|
<!-- <NButton v-permission="'post/api/v1/app_user/create'" type="primary" @click="handleAdd">
|
|
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建用户
|
|
</NButton> -->
|
|
</template>
|
|
|
|
<!-- 表格 -->
|
|
<CrudTable
|
|
ref="$table"
|
|
v-model:query-items="queryItems"
|
|
:columns="columns"
|
|
:get-data="api.getAppUserList"
|
|
>
|
|
<template #queryBar>
|
|
<QueryBarItem label="手机号" :label-width="60">
|
|
<NInput
|
|
v-model:value="queryItems.phone"
|
|
clearable
|
|
type="text"
|
|
placeholder="请输入手机号"
|
|
style="width: 200px"
|
|
@keypress.enter="$table?.handleSearch()"
|
|
/>
|
|
</QueryBarItem>
|
|
<QueryBarItem label="微信号" :label-width="60">
|
|
<NInput
|
|
v-model:value="queryItems.wechat"
|
|
clearable
|
|
type="text"
|
|
placeholder="请输入微信号"
|
|
style="width: 200px"
|
|
@keypress.enter="$table?.handleSearch()"
|
|
/>
|
|
</QueryBarItem>
|
|
<QueryBarItem label="注册时间" :label-width="70">
|
|
<NDatePicker
|
|
v-model:value="queryItems.created_at"
|
|
type="daterange"
|
|
clearable
|
|
placeholder="请选择注册时间"
|
|
style="width: 280px"
|
|
@update:value="$table?.handleSearch()"
|
|
/>
|
|
</QueryBarItem>
|
|
</template>
|
|
</CrudTable>
|
|
|
|
<!-- 新增/编辑 弹窗 -->
|
|
<CrudModal
|
|
v-model:visible="modalVisible"
|
|
:title="modalTitle"
|
|
:loading="modalLoading"
|
|
@save="handleSave"
|
|
>
|
|
<NForm
|
|
ref="modalFormRef"
|
|
label-placement="left"
|
|
label-align="left"
|
|
:label-width="80"
|
|
:model="modalForm"
|
|
:rules="validateForm"
|
|
>
|
|
<NFormItem label="手机号" path="phone">
|
|
<NInput v-model:value="modalForm.phone" clearable placeholder="请输入手机号" />
|
|
</NFormItem>
|
|
<NFormItem v-if="modalAction === 'add'" label="说明">
|
|
<span style="color: #999; font-size: 12px;">
|
|
注册后默认密码为手机号后6位
|
|
</span>
|
|
</NFormItem>
|
|
</NForm>
|
|
</CrudModal>
|
|
|
|
<!-- 次数设置弹窗 -->
|
|
<LimitSettingModal
|
|
v-model:visible="limitModalVisible"
|
|
:user-data="currentUser"
|
|
@save="handleSaveLimitSetting"
|
|
/>
|
|
|
|
<!-- 用户详情弹窗 -->
|
|
<UserDetailModal
|
|
v-model:visible="detailModalVisible"
|
|
:data="userDetail"
|
|
:loading="detailLoading"
|
|
/>
|
|
</CommonPage>
|
|
</template>
|