|
|
@@ -1,118 +1,46 @@
|
|
|
<template>
|
|
|
<CommonPage>
|
|
|
<template #action>
|
|
|
- <NButton v-permission="'addOnline'" type="primary" @click="handleAdd()">
|
|
|
+ <NButton v-if="!showAddEditForm" type="primary" @click="handleAdd()">
|
|
|
<i class="i-material-symbols:add mr-4 text-18" />
|
|
|
新增
|
|
|
</NButton>
|
|
|
</template>
|
|
|
|
|
|
<MeCrud
|
|
|
+ v-if="!showAddEditForm"
|
|
|
ref="$table"
|
|
|
v-model:query-items="queryItems"
|
|
|
:scroll-x="1400"
|
|
|
:columns="columns"
|
|
|
:get-data="api.read"
|
|
|
>
|
|
|
- <MeQueryItem label="展览名称" :label-width="70">
|
|
|
+ <MeQueryItem label="展会名称" :label-width="70">
|
|
|
<n-input
|
|
|
v-model:value="queryItems.name"
|
|
|
type="text"
|
|
|
- placeholder="请输入展览名称"
|
|
|
+ placeholder="请输入展会名称"
|
|
|
clearable
|
|
|
/>
|
|
|
</MeQueryItem>
|
|
|
-
|
|
|
- <MeQueryItem label="展览状态" :label-width="70">
|
|
|
- <n-select
|
|
|
- v-model:value="queryItems.status"
|
|
|
- clearable
|
|
|
- :options="[
|
|
|
- { label: '待审核', value: '待审核' },
|
|
|
- { label: '已上架', value: '已上架' },
|
|
|
- ]"
|
|
|
- />
|
|
|
- </MeQueryItem>
|
|
|
-
|
|
|
- <MeQueryItem label="展馆" :label-width="50">
|
|
|
- <n-input
|
|
|
- v-model:value="queryItems.museum"
|
|
|
- type="text"
|
|
|
- placeholder="请输入展馆"
|
|
|
- clearable
|
|
|
- />
|
|
|
- </MeQueryItem>
|
|
|
-
|
|
|
- <div class="flex justify-end">
|
|
|
- <NButton v-permission="'findOnline'" type="primary" @click="$table?.handleSearch()">
|
|
|
- <template #icon>
|
|
|
- <i class="i-material-symbols:search mr-2 text-16" />
|
|
|
- </template>
|
|
|
- 查找
|
|
|
- </NButton>
|
|
|
- </div>
|
|
|
</MeCrud>
|
|
|
|
|
|
- <MeModal ref="modalRef" width="520px">
|
|
|
- <n-form
|
|
|
- ref="modalFormRef"
|
|
|
- label-placement="left"
|
|
|
- label-align="left"
|
|
|
- :label-width="80"
|
|
|
- :model="modalForm"
|
|
|
- :disabled="modalAction === 'view'"
|
|
|
- >
|
|
|
- <n-form-item
|
|
|
- label="展览名称"
|
|
|
- path="name"
|
|
|
- :rule="{
|
|
|
- required: true,
|
|
|
- message: '请输入展览名称',
|
|
|
- trigger: ['input', 'blur'],
|
|
|
- }"
|
|
|
- >
|
|
|
- <n-input v-model:value="modalForm.name" />
|
|
|
- </n-form-item>
|
|
|
- <n-form-item
|
|
|
- label="展览状态"
|
|
|
- path="status"
|
|
|
- :rule="{
|
|
|
- required: true,
|
|
|
- message: '请选择展览状态',
|
|
|
- trigger: ['change', 'blur'],
|
|
|
- }"
|
|
|
- >
|
|
|
- <n-select
|
|
|
- v-model:value="modalForm.status"
|
|
|
- :options="[
|
|
|
- { label: '已上架', value: '已上架' },
|
|
|
- { label: '待审核', value: '待审核' },
|
|
|
- ]"
|
|
|
- />
|
|
|
- </n-form-item>
|
|
|
- <n-form-item
|
|
|
- label="展览时间"
|
|
|
- path="exhibitionTime"
|
|
|
- >
|
|
|
- <n-date-picker
|
|
|
- v-model:value="modalForm.exhibitionTime"
|
|
|
- type="daterange"
|
|
|
- clearable
|
|
|
- />
|
|
|
- </n-form-item>
|
|
|
- <n-form-item
|
|
|
- label="展馆位置"
|
|
|
- path="museum"
|
|
|
- :rule="{
|
|
|
- required: true,
|
|
|
- message: '请输入展馆位置',
|
|
|
- trigger: ['input', 'blur'],
|
|
|
- }"
|
|
|
- >
|
|
|
- <n-input v-model:value="modalForm.museum" />
|
|
|
- </n-form-item>
|
|
|
- </n-form>
|
|
|
- </MeModal>
|
|
|
+ <AddAndEditOffline
|
|
|
+ v-if="showAddEditForm"
|
|
|
+ ref="addEditRef"
|
|
|
+ :is-edit="modalAction === 'edit'"
|
|
|
+ :form-data="modalForm"
|
|
|
+ :img-list="imgList"
|
|
|
+ :modal-action="modalAction"
|
|
|
+ :pavilion-options="pavilionOptions"
|
|
|
+ :artist-options="artistOptions"
|
|
|
+ @cancel="handleFormCancel"
|
|
|
+ @confirm="handleComponentConfirm"
|
|
|
+ @upload="handleUpload"
|
|
|
+ @search-location="handleAddressSelect"
|
|
|
+ @pavilion-search="handlePavilionSearch"
|
|
|
+ @artist-search="handleArtistSearch"
|
|
|
+ />
|
|
|
</CommonPage>
|
|
|
</template>
|
|
|
|
|
|
@@ -120,18 +48,367 @@
|
|
|
import { MeCrud, MeModal, MeQueryItem } from '@/components'
|
|
|
import { useCrud } from '@/composables'
|
|
|
import { withPermission } from '@/directives'
|
|
|
-import { formatDateTime } from '@/utils'
|
|
|
-import { NButton, NImage, NSwitch, NTag, NDatePicker, NSelect } from 'naive-ui'
|
|
|
+import { formatDateTime, request } from '@/utils'
|
|
|
+import { NButton, NImage, NSwitch, NTag, NSelect, NUpload, NUploadDragger, NText, NRadio, NRadioGroup, NInputNumber, NDatePicker, NTimePicker } from 'naive-ui'
|
|
|
+import WangEditor from '@/components/common/WangEditor.vue'
|
|
|
+import AddAndEditOffline from './components/addAndEditOffline.vue'
|
|
|
import api from './api'
|
|
|
+import pavilionApi from '../GalleryMgt/api'
|
|
|
+import artistApi from '../ArtistMgt/api'
|
|
|
|
|
|
-defineOptions({ name: 'OfflineExhibitionNewsMgt' })
|
|
|
+defineOptions({ name: 'OnlineExhibition' })
|
|
|
|
|
|
const $table = ref(null)
|
|
|
+const addEditRef = ref(null)
|
|
|
/** QueryBar筛选参数(可选) */
|
|
|
const queryItems = ref({})
|
|
|
|
|
|
-onMounted(() => {
|
|
|
+// 控制显示状态:false 显示表格,true 显示表单
|
|
|
+const showAddEditForm = ref(false)
|
|
|
+
|
|
|
+// 展馆相关状态
|
|
|
+const pavilionOptions = ref([])
|
|
|
+const pavilionLoading = ref(false)
|
|
|
+
|
|
|
+// 艺术家相关状态
|
|
|
+const artistOptions = ref([])
|
|
|
+const artistLoading = ref(false)
|
|
|
+
|
|
|
+// 地址相关状态
|
|
|
+const addressOptions = ref([])
|
|
|
+const addressLoading = ref(false)
|
|
|
+
|
|
|
+// 富文本编辑器相关 - 移到组件内部
|
|
|
+// const editorRef = ref(null)
|
|
|
+// const toolbarConfig = ref({})
|
|
|
+// const editorConfig = ref({})
|
|
|
+
|
|
|
+// 图片上传相关状态
|
|
|
+const imgList = ref({
|
|
|
+ cover: [],
|
|
|
+ share: []
|
|
|
+})
|
|
|
+
|
|
|
+// 防抖搜索展馆
|
|
|
+let searchTimer = null
|
|
|
+const handlePavilionSearch = (query) => {
|
|
|
+ if (searchTimer) {
|
|
|
+ clearTimeout(searchTimer)
|
|
|
+ }
|
|
|
+ searchTimer = setTimeout(() => {
|
|
|
+ loadPavilionOptions(query)
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+// 防抖搜索艺术家
|
|
|
+let artistSearchTimer = null
|
|
|
+const handleArtistSearch = (query) => {
|
|
|
+ if (artistSearchTimer) {
|
|
|
+ clearTimeout(artistSearchTimer)
|
|
|
+ }
|
|
|
+ artistSearchTimer = setTimeout(() => {
|
|
|
+ loadArtistOptions(query)
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+// 防抖搜索地址
|
|
|
+let addressSearchTimer = null
|
|
|
+const handleAddressSearch = (query) => {
|
|
|
+ if (addressSearchTimer) {
|
|
|
+ clearTimeout(addressSearchTimer)
|
|
|
+ }
|
|
|
+ addressSearchTimer = setTimeout(() => {
|
|
|
+ loadAddressOptions(query)
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+// 加载展馆选项
|
|
|
+const loadPavilionOptions = async (name = '') => {
|
|
|
+ pavilionLoading.value = true
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ pageSize: 20,
|
|
|
+ pageNo: 1
|
|
|
+ }
|
|
|
+ if (name) {
|
|
|
+ params.name = name,
|
|
|
+ params.pageSize = 100
|
|
|
+ }
|
|
|
+ const { data } = await pavilionApi.read(params)
|
|
|
+ pavilionOptions.value = data.pageData || []
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载展馆列表失败:', error)
|
|
|
+ pavilionOptions.value = []
|
|
|
+ } finally {
|
|
|
+ pavilionLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载艺术家选项
|
|
|
+const loadArtistOptions = async (name = '') => {
|
|
|
+ artistLoading.value = true
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ pageSize: 20,
|
|
|
+ pageNo: 1
|
|
|
+ }
|
|
|
+ if (name) {
|
|
|
+ params.name = name
|
|
|
+ params.pageSize = 100
|
|
|
+ }
|
|
|
+ const { data } = await artistApi.read(params)
|
|
|
+ artistOptions.value = data.pageData || []
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载艺术家列表失败:', error)
|
|
|
+ artistOptions.value = []
|
|
|
+ } finally {
|
|
|
+ artistLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 地图相关变量
|
|
|
+let map = null
|
|
|
+let marker = null
|
|
|
+
|
|
|
+// 初始化地图
|
|
|
+function initMap() {
|
|
|
+ if (typeof TMap === 'undefined') {
|
|
|
+ console.error('腾讯地图SDK未加载')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待DOM完全渲染后再初始化地图
|
|
|
+ setTimeout(() => {
|
|
|
+ const mapContainer = document.getElementById('mapContainer')
|
|
|
+ if (!mapContainer) {
|
|
|
+ console.warn('地图容器未找到')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查容器是否可见
|
|
|
+ const rect = mapContainer.getBoundingClientRect()
|
|
|
+ if (rect.width === 0 || rect.height === 0) {
|
|
|
+ console.warn('地图容器尺寸为0,延迟初始化')
|
|
|
+ setTimeout(initMap, 500)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果地图实例已存在,先销毁
|
|
|
+ if (map) {
|
|
|
+ try {
|
|
|
+ map.destroy()
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('销毁旧地图实例失败:', error)
|
|
|
+ }
|
|
|
+ map = null
|
|
|
+ marker = null
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ map = new TMap.Map(mapContainer, {
|
|
|
+ center: new TMap.LatLng(39.908823, 116.397470),
|
|
|
+ zoom: 13,
|
|
|
+ })
|
|
|
+
|
|
|
+ marker = new TMap.MultiMarker({
|
|
|
+ map,
|
|
|
+ styles: {
|
|
|
+ marker: new TMap.MarkerStyle({
|
|
|
+ width: 25,
|
|
|
+ height: 35,
|
|
|
+ anchor: { x: 16, y: 32 },
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ geometries: [{
|
|
|
+ id: 'marker1',
|
|
|
+ styleId: 'marker',
|
|
|
+ position: new TMap.LatLng(39.908823, 116.397470),
|
|
|
+ }],
|
|
|
+ })
|
|
|
+
|
|
|
+ console.log('地图初始化成功')
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('地图初始化失败:', error)
|
|
|
+ }
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+// 在地图上标记位置
|
|
|
+function markLocationOnMap(location, address, locationData) {
|
|
|
+ if (!map || !location) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 更新标记位置
|
|
|
+ const position = new TMap.LatLng(location.lat, location.lng)
|
|
|
+
|
|
|
+ // 更新地图中心和缩放级别
|
|
|
+ if (map) {
|
|
|
+ map.setCenter(position)
|
|
|
+ map.setZoom(16) // 设置合适的缩放级别
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新标记位置
|
|
|
+ if (marker) {
|
|
|
+ marker.updateGeometries([{
|
|
|
+ id: 'marker1',
|
|
|
+ styleId: 'marker',
|
|
|
+ position: position,
|
|
|
+ }])
|
|
|
+ }
|
|
|
+
|
|
|
+ // 存储经纬度信息到modalForm
|
|
|
+ modalForm.value.latitude = location.lat
|
|
|
+ modalForm.value.longitude = location.lng
|
|
|
+ modalForm.value.city = locationData.city
|
|
|
+
|
|
|
+ console.log('地图标记成功:', { lat: location.lat, lng: location.lng, city: modalForm.value.city })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('地图标记失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理地址选择 - 地图定位逻辑
|
|
|
+const handleAddressSelect = (locationData) => {
|
|
|
+ console.log('选择的地址:', locationData)
|
|
|
+
|
|
|
+ if (locationData.location) {
|
|
|
+ // 在地图上标记位置
|
|
|
+ markLocationOnMap(locationData.location, locationData.address, locationData)
|
|
|
+ } else if (locationData.address) {
|
|
|
+ // 检查是否有传递过来的经纬度信息(编辑模式回显)
|
|
|
+ if (locationData.latitude && locationData.longitude) {
|
|
|
+ // 使用传递过来的经纬度进行地图定位
|
|
|
+ const savedLocation = {
|
|
|
+ lat: locationData.latitude,
|
|
|
+ lng: locationData.longitude
|
|
|
+ }
|
|
|
+ markLocationOnMap(savedLocation, locationData.address, {
|
|
|
+ city: locationData.city,
|
|
|
+ address: locationData.address
|
|
|
+ })
|
|
|
+ } else if (modalForm.value.latitude && modalForm.value.longitude) {
|
|
|
+ // 使用表单中已保存的经纬度进行地图定位
|
|
|
+ const savedLocation = {
|
|
|
+ lat: modalForm.value.latitude,
|
|
|
+ lng: modalForm.value.longitude
|
|
|
+ }
|
|
|
+ markLocationOnMap(savedLocation, locationData.address, {
|
|
|
+ city: modalForm.value.city,
|
|
|
+ address: locationData.address
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 如果没有坐标,可以进行地址解析
|
|
|
+ console.log('需要进行地址解析:', locationData.address)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 确定操作
|
|
|
+function handleComponentConfirm() {
|
|
|
+ // 重置地图实例
|
|
|
+ if (map) {
|
|
|
+ map.destroy()
|
|
|
+ map = null
|
|
|
+ marker = null
|
|
|
+ }
|
|
|
+ // 保存成功后隐藏表单
|
|
|
+ showAddEditForm.value = false
|
|
|
+ // 使用nextTick确保DOM更新后再刷新列表
|
|
|
+ nextTick(() => {
|
|
|
+ $table.value?.handleSearch()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 加载腾讯地图SDK
|
|
|
+function loadTencentMapSDK() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ // 检查是否已经加载
|
|
|
+ if (typeof TMap !== 'undefined') {
|
|
|
+ resolve()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建script标签加载SDK
|
|
|
+ const script = document.createElement('script')
|
|
|
+ script.src = 'https://map.qq.com/api/gljs?v=1.exp&key=YCABZ-AFPRX-VD54O-TL3VN-TL7A3-KPBQJ'
|
|
|
+ script.onload = () => {
|
|
|
+ console.log('腾讯地图SDK加载成功')
|
|
|
+ resolve()
|
|
|
+ }
|
|
|
+ script.onerror = () => {
|
|
|
+ console.error('腾讯地图SDK加载失败')
|
|
|
+ reject(new Error('腾讯地图SDK加载失败'))
|
|
|
+ }
|
|
|
+ document.head.appendChild(script)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 上传前验证
|
|
|
+function onBeforeUpload({ file }) {
|
|
|
+ if (!file.file?.type.startsWith('image/')) {
|
|
|
+ $message.error('只能上传图片')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+// 处理文件上传
|
|
|
+async function handleUpload(options, type) {
|
|
|
+ const { file } = options
|
|
|
+ try {
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', file.file)
|
|
|
+
|
|
|
+ const response = await request.post('/file/upload', formData, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'multipart/form-data'
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (response.data) {
|
|
|
+ // 更新对应类型的图片列表
|
|
|
+ imgList.value[type] = [{
|
|
|
+ url: response.data,
|
|
|
+ remoteUrl: response.data
|
|
|
+ }]
|
|
|
+
|
|
|
+ // 更新表单数据
|
|
|
+ if (type === 'cover') {
|
|
|
+ modalForm.value.coverImageUrl = response.data
|
|
|
+ } else if (type === 'share') {
|
|
|
+ modalForm.value.shareImageUrl = response.data
|
|
|
+ }
|
|
|
+
|
|
|
+ $message.success('图片上传成功')
|
|
|
+ options.onFinish()
|
|
|
+ } else {
|
|
|
+ throw new Error('上传响应格式错误')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('图片上传失败', error)
|
|
|
+ $message.error('图片上传失败:' + (error.message || '未知错误'))
|
|
|
+ options.onError()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
$table.value?.handleSearch()
|
|
|
+ // 初始加载展馆选项
|
|
|
+ loadPavilionOptions()
|
|
|
+ // 初始加载艺术家选项
|
|
|
+ loadArtistOptions()
|
|
|
+
|
|
|
+ // 加载腾讯地图SDK并初始化地图
|
|
|
+ try {
|
|
|
+ await loadTencentMapSDK()
|
|
|
+ // 延迟初始化地图,确保DOM已渲染
|
|
|
+ // setTimeout(() => {
|
|
|
+ // initMap()
|
|
|
+ // }, 500)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('地图SDK加载失败:', error)
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
const {
|
|
|
@@ -139,19 +416,252 @@ const {
|
|
|
modalFormRef,
|
|
|
modalForm,
|
|
|
modalAction,
|
|
|
- handleAdd,
|
|
|
handleDelete,
|
|
|
- handleOpen,
|
|
|
+ handleOpen: originalHandleOpen,
|
|
|
handleSave,
|
|
|
} = useCrud({
|
|
|
- name: '线下展览',
|
|
|
- initForm: { status: '待审核' },
|
|
|
- doCreate: api.create,
|
|
|
+ name: '线上展会',
|
|
|
+ initForm: {
|
|
|
+ display: true,
|
|
|
+ setTop: false,
|
|
|
+ isBomb: false,
|
|
|
+ hot: false,
|
|
|
+ dateRange: null,
|
|
|
+ startTime: null,
|
|
|
+ endTime: null,
|
|
|
+ statusText: '',
|
|
|
+ artistId: [],
|
|
|
+ address: '',
|
|
|
+ description: '',
|
|
|
+ coverImageUrl: '',
|
|
|
+ shareImageUrl: '',
|
|
|
+ latitude: 0,
|
|
|
+ longitude: 0,
|
|
|
+ city: ''
|
|
|
+ },
|
|
|
+ doCreate: (formData) => {
|
|
|
+ // 格式化展览日期
|
|
|
+ let openTime = ''
|
|
|
+ if (formData.dateRange && formData.dateRange.length === 2) {
|
|
|
+ const startDate = new Date(formData.dateRange[0]).toISOString().split('T')[0]
|
|
|
+ const endDate = new Date(formData.dateRange[1]).toISOString().split('T')[0]
|
|
|
+ openTime = `${startDate} - ${endDate}`
|
|
|
+ }
|
|
|
+
|
|
|
+ const apiData = {
|
|
|
+ address: formData.address || '',
|
|
|
+ artistIdList: Array.isArray(formData.artistId) ? formData.artistId : (formData.artistId ? [formData.artistId] : []),
|
|
|
+ city: formData.city || '',
|
|
|
+ description: formData.description || '',
|
|
|
+ imageUrl: formData.coverImageUrl || '',
|
|
|
+ latitude: formData.latitude || 0,
|
|
|
+ longitude: formData.longitude || 0,
|
|
|
+ name: formData.name || '',
|
|
|
+ online: 0,
|
|
|
+ openTime: openTime,
|
|
|
+ openTimeDetail: formData.openTimeDetail || '',
|
|
|
+ pavilionId: formData.pavilionId || 0,
|
|
|
+ setHot: formData.hot ? 1 : 0,
|
|
|
+ setTop: formData.setTop ? 'A' : 'I',
|
|
|
+ statusText: formData.statusText || ''
|
|
|
+ }
|
|
|
+ return api.create(apiData)
|
|
|
+ },
|
|
|
doDelete: api.delete,
|
|
|
- doUpdate: api.update,
|
|
|
+ doUpdate: (formData) => {
|
|
|
+ // 格式化展览日期
|
|
|
+ let openTime = ''
|
|
|
+ if (formData.dateRange && formData.dateRange.length === 2) {
|
|
|
+ const startDate = new Date(formData.dateRange[0]).toISOString().split('T')[0]
|
|
|
+ const endDate = new Date(formData.dateRange[1]).toISOString().split('T')[0]
|
|
|
+ openTime = `${startDate} - ${endDate}`
|
|
|
+ }
|
|
|
+
|
|
|
+ const apiData = {
|
|
|
+ id: formData.id,
|
|
|
+ address: formData.address || '',
|
|
|
+ artistIdList: Array.isArray(formData.artistId) ? formData.artistId : (formData.artistId ? [formData.artistId] : []),
|
|
|
+ city: formData.city || '',
|
|
|
+ description: formData.description || '',
|
|
|
+ imageUrl: formData.coverImageUrl || '',
|
|
|
+ latitude: formData.latitude || 0,
|
|
|
+ longitude: formData.longitude || 0,
|
|
|
+ name: formData.name || '',
|
|
|
+ online: 0,
|
|
|
+ openTime: openTime,
|
|
|
+ openTimeDetail: formData.openTimeDetail || '',
|
|
|
+ pavilionId: formData.pavilionId || 0,
|
|
|
+ setHot: formData.hot ? 1 : 0,
|
|
|
+ setTop: formData.setTop ? 'A' : 'I',
|
|
|
+ statusText: formData.statusText || ''
|
|
|
+ }
|
|
|
+ return api.update(apiData)
|
|
|
+ },
|
|
|
refresh: () => $table.value?.handleSearch(),
|
|
|
})
|
|
|
|
|
|
+// 重写handleAdd方法,切换到表单视图
|
|
|
+function handleAdd() {
|
|
|
+ // 重置表单数据
|
|
|
+ Object.assign(modalForm, {
|
|
|
+ display: true,
|
|
|
+ setTop: false,
|
|
|
+ isBomb: false,
|
|
|
+ hot: false,
|
|
|
+ dateRange: null,
|
|
|
+ startTime: null,
|
|
|
+ endTime: null,
|
|
|
+ statusText: '',
|
|
|
+ artistId: [],
|
|
|
+ address: '',
|
|
|
+ description: '',
|
|
|
+ coverImageUrl: '',
|
|
|
+ shareImageUrl: '',
|
|
|
+ name: '',
|
|
|
+ pavilionId: null,
|
|
|
+ latitude: 0,
|
|
|
+ longitude: 0,
|
|
|
+ city: ''
|
|
|
+ })
|
|
|
+
|
|
|
+ // 设置为新增模式
|
|
|
+ modalAction.value = 'add'
|
|
|
+
|
|
|
+ // 清空图片列表
|
|
|
+ imgList.value = {
|
|
|
+ cover: [],
|
|
|
+ share: []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新加载选项
|
|
|
+ loadPavilionOptions()
|
|
|
+ loadArtistOptions()
|
|
|
+
|
|
|
+ // 切换到表单视图
|
|
|
+ showAddEditForm.value = true
|
|
|
+
|
|
|
+ // 延迟初始化地图,确保DOM已渲染
|
|
|
+ setTimeout(() => {
|
|
|
+ initMap()
|
|
|
+ }, 800)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理表单取消,返回表格视图
|
|
|
+function handleFormCancel() {
|
|
|
+ showAddEditForm.value = false
|
|
|
+ // 刷新列表数据
|
|
|
+ nextTick(() => {
|
|
|
+ $table.value?.handleSearch()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 处理编辑操作
|
|
|
+function handleEdit(row) {
|
|
|
+ handleOpen({ action: 'edit', title: '编辑展会', row })
|
|
|
+}
|
|
|
+
|
|
|
+// 重写handleOpen方法,处理编辑时的数据获取和回显
|
|
|
+async function handleOpen(options) {
|
|
|
+ // 重新加载选项
|
|
|
+ loadPavilionOptions()
|
|
|
+ loadArtistOptions()
|
|
|
+
|
|
|
+ // 切换到表单视图
|
|
|
+ showAddEditForm.value = true
|
|
|
+
|
|
|
+ // 延迟初始化地图,确保弹窗DOM已渲染
|
|
|
+ setTimeout(() => {
|
|
|
+ initMap()
|
|
|
+ }, 800)
|
|
|
+
|
|
|
+ // 清空图片列表
|
|
|
+ imgList.value = {
|
|
|
+ cover: [],
|
|
|
+ share: []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是编辑模式,调用接口获取展会详情
|
|
|
+ if (options.action === 'edit' && options.row && options.row.id) {
|
|
|
+ try {
|
|
|
+ const { data: detailData } = await api.getById(options.row.id)
|
|
|
+ if (detailData) {
|
|
|
+ // 回显封面图
|
|
|
+ if (detailData.imageUrl) {
|
|
|
+ imgList.value.cover = [{
|
|
|
+ url: detailData.imageUrl,
|
|
|
+ remoteUrl: detailData.imageUrl
|
|
|
+ }]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果详情中有pavilion信息,将其合并到展馆选项列表中
|
|
|
+ let pavilionId = ''
|
|
|
+ if (detailData.pavilion) {
|
|
|
+ const existingPavilion = pavilionOptions.value.find(p => p.id === detailData.pavilion.id)
|
|
|
+ if (!existingPavilion) {
|
|
|
+ pavilionOptions.value.unshift(detailData.pavilion)
|
|
|
+ }
|
|
|
+ pavilionId = detailData.pavilion.id
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果详情中有artistList信息,将其合并到艺术家选项列表中
|
|
|
+ let artistId = []
|
|
|
+ if (detailData.artistList && Array.isArray(detailData.artistList)) {
|
|
|
+ detailData.artistList.forEach(artist => {
|
|
|
+ const existingArtist = artistOptions.value.find(a => a.id === artist.id)
|
|
|
+ if (!existingArtist) {
|
|
|
+ artistOptions.value.unshift(artist)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ artistId = detailData.artistList.map(artist => artist.id)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理日期范围回显
|
|
|
+ let dateRange = null
|
|
|
+ if (detailData.openTime && typeof detailData.openTime === 'string') {
|
|
|
+ const dateRangeParts = detailData.openTime.split(' - ')
|
|
|
+ if (dateRangeParts.length === 2) {
|
|
|
+ const startDate = new Date(dateRangeParts[0])
|
|
|
+ const endDate = new Date(dateRangeParts[1])
|
|
|
+ if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) {
|
|
|
+ dateRange = [startDate.getTime(), endDate.getTime()]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新options中的row数据为接口返回的完整数据
|
|
|
+ options.row = {
|
|
|
+ ...options.row,
|
|
|
+ id: detailData.id,
|
|
|
+ name: detailData.name || '',
|
|
|
+ dateRange: dateRange,
|
|
|
+ startTime: detailData.startTime || null,
|
|
|
+ endTime: detailData.endTime || null,
|
|
|
+ statusText: detailData.statusText || '',
|
|
|
+ pavilionId: pavilionId,
|
|
|
+ artistId: artistId,
|
|
|
+ address: detailData.address || '',
|
|
|
+ description: detailData.description || '',
|
|
|
+ coverImageUrl: detailData.imageUrl || '',
|
|
|
+ shareImageUrl: detailData.relayUrl || '',
|
|
|
+ display: detailData.display === 1,
|
|
|
+ setTop: detailData.setTop === 'A',
|
|
|
+ isBomb: detailData.isBomb === 1,
|
|
|
+ hot: detailData.hot === 1,
|
|
|
+ latitude: detailData.latitude || 0,
|
|
|
+ longitude: detailData.longitude || 0,
|
|
|
+ city: detailData.city || ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取展会详情失败:', error)
|
|
|
+ $message.error('获取展会详情失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用原始的handleOpen方法
|
|
|
+ originalHandleOpen(options)
|
|
|
+}
|
|
|
+
|
|
|
const columns = [
|
|
|
{
|
|
|
title: 'ID',
|
|
|
@@ -159,44 +669,52 @@ const columns = [
|
|
|
width: 80,
|
|
|
},
|
|
|
{
|
|
|
- title: '展览名称',
|
|
|
+ title: '图片',
|
|
|
+ key: 'imageUrl',
|
|
|
+ width: 120,
|
|
|
+ align: 'center',
|
|
|
+ render: (row) => {
|
|
|
+ const imageUrl = row.imageUrl && row.imageUrl.startsWith('http')
|
|
|
+ ? row.imageUrl
|
|
|
+ : `${import.meta.env.VITE_COS_BASE_URL}/${row.imageUrl}`
|
|
|
+ return h(NImage, {
|
|
|
+ width: 80,
|
|
|
+ height: 60,
|
|
|
+ src: imageUrl,
|
|
|
+ style: 'object-fit: cover;',
|
|
|
+ })
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '展会名称',
|
|
|
key: 'name',
|
|
|
- width: 150,
|
|
|
+ width: 270,
|
|
|
ellipsis: { tooltip: true },
|
|
|
},
|
|
|
{
|
|
|
- title: '展览状态',
|
|
|
- key: 'status',
|
|
|
- width: 100,
|
|
|
- render: ({ status }) => {
|
|
|
- return h(
|
|
|
- NTag,
|
|
|
- { type: status === '已上架' ? 'success' : 'warning' },
|
|
|
- { default: () => status },
|
|
|
- )
|
|
|
- },
|
|
|
+ title: '展会状态',
|
|
|
+ key: 'statusText',
|
|
|
+ width: 100
|
|
|
},
|
|
|
{
|
|
|
title: '展览时间',
|
|
|
- key: 'exhibitionTime',
|
|
|
+ key: 'openTime',
|
|
|
width: 200,
|
|
|
- render(row) {
|
|
|
- if (row.exhibitionTime && row.exhibitionTime.length === 2) {
|
|
|
- return `${formatDateTime(row.exhibitionTime[0], 'YYYY.MM.DD')} - ${formatDateTime(row.exhibitionTime[1], 'YYYY.MM.DD')}`
|
|
|
- }
|
|
|
- return '-'
|
|
|
- }
|
|
|
+ ellipsis: { tooltip: true },
|
|
|
},
|
|
|
{
|
|
|
title: '展馆',
|
|
|
- key: 'museum',
|
|
|
+ key: 'pavilionName',
|
|
|
width: 200,
|
|
|
ellipsis: { tooltip: true },
|
|
|
},
|
|
|
{
|
|
|
- title: '展览资源',
|
|
|
- key: 'resource',
|
|
|
- width: 100,
|
|
|
+ title: '是否置顶',
|
|
|
+ key: 'setTop',
|
|
|
+ width: 120,
|
|
|
+ render: ({ setTop }) => {
|
|
|
+ return setTop == 'I' ? '是' : '否'
|
|
|
+ },
|
|
|
},
|
|
|
{
|
|
|
title: '操作',
|
|
|
@@ -212,7 +730,7 @@ const columns = [
|
|
|
{
|
|
|
size: 'small',
|
|
|
type: 'primary',
|
|
|
- onClick: () => handleOpen({ action: 'edit', title: '编辑展览', row }),
|
|
|
+ onClick: () => handleEdit(row),
|
|
|
},
|
|
|
{
|
|
|
default: () => '编辑',
|
|
|
@@ -237,17 +755,17 @@ const columns = [
|
|
|
},
|
|
|
]
|
|
|
|
|
|
-async function handleEnable(row) {
|
|
|
- row.enableLoading = true
|
|
|
+async function handleOnline(row) {
|
|
|
+ row.onlineLoading = true
|
|
|
try {
|
|
|
- await api.update({ id: row.id, status: row.status === '已上架' ? '待审核' : '已上架' })
|
|
|
- row.enableLoading = false
|
|
|
+ await api.update({ id: row.id})
|
|
|
+ row.onlineLoading = false
|
|
|
$message.success('操作成功')
|
|
|
$table.value?.handleSearch()
|
|
|
}
|
|
|
catch (error) {
|
|
|
console.error(error)
|
|
|
- row.enableLoading = false
|
|
|
+ row.onlineLoading = false
|
|
|
}
|
|
|
}
|
|
|
</script>
|