管理后台修改.

This commit is contained in:
18631081161 2025-12-22 18:11:31 +08:00
parent 3b5dd2bad6
commit 4b27db42d3
5 changed files with 163 additions and 60 deletions

View File

@ -70,8 +70,8 @@
<el-table-column label="用户信息" min-width="150">
<template #default="{ row }">
<div class="user-info">
<span class="nickname">{{ row.user?.nickname || row.user?.realName || '-' }}</span>
<span class="phone">{{ row.user?.phone || '-' }}</span>
<span class="nickname">{{ row.user?.nickname || '-' }}</span>
<span class="uid">UID: {{ row.user?.uid || '-' }}</span>
</div>
</template>
</el-table-column>
@ -137,7 +137,7 @@
{{ formatDate(row.createdAt) }}
</template>
</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 }">
<el-button type="primary" link size="small" @click="handleViewDetails(row)">
<el-icon><View /></el-icon>
@ -147,6 +147,10 @@
<el-icon><Edit /></el-icon>
状态
</el-button>
<el-button type="success" link size="small" @click="handleCreatePaymentOrder(row)">
<el-icon><Money /></el-icon>
收款
</el-button>
</template>
</el-table-column>
</el-table>
@ -512,19 +516,16 @@
<el-descriptions-item label="用户昵称">
{{ appointmentDetails.user.nickname || '-' }}
</el-descriptions-item>
<el-descriptions-item label="真实姓名">
{{ appointmentDetails.user.realName || '-' }}
<el-descriptions-item label="UID">
{{ appointmentDetails.user.uid || '-' }}
</el-descriptions-item>
<el-descriptions-item label="手机号">
{{ appointmentDetails.user.phone || '-' }}
</el-descriptions-item>
<el-descriptions-item label="WhatsApp">
{{ appointmentDetails.user.whatsapp || '-' }}
</el-descriptions-item>
<el-descriptions-item label="微信号">
{{ appointmentDetails.user.wechatId || '-' }}
</el-descriptions-item>
<el-descriptions-item label="语言偏好">
<el-descriptions-item>
<template #label>
语言偏好
<el-tooltip content="用户在小程序中设置的界面语言,联系用户时可参考使用对应语言沟通" placement="top">
<el-icon style="margin-left: 4px; cursor: help;"><QuestionFilled /></el-icon>
</el-tooltip>
</template>
{{ getLanguageLabel(appointmentDetails.user.language) }}
</el-descriptions-item>
</el-descriptions>
@ -578,6 +579,58 @@
</el-button>
</template>
</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>
</template>
@ -633,6 +686,33 @@ const statusOptions = [
{ 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
async function fetchCategories() {
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
function formatDate(dateStr) {
if (!dateStr) return '-'
@ -1018,7 +1143,7 @@ onMounted(() => {
color: #303133;
}
.phone {
.uid {
font-size: 12px;
color: #909399;
}

View File

@ -72,7 +72,7 @@
</el-table-column>
<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="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">
<template #default="{ row }">
<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-input v-model="form.titleEn" placeholder="Please enter English title" />
</el-form-item>
<el-form-item label="西语标题" prop="titlePt">
<el-input v-model="form.titlePt" placeholder="Por favor, ingrese el título en español" />
<el-form-item label="西语标题" prop="titleEs">
<el-input v-model="form.titleEs" placeholder="Por favor, ingrese el título en español" />
</el-form-item>
<el-divider content-position="left">其他信息</el-divider>
@ -285,7 +285,7 @@ const defaultForm = {
serviceType: '',
titleZh: '',
titleEn: '',
titlePt: '',
titleEs: '',
image: '',
sortOrder: 0,
status: 'active'
@ -299,7 +299,7 @@ const formRules = {
serviceType: [{ required: true, message: '请选择服务具体类型', trigger: 'change' }],
titleZh: [{ 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 })
// Support both old format (success: true) and new format (code: 0)
if (response.data.success === true || response.data.code === 0) {
services.value = response.data.data
// Handle pagination from both formats
const paginationData = response.data.pagination || {}
pagination.total = paginationData.total || 0
pagination.totalPages = paginationData.totalPages || 0
services.value = response.data.data || []
// Handle pagination from response
if (response.data.pagination) {
pagination.total = response.data.pagination.total || 0
pagination.totalPages = response.data.pagination.totalPages || 0
}
}
} catch (error) {
console.error('Failed to fetch services:', error)
@ -408,7 +409,7 @@ function handleEdit(row) {
serviceType: row.serviceType || '',
titleZh: row.titleZh,
titleEn: row.titleEn,
titlePt: row.titlePt,
titleEs: row.titleEs,
image: row.image || '',
sortOrder: row.sortOrder || 0,
status: row.status

View File

@ -63,21 +63,6 @@
</template>
</el-table-column>
<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">
<template #default="{ row }">
<el-tag :type="getLanguageTagType(row.language)" size="small">
@ -85,11 +70,6 @@
</el-tag>
</template>
</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">
<template #default="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'danger'" size="small">
@ -151,10 +131,6 @@
</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="真实姓名">{{ 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-tag :type="getLanguageTagType(userDetails.user.language)" size="small">
{{ getLanguageLabel(userDetails.user.language) }}
@ -165,10 +141,6 @@
{{ userDetails.user.status === 'active' ? '正常' : '已禁用' }}
</el-tag>
</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="邀请人">
{{ userDetails.inviter ? (userDetails.inviter.nickname || userDetails.inviter.realName) : '-' }}
</el-descriptions-item>

View File

@ -17,13 +17,18 @@ const responseFormatter = (req, res, next) => {
// Transform old format to new format
if (body && typeof body === 'object') {
// Success response: { success: true, data: {...} }
// Success response: { success: true, data: {...}, pagination: {...} }
if (body.success === true) {
return originalJson({
const response = {
code: 0,
message: body.message || 'Success',
data: body.data || null,
});
};
// Preserve pagination if present
if (body.pagination) {
response.pagination = body.pagination;
}
return originalJson(response);
}
// Error response: { success: false, error: {...} }

View File

@ -81,7 +81,7 @@ const getAppointmentList = async (options = {}) => {
{
model: 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,
as: 'user',
attributes: ['id', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance'],
attributes: ['id', 'uid', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance'],
},
],
});