由于本次代码变更内容为空,无法生成有效的提交信息。请提供具体的代码变更内容以便生成合适的提交信息。
This commit is contained in:
@@ -226,12 +226,106 @@
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<!-- 创建/编辑动物模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="modalTitle"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
width="600px"
|
||||
>
|
||||
<a-form
|
||||
ref="animalFormRef"
|
||||
:model="currentAnimal"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="动物名称" name="name">
|
||||
<a-input v-model:value="currentAnimal.name" placeholder="请输入动物名称" />
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型" name="type">
|
||||
<a-select v-model:value="currentAnimal.type" placeholder="请选择类型">
|
||||
<a-select-option value="alpaca">羊驼</a-select-option>
|
||||
<a-select-option value="dog">狗狗</a-select-option>
|
||||
<a-select-option value="cat">猫咪</a-select-option>
|
||||
<a-select-option value="rabbit">兔子</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="品种" name="breed">
|
||||
<a-input v-model:value="currentAnimal.breed" placeholder="请输入品种" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="年龄" name="age">
|
||||
<a-input-number
|
||||
v-model:value="currentAnimal.age"
|
||||
:min="0"
|
||||
:max="100"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-select v-model:value="currentAnimal.gender" placeholder="请选择性别">
|
||||
<a-select-option value="male">雄性</a-select-option>
|
||||
<a-select-option value="female">雌性</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="价格(元)" name="price">
|
||||
<a-input-number
|
||||
v-model:value="currentAnimal.price"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="currentAnimal.status" placeholder="请选择状态">
|
||||
<a-select-option value="available">可认领</a-select-option>
|
||||
<a-select-option value="claimed">已认领</a-select-option>
|
||||
<a-select-option value="reserved">预留中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="图片URL" name="image_url">
|
||||
<a-input v-model:value="currentAnimal.image_url" placeholder="请输入图片URL" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea
|
||||
v-model:value="currentAnimal.description"
|
||||
placeholder="请输入动物描述"
|
||||
:rows="4"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
@@ -243,8 +337,8 @@ import {
|
||||
CheckOutlined,
|
||||
CloseOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { getAnimals, deleteAnimal, getAnimalClaims, approveAnimalClaim, rejectAnimalClaim } from '@/api/animal'
|
||||
import type { Animal, AnimalClaim } from '@/api/animal'
|
||||
import { getAnimals, deleteAnimal, getAnimalClaims, approveAnimalClaim, rejectAnimalClaim, createAnimal, updateAnimal, getAnimal } from '@/api/animal'
|
||||
import type { Animal, AnimalClaim, AnimalCreateData, AnimalUpdateData } from '@/api/animal'
|
||||
|
||||
interface SearchForm {
|
||||
keyword: string
|
||||
@@ -403,7 +497,7 @@ const claimColumns = [
|
||||
}
|
||||
]
|
||||
|
||||
const fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjYwIiBoZWlnaHQ9IjYwIiBmaWxsPSIjRkZGIi8+CjxwYXRoIGQ9Ik0zMCAxNUMzMS42NTY5IDE1IDMzIDE2LjM0MzEgMzMgMThDMzMgMTkuNjU2OSAzMS42NTY5IDIxIDMwIDIxQzI4LjM0MzEgMjEgMjcgMTkuNjU2OSAyNyAxOEMyNyAxNi4zNDMxIDI4LjM0MzEgMTUgMzAgMTVaIiBmaWxsPSIjQ0NDQ0NDIi8+CjxwYXRoIGQ9Ik0yMi41IDI1QzIyLjUgMjUuODI4NCAyMS44Mjg0IDI2LjUgMjEgMjYuNUgxOUMxOC4xNzE2IDI2LjUgMTcuNSAyNS44Mjg0IDE3LjUgMjVDMTcuNSAyNC4xNzE2IDE4LjE3MTYgMjMuNSAxOSAyMy41SDIxQzIxLjgyODQgMjMuNSAyMi41IDI0LjE3MTYgMjIuNSAyNVoiIGZpbGw9IiNDQ0NDQ0MiLz4KPHBhdGggZD0iTTQyLjUgMjVDNDIuNSAyNS44Mjg0IDQxLjgyODQgMjYuNSA0MSAyNi41SDM5QzM4LjE3MTYgMjYuNSAzNy41IDI1LjgyODQgMzcuNSAyNUMzNy41IDI0LjE3MTYgMzguMTcxNiAyMy41IDM5IDIzLjVMNDEgMjMuNUM0MS44Mjg0IDIzLjUgNDIuNSAyNC4xNzE2IDQyLjUgMjVaIiBmaWxsPSIjQ0NDQ0NDIi8+Cjwvc3ZnPgo='
|
||||
const fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjYwIiBoZWlnaHQ9IjYwIiBmaWxsPSIjRkZGIi8+CjxwYXRoIGQ9Ik0zMCAxNUMzMS42NTY5IDE1IDMzIDE2LjM0MzEgMzMgMThDMzMgMTkuNjU2OSAzMS42NTY5IDIxIDMwIDIxQzI4LjM0MzEgMjEgMjcgMTkuNjU2OSAyNyAxOEMyNyAxNi4zNDMxIDI4LjM0MzEgMTUgMzAgMTVaIiBmaWxsPSIjQ0NDQ0NDIi8+CjxwYXRoIGQ9Ik0yMi41IDI1QzIyLjUgMjUuODI4NCAyMS44Mjg0IDI2LjUgMjEgMjYuNUgxOUMxOC4xNzE2IDI2LjUgMTcuNSAyNS44Mjg0IDE3LjUgMjVDMTcuNSAyNC4xNzE2IDE4LjE3MTYgMjMuNSAxOSAyMy55SDIxQzIxLjgyODQgMjMuNSAyMi41IDI0LjE3MTYgMjIuNSAyNVoiIGZpbGw9IiNDQ0NDQ0MiLz4KPHBhdGggZD0iTTQyLjUgMjVDNDIuNSAyNS44Mjg0IDQxLjgyODQgMjYuNSA0MSAyNi41SDM5QzM4LjE3MTYgMjYuNSAzNy41IDI1LjgyODQgMzcuNSAyNUMzNy41IDI0LjE3MTYgMzguMTcxNiAyMy41IDM5IDIzLjVMNDEgMjMuNUM0MS44Mjg0IDIzLjUgNDIuNSAyNC4xNzE2IDQyLjUgMjVaIiBmaWxsPSIjQ0NDQ0NDIi8+Cjwvc3ZnPgo='
|
||||
|
||||
// 类型映射
|
||||
const getTypeColor = (type: string) => {
|
||||
@@ -465,6 +559,41 @@ const getClaimStatusText = (status: string) => {
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 添加模态框相关状态
|
||||
const modalVisible = ref(false)
|
||||
const modalLoading = ref(false)
|
||||
const modalTitle = ref('新增动物')
|
||||
const isEditing = ref(false)
|
||||
const animalFormRef = ref<FormInstance>()
|
||||
|
||||
// 当前动物数据
|
||||
const currentAnimal = ref<Partial<Animal>>({})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入动物名称' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择类型' }
|
||||
],
|
||||
breed: [
|
||||
{ required: true, message: '请输入品种' }
|
||||
],
|
||||
age: [
|
||||
{ required: true, message: '请输入年龄' }
|
||||
],
|
||||
gender: [
|
||||
{ required: true, message: '请选择性别' }
|
||||
],
|
||||
price: [
|
||||
{ required: true, message: '请输入价格' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态' }
|
||||
]
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadAnimals()
|
||||
@@ -567,8 +696,21 @@ const handleViewAnimal = (record: Animal) => {
|
||||
message.info(`查看动物: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleEditAnimal = (record: Animal) => {
|
||||
message.info(`编辑动物: ${record.name}`)
|
||||
const handleEditAnimal = async (record: Animal) => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
modalTitle.value = '编辑动物'
|
||||
isEditing.value = true
|
||||
|
||||
// 获取动物详情
|
||||
const response = await getAnimal(record.id)
|
||||
currentAnimal.value = response.data
|
||||
modalVisible.value = true
|
||||
} catch (error) {
|
||||
message.error('获取动物详情失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteAnimal = async (record: Animal) => {
|
||||
@@ -626,8 +768,64 @@ const handleViewClaim = (record: AnimalClaim) => {
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
message.info('新增动物功能开发中')
|
||||
modalTitle.value = '新增动物'
|
||||
isEditing.value = false
|
||||
currentAnimal.value = {
|
||||
age: 1,
|
||||
price: 0,
|
||||
status: 'available'
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleModalOk = () => {
|
||||
animalFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
if (isEditing.value) {
|
||||
handleUpdateAnimal()
|
||||
} else {
|
||||
handleCreateAnimal()
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('表单验证失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
animalFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleCreateAnimal = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
await createAnimal(currentAnimal.value as AnimalCreateData)
|
||||
message.success('创建动物成功')
|
||||
modalVisible.value = false
|
||||
loadAnimals()
|
||||
} catch (error) {
|
||||
message.error('创建动物失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateAnimal = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
await updateAnimal(currentAnimal.value.id!, currentAnimal.value as AnimalUpdateData)
|
||||
message.success('更新动物成功')
|
||||
modalVisible.value = false
|
||||
loadAnimals()
|
||||
} catch (error) {
|
||||
message.error('更新动物失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -104,6 +104,11 @@
|
||||
查看
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="showEditModal(record)">
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-button size="small" type="primary" @click="handleApprove(record)">
|
||||
<CheckOutlined />
|
||||
@@ -133,12 +138,85 @@
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 创建/编辑商家模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="modalTitle"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
width="600px"
|
||||
>
|
||||
<a-form
|
||||
ref="merchantFormRef"
|
||||
:model="currentMerchant"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="商家名称" name="business_name">
|
||||
<a-input v-model:value="currentMerchant.business_name" placeholder="请输入商家名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="商家类型" name="merchant_type">
|
||||
<a-select v-model:value="currentMerchant.merchant_type" placeholder="请选择商家类型">
|
||||
<a-select-option value="flower_shop">花店</a-select-option>
|
||||
<a-select-option value="activity_organizer">活动组织</a-select-option>
|
||||
<a-select-option value="farm_owner">农场主</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系人" name="contact_person">
|
||||
<a-input v-model:value="currentMerchant.contact_person" placeholder="请输入联系人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" name="contact_phone">
|
||||
<a-input v-model:value="currentMerchant.contact_phone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系邮箱" name="contact_email">
|
||||
<a-input v-model:value="currentMerchant.contact_email" placeholder="请输入联系邮箱" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="currentMerchant.status" placeholder="请选择状态">
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="disabled">已禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="详细地址" name="address">
|
||||
<a-input v-model:value="currentMerchant.address" placeholder="请输入详细地址" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="currentMerchant.remark" placeholder="请输入备注" :rows="3" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ShopOutlined,
|
||||
@@ -148,10 +226,11 @@ import {
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
StopOutlined,
|
||||
PlayCircleOutlined
|
||||
PlayCircleOutlined,
|
||||
EditOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { getMerchants, getMerchant, approveMerchant, rejectMerchant, disableMerchant, enableMerchant } from '@/api/merchant'
|
||||
import type { Merchant } from '@/api/merchant'
|
||||
import { getMerchants, getMerchant, approveMerchant, rejectMerchant, disableMerchant, enableMerchant, createMerchant, updateMerchant } from '@/api/merchant'
|
||||
import type { Merchant, MerchantCreateData, MerchantUpdateData } from '@/api/merchant'
|
||||
|
||||
interface SearchForm {
|
||||
keyword: string
|
||||
@@ -262,6 +341,44 @@ const getStatusText = (status: string) => {
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 添加模态框相关状态
|
||||
const modalVisible = ref(false)
|
||||
const modalLoading = ref(false)
|
||||
const modalTitle = ref('新增商家')
|
||||
const isEditing = ref(false)
|
||||
const merchantFormRef = ref<FormInstance>()
|
||||
|
||||
// 当前商家数据
|
||||
const currentMerchant = ref<Partial<Merchant>>({})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
business_name: [
|
||||
{ required: true, message: '请输入商家名称' },
|
||||
{ min: 2, max: 50, message: '商家名称长度为2-50个字符' }
|
||||
],
|
||||
merchant_type: [
|
||||
{ required: true, message: '请选择商家类型' }
|
||||
],
|
||||
contact_person: [
|
||||
{ required: true, message: '请输入联系人' },
|
||||
{ min: 2, max: 20, message: '联系人长度为2-20个字符' }
|
||||
],
|
||||
contact_phone: [
|
||||
{ required: true, message: '请输入联系电话' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
|
||||
],
|
||||
contact_email: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态' }
|
||||
],
|
||||
address: [
|
||||
{ min: 5, max: 100, message: '地址长度为5-100个字符' }
|
||||
]
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadMerchants()
|
||||
@@ -408,8 +525,145 @@ const handleEnable = async (record: Merchant) => {
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
message.info('新增商家功能开发中')
|
||||
modalTitle.value = '新增商家'
|
||||
isEditing.value = false
|
||||
currentMerchant.value = {
|
||||
status: 'pending'
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const showEditModal = async (record: Merchant) => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
modalTitle.value = '编辑商家'
|
||||
isEditing.value = true
|
||||
|
||||
// 获取商家详情
|
||||
const response = await getMerchant(record.id)
|
||||
currentMerchant.value = response.data
|
||||
modalVisible.value = true
|
||||
} catch (error) {
|
||||
message.error('获取商家详情失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalOk = () => {
|
||||
merchantFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
if (isEditing.value) {
|
||||
handleUpdateMerchant()
|
||||
} else {
|
||||
handleCreateMerchant()
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('表单验证失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
merchantFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleCreateMerchant = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
|
||||
// 前端数据验证
|
||||
if (!currentMerchant.value.business_name) {
|
||||
message.error('商家名称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentMerchant.value.merchant_type) {
|
||||
message.error('请选择商家类型')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentMerchant.value.contact_person) {
|
||||
message.error('联系人不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentMerchant.value.contact_phone) {
|
||||
message.error('联系电话不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(currentMerchant.value.contact_phone)) {
|
||||
message.error('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentMerchant.value.contact_email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(currentMerchant.value.contact_email)) {
|
||||
message.error('请输入正确的邮箱地址')
|
||||
return
|
||||
}
|
||||
|
||||
await createMerchant(currentMerchant.value as MerchantCreateData)
|
||||
message.success('创建商家成功')
|
||||
modalVisible.value = false
|
||||
loadMerchants()
|
||||
} catch (error) {
|
||||
console.error('创建商家失败:', error)
|
||||
message.error('创建商家失败: ' + (error as Error).message)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateMerchant = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
|
||||
// 前端数据验证
|
||||
if (!currentMerchant.value.business_name) {
|
||||
message.error('商家名称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentMerchant.value.merchant_type) {
|
||||
message.error('请选择商家类型')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentMerchant.value.contact_person) {
|
||||
message.error('联系人不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentMerchant.value.contact_phone) {
|
||||
message.error('联系电话不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(currentMerchant.value.contact_phone)) {
|
||||
message.error('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentMerchant.value.contact_email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(currentMerchant.value.contact_email)) {
|
||||
message.error('请输入正确的邮箱地址')
|
||||
return
|
||||
}
|
||||
|
||||
await updateMerchant(currentMerchant.value.id!, currentMerchant.value as MerchantUpdateData)
|
||||
message.success('更新商家成功')
|
||||
modalVisible.value = false
|
||||
loadMerchants()
|
||||
} catch (error) {
|
||||
console.error('更新商家失败:', error)
|
||||
message.error('更新商家失败: ' + (error as Error).message)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -204,12 +204,40 @@
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑订单备注模态框 -->
|
||||
<a-modal
|
||||
v-model:open="remarkModalVisible"
|
||||
title="编辑订单备注"
|
||||
:confirm-loading="remarkModalLoading"
|
||||
@ok="handleRemarkModalOk"
|
||||
@cancel="handleRemarkModalCancel"
|
||||
width="500px"
|
||||
>
|
||||
<a-form
|
||||
ref="remarkFormRef"
|
||||
:model="currentOrder"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="订单号">
|
||||
<a-input v-model:value="currentOrder.order_no" disabled />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="currentOrder.remark"
|
||||
placeholder="请输入订单备注"
|
||||
:rows="4"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ShoppingCartOutlined,
|
||||
@@ -224,7 +252,8 @@ import {
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
RedoOutlined,
|
||||
DownOutlined
|
||||
DownOutlined,
|
||||
EditOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import {
|
||||
getOrders,
|
||||
@@ -234,9 +263,10 @@ import {
|
||||
shipOrder,
|
||||
completeOrder,
|
||||
cancelOrder,
|
||||
refundOrder
|
||||
refundOrder,
|
||||
updateOrder
|
||||
} from '@/api/order'
|
||||
import type { Order, OrderStatistics } from '@/api/order'
|
||||
import type { Order, OrderStatistics, OrderUpdateData } from '@/api/order'
|
||||
|
||||
interface SearchForm {
|
||||
order_no: string
|
||||
@@ -355,6 +385,14 @@ const getPaymentMethodText = (method: string) => {
|
||||
return texts[method as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 添加备注模态框相关状态
|
||||
const remarkModalVisible = ref(false)
|
||||
const remarkModalLoading = ref(false)
|
||||
const remarkFormRef = ref<FormInstance>()
|
||||
|
||||
// 当前订单数据
|
||||
const currentOrder = ref<Partial<Order>>({})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadOrders()
|
||||
@@ -439,8 +477,19 @@ const handleView = async (record: Order) => {
|
||||
h('a-descriptions-item', { label: '下单时间' }, response.data.created_at),
|
||||
h('a-descriptions-item', { label: '支付时间' }, response.data.paid_at || '-'),
|
||||
h('a-descriptions-item', { label: '发货时间' }, response.data.shipped_at || '-'),
|
||||
h('a-descriptions-item', { label: '完成时间' }, response.data.completed_at || '-')
|
||||
h('a-descriptions-item', { label: '完成时间' }, response.data.completed_at || '-'),
|
||||
h('a-descriptions-item', { label: '备注' }, response.data.remark || '-')
|
||||
])
|
||||
]),
|
||||
okText: '关闭',
|
||||
footer: (_, { OkBtn }) => h('div', { style: 'text-align: right;' }, [
|
||||
h('a-button', {
|
||||
onClick: () => {
|
||||
Modal.destroyAll()
|
||||
handleEditRemark(response.data)
|
||||
}
|
||||
}, '编辑备注'),
|
||||
h(OkBtn)
|
||||
])
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -448,6 +497,46 @@ const handleView = async (record: Order) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加编辑备注的方法
|
||||
const handleEditRemark = async (record: Order) => {
|
||||
try {
|
||||
const response = await getOrder(record.id)
|
||||
currentOrder.value = response.data
|
||||
remarkModalVisible.value = true
|
||||
} catch (error) {
|
||||
message.error('获取订单详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemarkModalOk = () => {
|
||||
remarkFormRef.value
|
||||
?.validate()
|
||||
.then(async () => {
|
||||
try {
|
||||
remarkModalLoading.value = true
|
||||
const updateData: OrderUpdateData = {
|
||||
remark: currentOrder.value.remark
|
||||
}
|
||||
await updateOrder(currentOrder.value.id!, updateData)
|
||||
message.success('更新订单备注成功')
|
||||
remarkModalVisible.value = false
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('更新订单备注失败')
|
||||
} finally {
|
||||
remarkModalLoading.value = false
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('表单验证失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleRemarkModalCancel = () => {
|
||||
remarkModalVisible.value = false
|
||||
remarkFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handlePay = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认支付',
|
||||
|
||||
@@ -187,12 +187,95 @@
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<!-- 创建/编辑推广活动模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="modalTitle"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
width="600px"
|
||||
>
|
||||
<a-form
|
||||
ref="promotionFormRef"
|
||||
:model="currentActivity"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="活动名称" name="name">
|
||||
<a-input v-model:value="currentActivity.name" placeholder="请输入活动名称" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="活动描述" name="description">
|
||||
<a-textarea
|
||||
v-model:value="currentActivity.description"
|
||||
placeholder="请输入活动描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="奖励类型" name="reward_type">
|
||||
<a-select v-model:value="currentActivity.reward_type" placeholder="请选择奖励类型">
|
||||
<a-select-option value="cash">现金</a-select-option>
|
||||
<a-select-option value="points">积分</a-select-option>
|
||||
<a-select-option value="coupon">优惠券</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="奖励金额" name="reward_amount">
|
||||
<a-input-number
|
||||
v-model:value="currentActivity.reward_amount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="最大参与人数" name="max_participants">
|
||||
<a-input-number
|
||||
v-model:value="currentActivity.max_participants"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="currentActivity.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">进行中</a-select-option>
|
||||
<a-select-option value="upcoming">未开始</a-select-option>
|
||||
<a-select-option value="ended">已结束</a-select-option>
|
||||
<a-select-option value="paused">已暂停</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="活动时间" required>
|
||||
<a-range-picker
|
||||
v-model:value="activityTimeRange"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
show-time
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
@@ -212,7 +295,10 @@ import {
|
||||
pausePromotionActivity,
|
||||
resumePromotionActivity,
|
||||
getRewardRecords,
|
||||
issueReward
|
||||
issueReward,
|
||||
createPromotionActivity,
|
||||
updatePromotionActivity,
|
||||
getPromotionActivity
|
||||
} from '@/api/promotion'
|
||||
import type { PromotionActivity, RewardRecord } from '@/api/promotion'
|
||||
|
||||
@@ -439,8 +525,28 @@ const handleViewActivity = (record: PromotionActivity) => {
|
||||
message.info(`查看活动: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleEditActivity = (record: PromotionActivity) => {
|
||||
message.info(`编辑活动: ${record.name}`)
|
||||
const handleEditActivity = async (record: PromotionActivity) => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
modalTitle.value = '编辑活动'
|
||||
isEditing.value = true
|
||||
|
||||
// 获取活动详情
|
||||
const response = await getPromotionActivity(record.id)
|
||||
currentActivity.value = response.data
|
||||
|
||||
// 设置时间范围
|
||||
activityTimeRange.value = [
|
||||
new Date(response.data.start_time),
|
||||
new Date(response.data.end_time)
|
||||
]
|
||||
|
||||
modalVisible.value = true
|
||||
} catch (error) {
|
||||
message.error('获取活动详情失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handlePauseActivity = async (record: PromotionActivity) => {
|
||||
@@ -514,8 +620,75 @@ const handleViewReward = (record: RewardRecord) => {
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
message.info('新建活动功能开发中')
|
||||
modalTitle.value = '新增活动'
|
||||
isEditing.value = false
|
||||
currentActivity.value = {
|
||||
reward_amount: 0,
|
||||
max_participants: 100,
|
||||
status: 'upcoming'
|
||||
}
|
||||
activityTimeRange.value = []
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleModalOk = () => {
|
||||
promotionFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
// 检查时间范围是否已设置
|
||||
if (!activityTimeRange.value || activityTimeRange.value.length !== 2) {
|
||||
message.error('请选择活动时间')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置时间范围到当前活动数据
|
||||
currentActivity.value.start_time = activityTimeRange.value[0].toISOString()
|
||||
currentActivity.value.end_time = activityTimeRange.value[1].toISOString()
|
||||
|
||||
if (isEditing.value) {
|
||||
handleUpdateActivity()
|
||||
} else {
|
||||
handleCreateActivity()
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('表单验证失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
promotionFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleCreateActivity = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
await createPromotionActivity(currentActivity.value)
|
||||
message.success('创建活动成功')
|
||||
modalVisible.value = false
|
||||
loadActivities()
|
||||
} catch (error) {
|
||||
message.error('创建活动失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateActivity = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
await updatePromotionActivity(currentActivity.value.id!, currentActivity.value)
|
||||
message.success('更新活动成功')
|
||||
modalVisible.value = false
|
||||
loadActivities()
|
||||
} catch (error) {
|
||||
message.error('更新活动失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<a-page-header title="系统管理" sub-title="管理系统设置和配置">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="exportConfig">
|
||||
<ExportOutlined />
|
||||
导出配置
|
||||
</a-button>
|
||||
<a-button @click="handleRefresh">
|
||||
<ReloadOutlined />
|
||||
刷新
|
||||
@@ -91,6 +95,41 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-card title="系统监控" size="small">
|
||||
<div style="height: 200px;">
|
||||
<div style="display: flex; flex-direction: column; height: 100%; justify-content: space-around;">
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>CPU使用率</span>
|
||||
<span>{{ mockChartData.cpu }}%</span>
|
||||
</div>
|
||||
<a-progress :percent="mockChartData.cpu" size="small" />
|
||||
</div>
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>内存使用率</span>
|
||||
<span>{{ mockChartData.memory }}%</span>
|
||||
</div>
|
||||
<a-progress :percent="mockChartData.memory" size="small" status="active" />
|
||||
</div>
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>网络流量</span>
|
||||
<span>{{ mockChartData.network }} KB/s</span>
|
||||
</div>
|
||||
<a-progress :percent="Math.min(mockChartData.network / 10, 100)" size="small" status="success" />
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 12px; color: #666;">
|
||||
更新时间: {{ mockChartData.time }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="margin-top: 16px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="系统日志" size="small">
|
||||
<a-timeline>
|
||||
@@ -166,13 +205,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
DatabaseOutlined,
|
||||
CloudServerOutlined,
|
||||
MessageOutlined
|
||||
MessageOutlined,
|
||||
ExportOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import {
|
||||
getServices,
|
||||
@@ -217,12 +257,60 @@ const systemSettings = reactive<SystemSettings>({
|
||||
enableSwagger: true
|
||||
})
|
||||
|
||||
// 添加监控数据状态
|
||||
const mockChartData = reactive({
|
||||
time: new Date().toLocaleTimeString(),
|
||||
cpu: 0,
|
||||
memory: 0,
|
||||
network: 0
|
||||
})
|
||||
|
||||
let chartInterval: number | null = null
|
||||
|
||||
// 模拟系统监控数据
|
||||
const generateMockChartData = () => {
|
||||
return {
|
||||
time: new Date().toLocaleTimeString(),
|
||||
cpu: Math.floor(Math.random() * 100),
|
||||
memory: Math.floor(Math.random() * 100),
|
||||
network: Math.floor(Math.random() * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新监控图表
|
||||
const updateChart = () => {
|
||||
const data = generateMockChartData()
|
||||
mockChartData.time = data.time
|
||||
mockChartData.cpu = data.cpu
|
||||
mockChartData.memory = data.memory
|
||||
mockChartData.network = data.network
|
||||
}
|
||||
|
||||
// 开始监控
|
||||
const startMonitoring = () => {
|
||||
updateChart()
|
||||
chartInterval = window.setInterval(updateChart, 5000)
|
||||
}
|
||||
|
||||
// 停止监控
|
||||
const stopMonitoring = () => {
|
||||
if (chartInterval) {
|
||||
clearInterval(chartInterval)
|
||||
chartInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSystemInfo()
|
||||
loadDatabaseStatus()
|
||||
loadCacheStatus()
|
||||
loadServices()
|
||||
loadSystemSettings()
|
||||
startMonitoring()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopMonitoring()
|
||||
})
|
||||
|
||||
const loadSystemInfo = async () => {
|
||||
@@ -278,6 +366,39 @@ const handleRefresh = () => {
|
||||
message.success('系统状态已刷新')
|
||||
}
|
||||
|
||||
const exportConfig = () => {
|
||||
try {
|
||||
// 创建要导出的数据对象
|
||||
const exportData = {
|
||||
systemInfo: systemInfo.value,
|
||||
databaseStatus: databaseStatus.value,
|
||||
cacheStatus: cacheStatus.value,
|
||||
systemSettings: systemSettings,
|
||||
services: services.value,
|
||||
exportTime: new Date().toISOString()
|
||||
}
|
||||
|
||||
// 创建Blob对象
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
|
||||
type: 'application/json'
|
||||
})
|
||||
|
||||
// 创建下载链接
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `system-config-${new Date().toISOString().slice(0, 10)}.json`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
message.success('配置导出成功')
|
||||
} catch (error) {
|
||||
message.error('配置导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleStopService = (service: Service) => {
|
||||
Modal.confirm({
|
||||
title: '确认停止',
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新增旅行
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showStats">
|
||||
<template #icon>
|
||||
<BarChartOutlined />
|
||||
@@ -114,6 +120,11 @@
|
||||
详情
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="showEditModal(record)">
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="handleMembers(record)">
|
||||
<TeamOutlined />
|
||||
成员
|
||||
@@ -134,12 +145,100 @@
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 创建/编辑旅行计划模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="modalTitle"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
width="600px"
|
||||
>
|
||||
<a-form
|
||||
ref="travelFormRef"
|
||||
:model="currentTravel"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="旅行标题" name="title">
|
||||
<a-input v-model:value="currentTravel.title" placeholder="请输入旅行标题" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="目的地" name="destination">
|
||||
<a-input v-model:value="currentTravel.destination" placeholder="请输入目的地" />
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="开始日期" name="start_date">
|
||||
<a-date-picker
|
||||
v-model:value="currentTravel.start_date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="结束日期" name="end_date">
|
||||
<a-date-picker
|
||||
v-model:value="currentTravel.end_date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="最大参与人数" name="max_participants">
|
||||
<a-input-number
|
||||
v-model:value="currentTravel.max_participants"
|
||||
:min="1"
|
||||
:max="100"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="预算(元)" name="budget">
|
||||
<a-input-number
|
||||
v-model:value="currentTravel.budget"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="currentTravel.status" placeholder="请选择状态">
|
||||
<a-select-option value="recruiting">招募中</a-select-option>
|
||||
<a-select-option value="full">已满员</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea
|
||||
v-model:value="currentTravel.description"
|
||||
placeholder="请输入旅行描述"
|
||||
:rows="4"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
@@ -148,10 +247,12 @@ import {
|
||||
EyeOutlined,
|
||||
TeamOutlined,
|
||||
RocketOutlined,
|
||||
CloseOutlined
|
||||
CloseOutlined,
|
||||
PlusOutlined,
|
||||
EditOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { getTravelPlans, closeTravelPlan } from '@/api/travel'
|
||||
import type { TravelPlan } from '@/api/travel'
|
||||
import { getTravelPlans, closeTravelPlan, createTravel, updateTravel, getTravel } from '@/api/travel'
|
||||
import type { TravelPlan, TravelCreateData, TravelUpdateData } from '@/api/travel'
|
||||
|
||||
interface SearchForm {
|
||||
destination: string
|
||||
@@ -331,6 +432,197 @@ const handleClose = async (record: TravelPlan) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 添加模态框相关状态
|
||||
const modalVisible = ref(false)
|
||||
const modalLoading = ref(false)
|
||||
const modalTitle = ref('新增旅行')
|
||||
const isEditing = ref(false)
|
||||
const travelFormRef = ref<FormInstance>()
|
||||
|
||||
// 当前旅行数据
|
||||
const currentTravel = ref<Partial<TravelPlan>>({})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
title: [
|
||||
{ required: true, message: '请输入旅行标题' },
|
||||
{ min: 5, max: 100, message: '旅行标题长度为5-100个字符' }
|
||||
],
|
||||
destination: [
|
||||
{ required: true, message: '请输入目的地' },
|
||||
{ min: 2, max: 50, message: '目的地长度为2-50个字符' }
|
||||
],
|
||||
start_date: [
|
||||
{ required: true, message: '请选择开始日期' }
|
||||
],
|
||||
end_date: [
|
||||
{ required: true, message: '请选择结束日期' }
|
||||
],
|
||||
max_participants: [
|
||||
{ required: true, message: '请输入最大参与人数' },
|
||||
{ type: 'number', min: 1, max: 100, message: '参与人数应在1-100之间' }
|
||||
],
|
||||
budget: [
|
||||
{ required: true, message: '请输入预算' },
|
||||
{ type: 'number', min: 0, message: '预算不能为负数' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态' }
|
||||
]
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
modalTitle.value = '新增旅行'
|
||||
isEditing.value = false
|
||||
currentTravel.value = {
|
||||
status: 'recruiting',
|
||||
max_participants: 10,
|
||||
budget: 0
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const showEditModal = async (record: TravelPlan) => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
modalTitle.value = '编辑旅行'
|
||||
isEditing.value = true
|
||||
|
||||
// 获取旅行详情
|
||||
const response = await getTravel(record.id)
|
||||
currentTravel.value = response.data
|
||||
modalVisible.value = true
|
||||
} catch (error) {
|
||||
message.error('获取旅行详情失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalOk = () => {
|
||||
travelFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
if (isEditing.value) {
|
||||
handleUpdateTravel()
|
||||
} else {
|
||||
handleCreateTravel()
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('表单验证失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
travelFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleCreateTravel = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
|
||||
// 前端数据验证
|
||||
if (!currentTravel.value.title) {
|
||||
message.error('旅行标题不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.destination) {
|
||||
message.error('目的地不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.start_date) {
|
||||
message.error('请选择开始日期')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.end_date) {
|
||||
message.error('请选择结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
if (new Date(currentTravel.value.start_date) >= new Date(currentTravel.value.end_date)) {
|
||||
message.error('开始日期必须早于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.max_participants || currentTravel.value.max_participants < 1) {
|
||||
message.error('最大参与人数必须大于0')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentTravel.value.budget === undefined || currentTravel.value.budget < 0) {
|
||||
message.error('预算不能为负数')
|
||||
return
|
||||
}
|
||||
|
||||
await createTravel(currentTravel.value as TravelCreateData)
|
||||
message.success('创建旅行计划成功')
|
||||
modalVisible.value = false
|
||||
loadTravelPlans()
|
||||
} catch (error) {
|
||||
console.error('创建旅行计划失败:', error)
|
||||
message.error('创建旅行计划失败: ' + (error as Error).message)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateTravel = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
|
||||
// 前端数据验证
|
||||
if (!currentTravel.value.title) {
|
||||
message.error('旅行标题不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.destination) {
|
||||
message.error('目的地不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.start_date) {
|
||||
message.error('请选择开始日期')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.end_date) {
|
||||
message.error('请选择结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
if (new Date(currentTravel.value.start_date) >= new Date(currentTravel.value.end_date)) {
|
||||
message.error('开始日期必须早于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentTravel.value.max_participants || currentTravel.value.max_participants < 1) {
|
||||
message.error('最大参与人数必须大于0')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentTravel.value.budget === undefined || currentTravel.value.budget < 0) {
|
||||
message.error('预算不能为负数')
|
||||
return
|
||||
}
|
||||
|
||||
await updateTravel(currentTravel.value.id!, currentTravel.value as TravelUpdateData)
|
||||
message.success('更新旅行计划成功')
|
||||
modalVisible.value = false
|
||||
loadTravelPlans()
|
||||
} catch (error) {
|
||||
console.error('更新旅行计划失败:', error)
|
||||
message.error('更新旅行计划失败: ' + (error as Error).message)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const showStats = () => {
|
||||
message.info('数据统计功能开发中')
|
||||
}
|
||||
|
||||
@@ -342,7 +342,8 @@ const avatarFileList = ref<UploadProps['fileList']>([])
|
||||
const formRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ min: 3, max: 20, message: '用户名长度为3-20个字符' }
|
||||
{ min: 3, max: 20, message: '用户名长度为3-20个字符' },
|
||||
{ pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线' }
|
||||
],
|
||||
nickname: [
|
||||
{ required: true, message: '请输入昵称' },
|
||||
@@ -353,6 +354,15 @@ const formRules = {
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址' }
|
||||
],
|
||||
gender: [
|
||||
{ required: true, message: '请选择性别' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态' }
|
||||
],
|
||||
level: [
|
||||
{ required: true, message: '请选择等级' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -551,13 +561,35 @@ const handleModalCancel = () => {
|
||||
const handleCreateUser = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
|
||||
// 前端数据验证
|
||||
if (!currentUser.value.username) {
|
||||
message.error('用户名不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentUser.value.nickname) {
|
||||
message.error('昵称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentUser.value.phone && !/^1[3-9]\d{9}$/.test(currentUser.value.phone)) {
|
||||
message.error('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentUser.value.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(currentUser.value.email)) {
|
||||
message.error('请输入正确的邮箱地址')
|
||||
return
|
||||
}
|
||||
|
||||
await createUser(currentUser.value)
|
||||
message.success('创建用户成功')
|
||||
modalVisible.value = false
|
||||
fetchUsers()
|
||||
} catch (error) {
|
||||
console.error('创建用户失败:', error)
|
||||
message.error('创建用户失败')
|
||||
message.error('创建用户失败: ' + (error as Error).message)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
@@ -567,13 +599,35 @@ const handleCreateUser = async () => {
|
||||
const handleUpdateUser = async () => {
|
||||
try {
|
||||
modalLoading.value = true
|
||||
|
||||
// 前端数据验证
|
||||
if (!currentUser.value.username) {
|
||||
message.error('用户名不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentUser.value.nickname) {
|
||||
message.error('昵称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentUser.value.phone && !/^1[3-9]\d{9}$/.test(currentUser.value.phone)) {
|
||||
message.error('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (currentUser.value.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(currentUser.value.email)) {
|
||||
message.error('请输入正确的邮箱地址')
|
||||
return
|
||||
}
|
||||
|
||||
await updateUser(currentUser.value.id!, currentUser.value)
|
||||
message.success('更新用户成功')
|
||||
modalVisible.value = false
|
||||
fetchUsers()
|
||||
} catch (error) {
|
||||
console.error('更新用户失败:', error)
|
||||
message.error('更新用户失败')
|
||||
message.error('更新用户失败: ' + (error as Error).message)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user