Files
chazhi_store/src/pages/store/room-detail.vue
2026-01-10 19:09:01 +08:00

803 lines
24 KiB
Vue

<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationStyle": "custom"
}
}</route>
<template>
<view class="pb-180rpx">
<view>
<navbar title="修改包间信息" custom-class="!bg-[#fff]" />
</view>
<view class="store-tabs">
<wd-tabs v-model="tab" @change="RoomDetail.handleChangeTab">
<wd-tab title="基础信息" />
<wd-tab title="规格与价格" />
<wd-tab title="套餐详情" />
<!-- <wd-tab title="购买须知" /> -->
</wd-tabs>
</view>
<view class="h-64rpx bg-[#FFF6EB] px-30rpx text-26rpx text-[#111827] font-400 leading-64rpx">
带"<text class="text-[#ED2D2D]">*</text>"的为必填项
</view>
<view class="mt-20rpx bg-white p-30rpx">
<!-- 基础信息 -->
<view v-if="tab === 0">
<view class="text-34rpx text-[#303133] font-bold leading-48rpx">
基本信息
</view>
<!-- 包间名称 -->
<view class="mt-28rpx add-textarea">
<view class="mb-20rpx flex items-center">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
包间名称
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<wd-input v-model="form.title" no-border placeholder="请输入包间名称" :maxlength="10" show-word-limit
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
</view>
<!-- 包间标签 -->
<view class="mt-28rpx">
<view class="mb-20rpx flex items-center justify-between">
<view class="flex items-center">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
包间标签
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<view class="text-24rpx text-[#9CA3AF] font-400 leading-34rpx">
可选择1~2个标签,每个标签不超过5个字
</view>
</view>
<view class="flex flex-wrap items-center gap-16rpx">
<view v-for="(tag, index) in tags" :key="index"
class="flex items-center rounded-20rpx bg-[#F6F7F8] px-20rpx py-8rpx">
<view class="mr-8rpx text-26rpx text-[#303133] leading-36rpx">
{{ tag.label_name }}
</view>
<wd-icon name="close" size="14px" color="#909399"
@click="RoomDetail.handleRemoveTag(index)" />
</view>
<view v-if="form.tags.length < 2"
class="flex items-center border-2rpx border-[#E5E5E5] rounded-20rpx border-dashed px-20rpx py-8rpx"
@click="RoomDetail.handleAddTag">
<wd-icon name="add" size="16px" color="#909399" class="mr-8rpx" />
<view class="text-26rpx text-[#909399] leading-36rpx">
添加标签
</view>
</view>
</view>
</view>
<!-- 团购视频 -->
<!-- <view class="mt-28rpx">
<view class="mb-28rpx flex items-center">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
团购视频
</view>
</view>
<view v-if="form.video" class="relative h-184rpx w-184rpx overflow-hidden rounded-16rpx">
<wd-img width="100%" height="100%" :src="form.video.thumb" mode="aspectFill" />
<view class="absolute inset-0 flex items-center justify-center">
<wd-icon name="play" size="48rpx" color="#fff" />
</view>
<view
class="absolute right-8rpx top-8rpx h-32rpx w-32rpx flex items-center justify-center rounded-full bg-black bg-opacity-50"
@click="RoomDetail.handleRemoveVideo">
<wd-icon name="close" size="14px" color="#fff" />
</view>
</view>
<wd-upload v-else :file-list="[]" :limit="1" image-mode="scaleToFill" accept="video"
:action="action" @change="RoomDetail.handleUploadVideo">
<view
class="h-184rpx w-184rpx flex flex-col items-center justify-center border-2rpx border-[#E5E5E5] rounded-16rpx border-dashed">
<wd-img width="64rpx" height="64rpx" :src="`${OSS}icon/icon_video.png`" mode="aspectFill" />
<view class="mt-12rpx text-26rpx text-[#303133] font-400 leading-36rpx">
添加视频
</view>
</view>
</wd-upload>
</view> -->
<!-- 包间图片 -->
<view class="mt-28rpx">
<view class="mb-28rpx flex items-center justify-between">
<view class="flex items-center">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
包间图片
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<view class="text-26rpx text-[#9CA3AF] font-400 leading-36rpx">
可添加1-9张图片
</view>
</view>
<view class="flex flex-wrap items-center gap-16rpx">
<wd-upload
:header="{'token': token}"
v-model:file-list="fileList"
:limit="9"
multiple
image-mode="scaleToFill"
:action="action">
</wd-upload>
</view>
</view>
<!-- 排序 -->
<view class="mt-28rpx add-textarea">
<view class="flex items-center justify-between mb-20rpx">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
包间排序
</view>
<view class="text-24rpx text-[#9CA3AF] font-400 leading-34rpx">
数字越大排名越靠前
</view>
</view>
<wd-input v-model="form.weight" no-border placeholder="请输入包间排序" :maxlength="10"
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
</view>
<!-- 推荐人数 -->
<view class="mt-28rpx add-textarea">
<view class="flex items-center justify-between mb-20rpx">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
推荐人数
</view>
</view>
<wd-input v-model="form.people_number" no-border placeholder="推荐人数1-5人" :maxlength="10"
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
</view>
</view>
<!-- 规格与价格 -->
<view v-if="tab === 1">
<view class="mb-30rpx text-34rpx text-[#303133] font-bold leading-48rpx">
规格与价格
</view>
<!-- 价格 -->
<view class="mb-28rpx">
<view class="mb-20rpx flex items-center">
<view class="mr-10rpx text-30rpx text-[#303133] font-bold leading-44rpx">
价格
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<wd-input v-model="form.price" no-border placeholder="价格" type="digit"
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx">
<template #prefix>
<view class="mr-8rpx text-28rpx text-[#303133]">
¥
</view>
</template>
</wd-input>
</view>
<!-- 起订时间 -->
<view class="mb-28rpx">
<view class="mb-20rpx flex items-center justify-between">
<view class="flex items-center">
<view class="mr-10rpx text-30rpx text-[#303133] font-bold leading-44rpx">
起订时间
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<view class="text-24rpx text-[#9CA3AF] font-400 leading-34rpx">
<!-- 范围1~5 -->
</view>
</view>
<view class="flex items-center">
<wd-input v-model="form.hours" no-border placeholder="请输入起订时间" type="number" :max="5"
:min="1" custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
<view class="ml-16rpx text-28rpx text-[#303133]">
小时
</view>
</view>
</view>
<!-- 库存 -->
<!-- <view>
<view class="mb-20rpx flex items-center justify-between">
<view class="flex items-center">
<view class="mr-10rpx text-30rpx text-[#303133] font-bold leading-44rpx">
库存
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<view class="text-24rpx text-[#9CA3AF] font-400 leading-34rpx">
输入范围1-999
</view>
</view>
<wd-input v-model="form.inventory" no-border placeholder="请输入库存" type="number" :max="999" :min="1"
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
</view> -->
</view>
<!-- 套餐详情 -->
<view v-if="tab === 2">
<view class="mb-30rpx text-34rpx text-[#303133] font-bold leading-48rpx">
套餐详情
</view>
<!-- 套餐介绍 -->
<!-- <view class="add-textarea mb-28rpx">
<view class="mb-20rpx flex items-center justify-between">
<view class="flex items-center">
<view class="mr-10rpx text-30rpx text-[#303133] font-bold leading-44rpx">
套餐介绍
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<view class="text-24rpx text-[#9CA3AF] font-400 leading-34rpx">
每条内容之间需要换行输入
</view>
</view>
<wd-textarea v-model="form.packageIntro"
custom-class="!rounded-18rpx !border-2rpx !border-[#EFF0EF] !bg-[#F8F9FA] !mt-20rpx"
custom-textarea-class="!bg-[#F8F9FA]" placeholder="请输入套餐介绍,每条内容换行输入" />
</view> -->
<!-- 其他说明 -->
<view class="add-textarea mt-30rpx">
<view class="mb-20rpx flex items-center">
<view class="mr-10rpx text-30rpx text-[#303133] font-bold leading-44rpx">
其他说明
</view>
</view>
<wd-textarea v-model="form.other_describe"
custom-class="!rounded-18rpx !border-2rpx !border-[#EFF0EF] !bg-[#F8F9FA] !mt-20rpx"
custom-textarea-class="!bg-[#F8F9FA]" placeholder="请输入其他说明" />
</view>
</view>
<!-- 购买须知 -->
<view v-if="tab === 3">
<!-- <view class="mb-30rpx text-34rpx text-[#303133] font-bold leading-48rpx">
购买须知
</view> -->
<!-- 使用人数 -->
<!-- <view class="mb-28rpx">
<view class="mb-20rpx flex items-center">
<view class="mr-10rpx text-30rpx text-[#303133] font-bold leading-44rpx">
使用人数
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<wd-input v-model="form.userCount" no-border placeholder="建议4~6人使用"
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
</view> -->
<!-- 退改说明 -->
<!-- <view class="add-textarea mt-28rpx">
<view class="mb-20rpx flex items-center">
<view class="mr-10rpx text-32rpx text-[#303133] font-bold leading-44rpx">
退改说明
</view>
<view class="flex items-center">
<wd-img width="16rpx" height="16rpx" :src="`${OSS}icon/icon_validate.png`" />
</view>
</view>
<wd-textarea v-model="form.refundPolicy"
custom-class="!rounded-18rpx !border-2rpx !border-[#EFF0EF] !bg-[#F8F9FA] !mt-20rpx"
custom-textarea-class="!bg-[#F8F9FA]" placeholder="请输入退改说明" />
</view> -->
</view>
</view>
<!-- 底部保存按钮 -->
<view class="fixed bottom-0 left-0 right-0 h-152rpx bg-white">
<view class="mt-34rpx flex items-center justify-center">
<wd-button custom-class="!text-32rpx !w-630rpx !h-90rpx !bg-[#4C9F44] !rounded-8rpx !text-[#fff]"
@click="RoomDetail.handleSave">
保存
</wd-button>
</view>
</view>
<!-- 选择标签弹窗 -->
<wd-popup v-model="showTagSelectPopup" lock-scroll custom-style="border-radius: 32rpx 32rpx 0rpx 0rpx;"
position="bottom" @close="RoomDetail.handleCloseTagSelect">
<view class="relative bg-white px-30rpx pb-56rpx pt-50rpx">
<!-- 关闭按钮 -->
<view class="absolute right-30rpx top-18rpx" @click="RoomDetail.handleCloseTagSelect">
<wd-icon name="close" size="20px" color="#C0C4CC" />
</view>
<!-- 标题 -->
<view class="mb-40rpx text-center text-36rpx text-[#121212] leading-50rpx">
选择标签
</view>
<!-- 我的标签标题和管理/退出管理 -->
<view class="mb-20rpx flex items-center justify-between">
<view class="text-32rpx text-[#303133] font-bold leading-44rpx">
我的标签
</view>
<view class="text-28rpx text-[#4C9F44] leading-40rpx"
@click="isTagManageMode ? RoomDetail.handleExitManagement() : RoomDetail.handleEnterManagement()">
{{ isTagManageMode ? '退出管理' : '管理' }}
</view>
</view>
<!-- 说明文字 -->
<view class="mb-28rpx text-24rpx text-[#9CA3AF] leading-34rpx">
可选择1~2个标签, 每个标签不超过5个字
</view>
<!-- 标签列表 -->
<view class="flex flex-wrap gap-16rpx">
<view v-for="(item, index) in availableTags" :key="item.id"
class="flex items-center rounded-20rpx px-20rpx py-8rpx"
:class="selectedTags.includes(index) ? 'bg-[#4C9F44]' : 'bg-[#F6F7F8]'"
@click="RoomDetail.handleSelectTag(index)">
<view class="text-26rpx leading-36rpx"
:class="selectedTags.includes(index) ? 'text-[#fff]' : 'text-[#303133]'"
:style="{ marginRight: isTagManageMode ? '8rpx' : '0' }">
{{ item.label_name }}
</view>
<wd-icon v-if="isTagManageMode" name="close-circle-filled" size="14px"
:color="selectedTags.includes(index) ? '#fff' : '#909399'"
@click.stop="RoomDetail.handleRemoveTagFromList(index, $event)" />
</view>
<!-- 新建标签按钮 -->
<view v-if="!isTagManageMode"
class="flex items-center border-2rpx border-[#E5E5E5] rounded-20rpx border-dashed px-20rpx py-8rpx"
@click="RoomDetail.handleShowCreateTag">
<wd-icon name="add" size="16px" color="#909399" class="mr-8rpx" />
<view class="text-26rpx text-[#909399] leading-36rpx">
新建标签
</view>
</view>
</view>
<!-- 确认按钮 -->
<view
class="mt-40rpx h-90rpx rounded-8rpx bg-[#4C9F44] text-center text-30rpx text-[#fff] leading-90rpx"
:class="isTagManageMode ? 'opacity-0 pointer-events-none' : ''"
@click="RoomDetail.handleConfirmTags">
确认
</view>
</view>
</wd-popup>
<!-- 新建标签弹窗 -->
<wd-popup v-model="showCreateTagPopup" lock-scroll custom-style="border-radius: 32rpx 32rpx 0rpx 0rpx;"
position="bottom" @close="RoomDetail.handleCloseCreateTag">
<view class="relative bg-white px-30rpx pb-78rpx pt-50rpx">
<!-- 导航栏 -->
<view class="mb-40rpx flex items-center justify-between">
<view class="flex items-center" @click="RoomDetail.handleCloseCreateTag">
<wd-icon name="arrow-left" size="20px" color="#303133" />
</view>
<view class="text-center text-36rpx text-[#121212] leading-50rpx">
新建标签
</view>
<view class="flex items-center" @click="RoomDetail.handleCloseCreateTag">
<wd-icon name="close" size="20px" color="#C0C4CC" />
</view>
</view>
<!-- 输入框 -->
<view class="mb-40rpx">
<wd-input v-model="newTagName" type="text" no-border placeholder="标签名称"
custom-class="!bg-[#F6F7F8] !rounded-16rpx !px-28rpx !py-20rpx" />
</view>
<!-- 完成按钮 -->
<view class="h-90rpx rounded-8rpx bg-[#4C9F44] text-center text-30rpx text-[#fff] leading-90rpx"
@click="RoomDetail.handleCompleteCreateTag">
完成
</view>
</view>
</wd-popup>
</view>
</template>
<script lang="ts" setup>
import { getRoomDetails, getRoomLabelList, handleCreateTag, handleDeleteTag, editRoom } from '@/api/store'
import { updateUserInfo } from '@/api/user'
import { router, removeImageUrlPrefix } from '@/utils/tools'
import { toast } from '@/utils/toast'
import { useStoreStore } from '@/store'
const OSS = inject('OSS')
const token = ref<string>('') // 用户token
const useStore = useStoreStore()
// tab
const tab = ref<number>(0)
// 上传文件
const fileList = ref<any[]>([])
const action = import.meta.env.VITE_UPLOAD_BASEURL
// 标签相关
const showTagSelectPopup = ref(false)
const showCreateTagPopup = ref(false)
const newTagName = ref<string>('')
console.log("🚀 ~ newTagName:", newTagName.value)
const isTagManageMode = ref(false) // 是否处于管理模式
const selectedTags = ref<number[]>([]) // 临时选中的标签,点击确认后才回填到表单
// Mock 已有标签列表
const availableTags = ref<Array<{
id: number
label_name: string
}>>([
{id: 0, label_name: ''}
])
const roomId = ref<number>(0) // 包间ID
// 表单
const form = reactive({
title: '',
image_arr: [] as string[],
img: '',
tags: [] as string[],
price: '',
hours: '',
video: null as any,
other_describe: '',
weight: 0,
people_number: ''
})
const tags = ref<Array<{ id: number, label_name: string, index: number }>>([])
const roomLabelId = ref<string>('')
onLoad(async (args) => {
token.value = uni.getStorageSync('token')
roomId.value = args.id || 0
await RoomDetail.handleGetRoomDetails()
await RoomDetail.handleGetRoomLabels()
})
const RoomDetail = {
/**
* 初始化信息
*/
handleGetRoomDetails: async () => {
const res = await getRoomDetails(roomId.value)
fileList.value =res.details.room.room_arr.map((url: string) => ({
url,
}))
roomLabelId.value = res.details.room.label_id
form.title = res.details.room.title
form.img = res.details.room.img
form.price = res.details.room.price.toString()
form.hours = res.details.room.hours.toString()
form.other_describe = res.details.room.other_describe || ''
form.weight = res.details.room.weight || 0
form.people_number = res.details.room.people_number || ''
},
/**
* 获取包间标签列表
*/
handleGetRoomLabels: async () => {
const res = await getRoomLabelList(useStore.defaultStore.id)
availableTags.value = res.list
if (roomLabelId.value) {
const labelIds = roomLabelId.value.split(',').map((id: string) => parseInt(id))
tags.value = labelIds.map((id: number) => {
const tagIndex = availableTags.value.findIndex(tag => tag.id === id)
const tag = availableTags.value[tagIndex]
return {
id: tag.id,
label_name: tag.label_name,
index: tagIndex
}
})
// 设置已选择的标签
selectedTags.value = []
labelIds.forEach((id: number) => {
const index = availableTags.value.findIndex(tag => tag.id === id)
if (index !== -1) {
selectedTags.value.push(index)
}
})
}
},
/**
* 切换tab
*/
handleChangeTab: (e: any) => {
tab.value = e.name
},
/**
* 添加标签 - 显示选择标签弹窗
*/
handleAddTag: () => {
// 初始化临时选中的标签为当前表单中的标签
// selectedTags.value = availableTags.value
showTagSelectPopup.value = true
},
/**
* 关闭选择标签弹窗
*/
handleCloseTagSelect: () => {
showTagSelectPopup.value = false
isTagManageMode.value = false // 关闭时重置管理模式
// selectedTags.value = [] // 清空临时选中的标签
},
/**
* 选择标签(临时选择,不直接修改表单)
*/
handleSelectTag: (id: number) => {
if (isTagManageMode.value) {
// 管理模式时,点击标签不进行选择操作
return
}
if (selectedTags.value.includes(id)) {
// 如果已选择,则取消选择
const index = selectedTags.value.indexOf(id)
selectedTags.value.splice(index, 1)
}
else {
// 如果未选择,则添加
if (selectedTags.value.length >= 2) {
toast.info('最多只能选择2个标签')
return
}
selectedTags.value.push(id)
}
},
/**
* 从选择列表删除标签
*/
handleRemoveTagFromList: async (index: number, event: any) => {
event.stopPropagation()
uni.showLoading({
title: '操作中...',
mask: true
})
try {
const tagId = availableTags.value[index].id
availableTags.value.splice(index, 1)
await handleDeleteTag(tagId)
tags.value = tags.value.filter(tag => tag.id !== tagId)
uni.hideLoading()
toast.info('删除成功')
} catch(e) {
uni.hideLoading()
toast.info('删除失败,请稍后重试')
}
},
/**
* 确认选择标签
*/
handleConfirmTags: () => {
tags.value = selectedTags.value.map(index => {
return {
id: availableTags.value[index].id,
label_name: availableTags.value[index].label_name,
index
}
})
showTagSelectPopup.value = false
isTagManageMode.value = false
// selectedTags.value = []
},
/**
* 进入管理模式
*/
handleEnterManagement: () => {
isTagManageMode.value = true
selectedTags.value = []
},
/**
* 退出管理模式
*/
handleExitManagement: () => {
isTagManageMode.value = false
},
/**
* 显示新建标签弹窗
*/
handleShowCreateTag: () => {
showTagSelectPopup.value = false
showCreateTagPopup.value = true
},
/**
* 关闭新建标签弹窗
*/
handleCloseCreateTag: () => {
showCreateTagPopup.value = false
newTagName.value = ''
},
/**
* 完成新建标签
*/
handleCompleteCreateTag: async () => {
const tagName = newTagName.value.trim()
console.log("🚀 ~ tagName:", newTagName.value)
console.log("🚀 ~ tagName:", tagName)
if (!tagName) {
toast.info('请输入标签名称')
return
}
if (tagName.length > 5) {
toast.info('标签不能超过5个字')
return
}
console.log("🚀 ~ tagName:", tagName)
uni.showLoading({
title: '操作者中...'
})
try {
await handleCreateTag({
store_id: useStore.defaultStore.id,
label_name: tagName
})
uni.hideLoading()
RoomDetail.handleGetRoomLabels()
showCreateTagPopup.value = false
newTagName.value = ''
showTagSelectPopup.value = true
toast.info('标签创建成功')
} catch (e) {
uni.hideLoading()
}
},
/**
* 删除标签
*/
handleRemoveTag: (index: number) => {
tags.value.splice(index, 1)
},
/**
* 上传视频
*/
handleUploadVideo: (event: any) => {
if (event.fileList && event.fileList.length > 0) {
form.video = event.fileList[0]
}
},
/**
* 删除视频
*/
handleRemoveVideo: () => {
form.video = null
},
/**
* 保存
*/
handleSave: async () => {
// TODO: 实现保存功能
if (!form.title) {
toast.info('请输入包间名称')
return
}
if (tags.value.length === 0) {
toast.info('请选择包间标签')
return
}
if (fileList.value.length === 0) {
toast.info('请上传包间图片')
return
}
if (!form.price) {
toast.info('请输入价格')
return
}
if (!form.hours) {
toast.info('请输入起订时间')
return
}
const imgArr = removeImageUrlPrefix(fileList.value)
let params = {
id: roomId.value,
img: imgArr[0],
img_arr: imgArr.join(','),
title: form.title,
label_id: tags.value.map(tag => tag.id).join(','),
price: Number(form.price),
hours: Number(form.hours),
other_describe: form.other_describe,
weight: Number(form.weight),
people_number: form.people_number
}
uni.showLoading({
title: '保存中...',
mask: true
})
try {
await editRoom(params)
uni.hideLoading()
toast.info('修改成功')
// router.navigateBack(1, 500)
} catch (e) {
toast.info('修改失败,请稍后重试')
uni.hideLoading()
return
}
}
}
</script>
<style lang="scss">
page {
background: #f6f7f8;
}
.store-tabs {
:deep() {
.wd-tabs__line {
background-color: #4c9f44 !important;
}
}
}
.add-textarea {
:deep() {
.wd-input__value,
.wd-input__count {
background: transparent !important;
}
}
}
</style>