管理后台修改.
This commit is contained in:
parent
3b5dd2bad6
commit
4b27db42d3
|
|
@ -70,8 +70,8 @@
|
||||||
<el-table-column label="用户信息" min-width="150">
|
<el-table-column label="用户信息" min-width="150">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<span class="nickname">{{ row.user?.nickname || row.user?.realName || '-' }}</span>
|
<span class="nickname">{{ row.user?.nickname || '-' }}</span>
|
||||||
<span class="phone">{{ row.user?.phone || '-' }}</span>
|
<span class="uid">UID: {{ row.user?.uid || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
@ -137,7 +137,7 @@
|
||||||
{{ formatDate(row.createdAt) }}
|
{{ formatDate(row.createdAt) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link size="small" @click="handleViewDetails(row)">
|
<el-button type="primary" link size="small" @click="handleViewDetails(row)">
|
||||||
<el-icon><View /></el-icon>
|
<el-icon><View /></el-icon>
|
||||||
|
|
@ -147,6 +147,10 @@
|
||||||
<el-icon><Edit /></el-icon>
|
<el-icon><Edit /></el-icon>
|
||||||
状态
|
状态
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="success" link size="small" @click="handleCreatePaymentOrder(row)">
|
||||||
|
<el-icon><Money /></el-icon>
|
||||||
|
收款
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -512,19 +516,16 @@
|
||||||
<el-descriptions-item label="用户昵称">
|
<el-descriptions-item label="用户昵称">
|
||||||
{{ appointmentDetails.user.nickname || '-' }}
|
{{ appointmentDetails.user.nickname || '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="真实姓名">
|
<el-descriptions-item label="UID">
|
||||||
{{ appointmentDetails.user.realName || '-' }}
|
{{ appointmentDetails.user.uid || '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="手机号">
|
<el-descriptions-item>
|
||||||
{{ appointmentDetails.user.phone || '-' }}
|
<template #label>
|
||||||
</el-descriptions-item>
|
语言偏好
|
||||||
<el-descriptions-item label="WhatsApp">
|
<el-tooltip content="用户在小程序中设置的界面语言,联系用户时可参考使用对应语言沟通" placement="top">
|
||||||
{{ appointmentDetails.user.whatsapp || '-' }}
|
<el-icon style="margin-left: 4px; cursor: help;"><QuestionFilled /></el-icon>
|
||||||
</el-descriptions-item>
|
</el-tooltip>
|
||||||
<el-descriptions-item label="微信号">
|
</template>
|
||||||
{{ appointmentDetails.user.wechatId || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="语言偏好">
|
|
||||||
{{ getLanguageLabel(appointmentDetails.user.language) }}
|
{{ getLanguageLabel(appointmentDetails.user.language) }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
@ -578,6 +579,58 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- Create Payment Order Dialog -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="paymentDialogVisible"
|
||||||
|
title="创建支付订单"
|
||||||
|
width="500px"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form :model="paymentForm" :rules="paymentRules" ref="paymentFormRef" label-width="100px">
|
||||||
|
<el-form-item label="关联订单">
|
||||||
|
<el-input :value="paymentForm.appointmentNo" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户">
|
||||||
|
<el-input :value="paymentForm.userNickname" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="服务内容" prop="serviceContent">
|
||||||
|
<el-input v-model="paymentForm.serviceContent" placeholder="服务内容描述" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="金额" prop="amount">
|
||||||
|
<el-input-number
|
||||||
|
v-model="paymentForm.amount"
|
||||||
|
:min="0.01"
|
||||||
|
:precision="2"
|
||||||
|
:step="10"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="支付时间" prop="paymentTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="paymentForm.paymentTime"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="选择支付时间"
|
||||||
|
style="width: 100%"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注">
|
||||||
|
<el-input
|
||||||
|
v-model="paymentForm.notes"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="可选,填写备注信息"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="paymentDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmCreatePaymentOrder" :loading="paymentCreating">
|
||||||
|
创建支付订单
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -633,6 +686,33 @@ const statusOptions = [
|
||||||
{ value: 'cancelled', label: '已取消' }
|
{ value: 'cancelled', label: '已取消' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Payment order dialog state
|
||||||
|
const paymentDialogVisible = ref(false)
|
||||||
|
const paymentCreating = ref(false)
|
||||||
|
const paymentFormRef = ref(null)
|
||||||
|
const paymentForm = reactive({
|
||||||
|
appointmentId: '',
|
||||||
|
appointmentNo: '',
|
||||||
|
userId: '',
|
||||||
|
userNickname: '',
|
||||||
|
serviceContent: '',
|
||||||
|
amount: 0,
|
||||||
|
paymentTime: '',
|
||||||
|
notes: ''
|
||||||
|
})
|
||||||
|
const paymentRules = {
|
||||||
|
serviceContent: [
|
||||||
|
{ required: true, message: '请输入服务内容', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
amount: [
|
||||||
|
{ required: true, message: '请输入金额', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 0.01, message: '金额必须大于0', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
paymentTime: [
|
||||||
|
{ required: true, message: '请选择支付时间', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch categories for filter
|
// Fetch categories for filter
|
||||||
async function fetchCategories() {
|
async function fetchCategories() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -808,6 +888,51 @@ async function confirmUpdateStatus() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle create payment order
|
||||||
|
function handleCreatePaymentOrder(row) {
|
||||||
|
paymentForm.appointmentId = row.id
|
||||||
|
paymentForm.appointmentNo = row.appointmentNo
|
||||||
|
paymentForm.userId = row.user?.id || row.userId
|
||||||
|
paymentForm.userNickname = `${row.user?.nickname || '-'} (UID: ${row.user?.uid || '-'})`
|
||||||
|
paymentForm.serviceContent = row.service?.titleZh || row.hotService?.name_zh || row.serviceType || ''
|
||||||
|
paymentForm.amount = parseFloat(row.amount) || 0
|
||||||
|
// 默认设置为当前时间
|
||||||
|
paymentForm.paymentTime = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
paymentForm.notes = ''
|
||||||
|
paymentDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm create payment order
|
||||||
|
async function confirmCreatePaymentOrder() {
|
||||||
|
if (!paymentFormRef.value) return
|
||||||
|
|
||||||
|
await paymentFormRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
paymentCreating.value = true
|
||||||
|
try {
|
||||||
|
const response = await api.post('/api/v1/admin/payment-orders', {
|
||||||
|
userId: paymentForm.userId,
|
||||||
|
appointmentId: paymentForm.appointmentNo,
|
||||||
|
amount: paymentForm.amount,
|
||||||
|
serviceContent: paymentForm.serviceContent,
|
||||||
|
paymentTime: paymentForm.paymentTime,
|
||||||
|
notes: paymentForm.notes
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data.success || response.data.code === 0) {
|
||||||
|
ElMessage.success('支付订单创建成功')
|
||||||
|
paymentDialogVisible.value = false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create payment order:', error)
|
||||||
|
ElMessage.error(error.response?.data?.message || '创建支付订单失败')
|
||||||
|
} finally {
|
||||||
|
paymentCreating.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
function formatDate(dateStr) {
|
function formatDate(dateStr) {
|
||||||
if (!dateStr) return '-'
|
if (!dateStr) return '-'
|
||||||
|
|
@ -1018,7 +1143,7 @@ onMounted(() => {
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phone {
|
.uid {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="titleZh" label="中文名称" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="titleZh" label="中文名称" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column prop="titleEn" label="英文名称" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="titleEn" label="英文名称" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column prop="titlePt" label="西语名称" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="titleEs" label="西语名称" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column prop="category" label="分类" width="120" align="center">
|
<el-table-column prop="category" label="分类" width="120" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag v-if="row.category" size="small">{{ row.category.nameZh || row.category.name_zh || '-' }}</el-tag>
|
<el-tag v-if="row.category" size="small">{{ row.category.nameZh || row.category.name_zh || '-' }}</el-tag>
|
||||||
|
|
@ -172,8 +172,8 @@
|
||||||
<el-form-item label="英文标题" prop="titleEn">
|
<el-form-item label="英文标题" prop="titleEn">
|
||||||
<el-input v-model="form.titleEn" placeholder="Please enter English title" />
|
<el-input v-model="form.titleEn" placeholder="Please enter English title" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="西语标题" prop="titlePt">
|
<el-form-item label="西语标题" prop="titleEs">
|
||||||
<el-input v-model="form.titlePt" placeholder="Por favor, ingrese el título en español" />
|
<el-input v-model="form.titleEs" placeholder="Por favor, ingrese el título en español" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-divider content-position="left">其他信息</el-divider>
|
<el-divider content-position="left">其他信息</el-divider>
|
||||||
|
|
@ -285,7 +285,7 @@ const defaultForm = {
|
||||||
serviceType: '',
|
serviceType: '',
|
||||||
titleZh: '',
|
titleZh: '',
|
||||||
titleEn: '',
|
titleEn: '',
|
||||||
titlePt: '',
|
titleEs: '',
|
||||||
image: '',
|
image: '',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
status: 'active'
|
status: 'active'
|
||||||
|
|
@ -299,7 +299,7 @@ const formRules = {
|
||||||
serviceType: [{ required: true, message: '请选择服务具体类型', trigger: 'change' }],
|
serviceType: [{ required: true, message: '请选择服务具体类型', trigger: 'change' }],
|
||||||
titleZh: [{ required: true, message: '请输入中文标题', trigger: 'blur' }],
|
titleZh: [{ required: true, message: '请输入中文标题', trigger: 'blur' }],
|
||||||
titleEn: [{ required: true, message: '请输入英文标题', trigger: 'blur' }],
|
titleEn: [{ required: true, message: '请输入英文标题', trigger: 'blur' }],
|
||||||
titlePt: [{ required: true, message: '请输入西语标题', trigger: 'blur' }]
|
titleEs: [{ required: true, message: '请输入西语标题', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取服务类型标签
|
// 获取服务类型标签
|
||||||
|
|
@ -338,11 +338,12 @@ async function fetchServices() {
|
||||||
const response = await api.get('/api/v1/admin/services', { params })
|
const response = await api.get('/api/v1/admin/services', { params })
|
||||||
// Support both old format (success: true) and new format (code: 0)
|
// Support both old format (success: true) and new format (code: 0)
|
||||||
if (response.data.success === true || response.data.code === 0) {
|
if (response.data.success === true || response.data.code === 0) {
|
||||||
services.value = response.data.data
|
services.value = response.data.data || []
|
||||||
// Handle pagination from both formats
|
// Handle pagination from response
|
||||||
const paginationData = response.data.pagination || {}
|
if (response.data.pagination) {
|
||||||
pagination.total = paginationData.total || 0
|
pagination.total = response.data.pagination.total || 0
|
||||||
pagination.totalPages = paginationData.totalPages || 0
|
pagination.totalPages = response.data.pagination.totalPages || 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch services:', error)
|
console.error('Failed to fetch services:', error)
|
||||||
|
|
@ -408,7 +409,7 @@ function handleEdit(row) {
|
||||||
serviceType: row.serviceType || '',
|
serviceType: row.serviceType || '',
|
||||||
titleZh: row.titleZh,
|
titleZh: row.titleZh,
|
||||||
titleEn: row.titleEn,
|
titleEn: row.titleEn,
|
||||||
titlePt: row.titlePt,
|
titleEs: row.titleEs,
|
||||||
image: row.image || '',
|
image: row.image || '',
|
||||||
sortOrder: row.sortOrder || 0,
|
sortOrder: row.sortOrder || 0,
|
||||||
status: row.status
|
status: row.status
|
||||||
|
|
|
||||||
|
|
@ -63,21 +63,6 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="nickname" label="昵称" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="nickname" label="昵称" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="realName" label="真实姓名" min-width="100" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.realName || '-' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="phone" label="手机号" min-width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.phone || '-' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="wechatId" label="微信号" min-width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.wechatId || '-' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="language" label="语言" width="100" align="center">
|
<el-table-column prop="language" label="语言" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="getLanguageTagType(row.language)" size="small">
|
<el-tag :type="getLanguageTagType(row.language)" size="small">
|
||||||
|
|
@ -85,11 +70,6 @@
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="balance" label="余额" width="100" align="right" sortable="custom">
|
|
||||||
<template #default="{ row }">
|
|
||||||
¥{{ parseFloat(row.balance || 0).toFixed(2) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.status === 'active' ? 'success' : 'danger'" size="small">
|
<el-tag :type="row.status === 'active' ? 'success' : 'danger'" size="small">
|
||||||
|
|
@ -151,10 +131,6 @@
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="昵称">{{ userDetails.user.nickname || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="昵称">{{ userDetails.user.nickname || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="UID">{{ userDetails.user.uid || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="UID">{{ userDetails.user.uid || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="真实姓名">{{ userDetails.user.realName || '-' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="手机号">{{ userDetails.user.phone || '-' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="WhatsApp">{{ userDetails.user.whatsapp || '-' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="微信号">{{ userDetails.user.wechatId || '-' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="语言偏好">
|
<el-descriptions-item label="语言偏好">
|
||||||
<el-tag :type="getLanguageTagType(userDetails.user.language)" size="small">
|
<el-tag :type="getLanguageTagType(userDetails.user.language)" size="small">
|
||||||
{{ getLanguageLabel(userDetails.user.language) }}
|
{{ getLanguageLabel(userDetails.user.language) }}
|
||||||
|
|
@ -165,10 +141,6 @@
|
||||||
{{ userDetails.user.status === 'active' ? '正常' : '已禁用' }}
|
{{ userDetails.user.status === 'active' ? '正常' : '已禁用' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="账户余额">
|
|
||||||
<span class="balance">¥{{ parseFloat(userDetails.user.balance || 0).toFixed(2) }}</span>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="邀请码">{{ userDetails.user.invitationCode || '-' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="邀请人">
|
<el-descriptions-item label="邀请人">
|
||||||
{{ userDetails.inviter ? (userDetails.inviter.nickname || userDetails.inviter.realName) : '-' }}
|
{{ userDetails.inviter ? (userDetails.inviter.nickname || userDetails.inviter.realName) : '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,18 @@ const responseFormatter = (req, res, next) => {
|
||||||
|
|
||||||
// Transform old format to new format
|
// Transform old format to new format
|
||||||
if (body && typeof body === 'object') {
|
if (body && typeof body === 'object') {
|
||||||
// Success response: { success: true, data: {...} }
|
// Success response: { success: true, data: {...}, pagination: {...} }
|
||||||
if (body.success === true) {
|
if (body.success === true) {
|
||||||
return originalJson({
|
const response = {
|
||||||
code: 0,
|
code: 0,
|
||||||
message: body.message || 'Success',
|
message: body.message || 'Success',
|
||||||
data: body.data || null,
|
data: body.data || null,
|
||||||
});
|
};
|
||||||
|
// Preserve pagination if present
|
||||||
|
if (body.pagination) {
|
||||||
|
response.pagination = body.pagination;
|
||||||
|
}
|
||||||
|
return originalJson(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error response: { success: false, error: {...} }
|
// Error response: { success: false, error: {...} }
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ const getAppointmentList = async (options = {}) => {
|
||||||
{
|
{
|
||||||
model: User,
|
model: User,
|
||||||
as: 'user',
|
as: 'user',
|
||||||
attributes: ['id', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId'],
|
attributes: ['id', 'uid', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -158,7 +158,7 @@ const getAppointmentDetails = async (appointmentId) => {
|
||||||
{
|
{
|
||||||
model: User,
|
model: User,
|
||||||
as: 'user',
|
as: 'user',
|
||||||
attributes: ['id', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance'],
|
attributes: ['id', 'uid', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user