From 03fbf454453daf7b3a45064ea781cf4bdcc76832 Mon Sep 17 00:00:00 2001
From: cloudroam <cloudroam>
Date: 星期四, 26 六月 2025 15:00:32 +0800
Subject: [PATCH] add 景点管理

---
 pages/film/filmset.vue             |  124 +++++++++
 pages/film/film-location.vue       |  403 +++++++++++++++++++++++++++++++++
 pages/goods/film-category-list.vue |  165 +++++++++++++
 3 files changed, 685 insertions(+), 7 deletions(-)

diff --git a/pages/film/film-location.vue b/pages/film/film-location.vue
new file mode 100644
index 0000000..3ef7d38
--- /dev/null
+++ b/pages/film/film-location.vue
@@ -0,0 +1,403 @@
+<template>
+  <el-bus-crud ref="crud" v-bind="tableConfig"/>
+</template>
+
+<script>
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+
+dayjs.locale('zh-cn')
+export default {
+  data() {
+    return {
+      tableConfig: {
+        url: 'flower/api/filmLocation/list',
+        newUrl: 'flower/api/filmLocation/new',
+        editUrl: 'flower/api/filmLocation/edit',
+        deleteUrl: 'flower/api/filmLocation/delete',
+        dialogNeedRequest: true,
+        persistSelection: true,
+        columns: [
+          {type: 'selection'},
+          {label: '拍摄地点名称', prop: 'locationName', minWidth: 120},
+          {label: '拍摄地点图片', prop: 'locationUrl', minWidth: 120},
+          {label: '省', prop: 'province', minWidth: 120},
+          {label: '市', prop: 'city', minWidth: 150},
+          {label: '区', prop: 'region'},
+          {label: '详细地址', prop: 'address', minWidth: 150},
+          {label: '纬度坐标', prop: 'gpsLat', minWidth: 150},
+          {label: '经度坐标', prop: 'gpsLng', minWidth: 300},
+          {label: '场景类型', prop: 'sceneType'},
+          {label: '经典画面描述', prop: 'classicScene', minWidth: 400},
+          {label: '开放参观', prop: 'isOpenVisitStr', minWidth: 200},
+          {label: '参观提示', prop: 'visitInfo', minWidth: 120},
+          {label: '地标性建筑描述', prop: 'landmarkDesc'},
+          {label: '停车场信息', prop: 'parkingInfo', minWidth: 80},
+          {label: '周边设施描述', prop: 'surroundingFacilities', minWidth: 80},
+          {label: 'ARURL', prop: 'arEntry', minWidth: 80},
+          {label: '打卡记录量', prop: 'checkinCount', minWidth: 80},
+          // { label: '游客实拍图', prop: 'visitorPhotos', minWidth: 80  },
+          {label: '景点热度', prop: 'locationWeight', minWidth: 80},
+          {label: '运营权重', prop: 'operationWeight', minWidth: 80},
+          {
+            label: '启用/禁用',
+            formatter: (row) => (
+              <el-switch
+                value={row.isEnabled}
+                onChange={this.onEnabledChange.bind(this, row)}
+              ></el-switch>
+            ),
+            minWidth: 120,
+            fixed: 'right',
+          },
+        ],
+        searchForm: [
+          {
+            type: 'row',
+            items: [
+              {label: '拍摄地点名称', id: 'locationName', type: 'input'},
+              {
+                label: '启用/禁用',
+                id: 'isEnabled',
+                type: 'bus-select-dict',
+                default: '1',
+                el: {
+                  code: 'USER_ENABLED_OR_DISABLED',
+                  clearable: true,
+                  style: 'width:100%',
+                },
+              },
+            ],
+          },
+        ],
+        form: [
+          {
+            label: '拍摄地点名称:',
+            id: 'locationName',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入拍摄地点名称',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '封面图片:',
+            id: 'locationUrl',
+            type: 'bus-upload',
+            el: {
+              listType: 'picture-card',
+              limitSize: 2,
+              limit: 1,
+              tipText: '大小不超过2M',
+            },
+          },
+          {
+            label: '省:',
+            id: 'province',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入省',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '市:',
+            id: 'city',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入市',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '区:',
+            id: 'region',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入区',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '详细地址:',
+            id: 'address',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输详细地址',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '纬度坐标:',
+            id: 'gpsLat',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入纬度坐标',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '经度坐标:',
+            id: 'gpsLng',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入经度坐标',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '场景类型:',
+            id: 'sceneType',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入场景类型',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '经典画面描述:',
+            id: 'classicScene',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入经典画面描述',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '开放参观:',
+            id: 'isOpenVisit',
+            type: 'bus-select-dict',
+            el: {
+              code: 'IS_VISITOR',
+              style: 'width:100%',
+              clearable: true,
+            },
+            rules: {
+              required: true,
+              message: '请选择参观类型',
+            },
+          },
+          {
+            label: '参观提示:',
+            id: 'visitInfo',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入参观提示',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '地标性建筑描述:',
+            id: 'landmarkDesc',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入地标性建筑描述',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '停车场信息:',
+            id: 'parkingInfo',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入停车场信息',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '周边设施描述:',
+            id: 'surroundingFacilities',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入周边设施描述',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: 'ARURL:',
+            id: 'arEntry',
+            type: 'input',
+            rules: {
+              required: false,
+              message: '请输入ARURL',
+              trigger: 'blur',
+            },
+          },
+          // {
+          //   label: '取景地内容:',
+          //   id: 'filmContent',
+          //   component: 'base-editor',
+          //   richText: true,
+          //   rules: { required: true, message: '请输入取景地内容', trigger: 'blur' },
+          // },
+          {
+            label: '打卡记录量:',
+            id: 'checkinCount',
+            type: 'input',
+            rules: {
+              required: false,
+              message: '请输入打卡记录量',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '景点热度:',
+            id: 'locationWeight',
+            type: 'input',
+            rules: {
+              required: false,
+              message: '请输入景点热度',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '运营权重:',
+            id: 'operationWeight',
+            type: 'input',
+            rules: {
+              required: false,
+              message: '请输入运营权重',
+              trigger: 'blur',
+            },
+          },
+        ],
+        extraButtons: [
+          {
+            text: '清除热度',
+            atClick: async (row) => {
+              try {
+                await this.$elBusUtil.confirm(`确定要清除热度吗?`)
+                const {code} = await this.$elBusHttp.request(
+                  'flower/api/filmLocation/setDown',
+                  {params: {id: row.id}}
+                )
+                if (code === 0) {
+                  this.$message.success(`清除热度成功`)
+                }
+              } catch (e) {
+                return false
+              }
+            },
+          },
+        ],
+        headerButtons: [
+          {
+            text: '合并',
+            type: 'primary',
+            disabled: (selected) =>
+              selected.filter((item) => item.isEnabled)
+                .length === 0,
+            atClick: async (selected) => {
+              const selectedNotice = selected.filter(
+                (item) => item.isEnabled
+              )
+              try {
+                await this.$elBusUtil.confirm(
+                  `确定要合并这${selectedNotice.length -1}个景点到第一个吗?`
+                )
+                const {code} = await this.$elBusHttp.request(
+                  'flower/api/filmLocation/merge/batch',
+                  {
+                    method: 'post',
+                    data: {
+                      ids: selected.map((item) => item.id),
+                    },
+                  }
+                )
+                if (code === 0) {
+                  this.$message.success('操作成功')
+                  this.$refs.crud.clearSelection()
+                }
+              } catch (e) {
+                return false
+              }
+            },
+          },
+          {
+            text: '批量删除',
+            type: 'danger',
+            disabled: (selected) => selected.length === 0,
+            atClick: async (selected) => {
+              try {
+                await this.$elBusUtil.confirm(
+                  `确定要批量删除这${selected.length}个片场内容吗?`
+                )
+                const {code} = await this.$elBusHttp.request(
+                  'flower/api/filmLocation/delete/batch',
+                  {
+                    method: 'post',
+                    data: {
+                      ids: selected.map((item) => item.id),
+                    },
+                  }
+                )
+                if (code === 0) {
+                  this.$message.success('操作成功')
+                  this.$refs.crud.clearSelection()
+                }
+              } catch (e) {
+                return false
+              }
+            },
+          },
+        ],
+      },
+    }
+  },
+  head() {
+    return {
+      title: '影视景点管理',
+    }
+  },
+  methods: {
+    onEnabledChange(row, e) {
+      const url = 'flower/api/filmLocation/isEnable'
+      const text = e ? '启用' : '禁用'
+      this.$elBusUtil
+        .confirm(`确定要${text}这个影视景点吗?`)
+        .then(async () => {
+          const { code } = await this.$elBusHttp.request(url, {
+            params: {
+              id: row.id,
+            },
+          })
+          if (code === 0) {
+            this.$message.success(`${text}成功`)
+            this.$refs.crud.getList()
+          }
+        })
+        .catch(() => { })
+    },
+
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .el-upload {
+    &-list__item {
+      width: 345px;
+      height: 128px;
+    }
+
+    &.el-upload--picture-card {
+      width: 345px;
+      height: 128px;
+    }
+  }
+}
+</style>
diff --git a/pages/content/filmset.vue b/pages/film/filmset.vue
similarity index 77%
rename from pages/content/filmset.vue
rename to pages/film/filmset.vue
index 30f9837..561cc69 100644
--- a/pages/content/filmset.vue
+++ b/pages/film/filmset.vue
@@ -189,9 +189,70 @@
               limitSize: 2,
               limit: 1,
               tipText: '大小不超过2M',
-              valueType: 'string',
+              // valueType: 'string',
             },
             forceDisabled: true,
+            inputFormat: (row) => {
+              if ('coverUrl' in row) {
+                if (typeof row.coverUrl === 'string' && row.coverUrl) {
+                  let url = row.coverUrl
+                  if (url.includes('doubanio')) {
+                    url = `https://images.weserv.nl/?url=${encodeURIComponent(url)}`
+                  }
+                  return [{ url }]
+                }
+                if (Array.isArray(row.coverUrl)) {
+                  return row.coverUrl
+                }
+                return []
+              }
+            },
+            outputFormat: (fileList) => {
+              if (Array.isArray(fileList) && fileList.length > 0) {
+                let url = fileList[0].url
+                const proxyPrefix = 'https://images.weserv.nl/?url='
+                if (url.startsWith(proxyPrefix)) {
+                  url = decodeURIComponent(url.replace(proxyPrefix, ''))
+                }
+                return url
+              }
+              return ''
+            }
+            // outputFormat: (fileList) => {
+            //   if (Array.isArray(fileList) && fileList.length > 0) {
+            //     return fileList[0].url
+            //   }
+            //   return ''
+            // }
+            // inputFormat: (row) => {
+            //   if ('coverUrl' in row) {
+            //     // 封面图片是字符串
+            //     if (typeof row.coverUrl === 'string' && row.coverUrl) {
+            //       let url = row.coverUrl
+            //       console.log("我进来了",url)
+            //       // 代理 doubanio 图片
+            //       if (url.includes('doubanio')) {
+            //         url = `https://images.weserv.nl/?url=${encodeURIComponent(url)}`
+            //         console.log("我处理了",url)
+            //       }
+            //       return [{ url }]
+            //     }
+            //     // 已经是数组
+            //     if (Array.isArray(row.coverUrl)) {
+            //       // 这里也可以做一遍代理处理,防止后端返回的数组里有 doubanio
+            //       return row.coverUrl.map(item => {
+            //         let url = item.url || item
+            //         if (url.includes('doubanio')) {
+            //           url = `https://images.weserv.nl/?url=${encodeURIComponent(url)}`
+            //         }
+            //         return { ...item, url }
+            //       })
+            //     }
+            //     return []
+            //   }
+            // },
+            // formatter: (url) => this.formatImageUrl(url)
+
           },
           {
             label: '封面图片描述:',
@@ -316,6 +377,24 @@
                 await this.$elBusUtil.confirm(`确定要${action}吗?`)
                 const { code } = await this.$elBusHttp.request(
                   'flower/api/filmWorks/changeStatus',
+                  { params: { id: row.id } }
+                )
+                if (code === 0) {
+                  this.$message.success(`${action}成功`)
+                }
+              } catch (e) {
+                return false
+              }
+            },
+          },
+          {
+            text: (row) => ( row.status === 'pending_create' ? '取消生成' : '重新生成'),
+            atClick: async (row) => {
+              const action = row.status === 'pending_create'  ? '取消生成' : '重新生成'
+              try {
+                await this.$elBusUtil.confirm(`确定要${action}吗?`)
+                const { code } = await this.$elBusHttp.request(
+                  'flower/api/filmWorks/changeCreateStatus',
                   { params: { id: row.id } }
                 )
                 if (code === 0) {
@@ -495,13 +574,44 @@
     }
   },
   methods: {
-    formatterImage(row) {
-      if (row.coverUrl) {
-        // 使用第三方镜像服务(示例)
-        const proxyUrl = `https://images.weserv.nl/?url=${encodeURIComponent(row.coverUrl)}`;
-        return <el-bus-image src={proxyUrl} preview-src-list={[proxyUrl]} style="width:150px" />
+    // formatterImage(row) {
+    //   if (row.coverUrl) {
+    //     if (row.coverUrl.includes('doubanio')) {
+    //       const proxyUrl = `https://images.weserv.nl/?url=${encodeURIComponent(row.coverUrl)}`;
+    //       return <el-bus-image src={proxyUrl} preview-src-list={[proxyUrl]} style="width:150px" />
+    //     } else {
+    //       return <el-bus-image src={row.coverUrl} preview-src-list={[row.coverUrl]} style="width:150px" />
+    //     }
+    //   }
+    //   return '无封面';
+    // }
+    formatImageUrl(url) {
+      if (!url) return '';
+      if (url.includes('doubanio')) {
+        return `https://images.weserv.nl/?url=${encodeURIComponent(url)}`;
       }
-      return '无封面';
+      return url;
+    },
+    // formatterImage(row) {
+    //   if (!row.coverUrl) return '无封面';
+    //   const displayUrl = this.formatImageUrl(row.coverUrl);
+    //   return <el-bus-image src={displayUrl} preview-src-list={[displayUrl]} style="width:150px" />;
+    // }
+    formatterImage(row) {
+      if (!row.coverUrl) return '无封面';
+
+      // 统一处理路径:doubanio路径使用代理,其他直接使用
+      const displayUrl = row.coverUrl.includes('doubanio')
+        ? `https://images.weserv.nl/?url=${encodeURIComponent(row.coverUrl)}`
+        : row.coverUrl;
+
+      return (
+        <el-bus-image
+          src={displayUrl}
+          preview-src-list={[displayUrl]}
+          style="width:150px"
+        />
+      );
     }
   },
 }
diff --git a/pages/goods/film-category-list.vue b/pages/goods/film-category-list.vue
new file mode 100644
index 0000000..9b374fe
--- /dev/null
+++ b/pages/goods/film-category-list.vue
@@ -0,0 +1,165 @@
+<template>
+  <el-bus-crud ref="crud" v-bind="tableConfig" />
+</template>
+
+<script>
+import cloneDeep from 'lodash.clonedeep'
+import { getSortConfig } from '@/utils/form-item-config'
+export default {
+  data() {
+    return {
+      originalList: [],
+      expandIds: [],
+      tableConfig: {
+        url: 'flower/api/film/category/tree',
+        hasPagination: false,
+        saveQuery: false,
+        isTree: true,
+        hasView: false,
+        canNewChild: (row) => !row.parentId,
+        dialogAttrs: {
+          width: '70%',
+        },
+        afterRequest: (list) => {
+          this.originalList = cloneDeep(list)
+          this.expandIds = this.expandIds.filter((i) =>
+            list.find((item) => item.id === i)
+          )
+          return list.map((i) => ({
+            ...i,
+            childrenCount: Array.isArray(i.children) ? i.children.length : 0,
+            children: this.expandIds.includes(i.id)
+              ? i.children
+              : i.children.slice(0, 1),
+          }))
+        },
+        tableEventHandlers: {
+          expandChange: (row, expand) => {
+            if (expand) {
+              if (!this.expandIds.includes(row.id)) {
+                this.expandIds.push(row.id)
+              }
+              row.children =
+                this.originalList.find((i) => i.id === row.id)?.children || []
+            } else {
+              const index = this.expandIds.indexOf(row.id)
+              if (index !== -1) {
+                this.expandIds.splice(index, 1)
+              }
+            }
+          },
+        },
+        beforeOpen(row, isNew) {
+          if (isNew && row.name) {
+            row.parentName = row.name
+          }
+          if (!isNew && !row.parentId && row.parentName) {
+            row.parentName = ''
+          }
+        },
+        extraParentKeys: ['parentName', 'levelLimit'],
+        tableAttrs: {
+          rowKey: 'id',
+        },
+        columns: [
+          { label: '板块名称', prop: 'name' },
+          {
+            label: '板块图片',
+            formatter: (row) => (
+              <el-bus-image
+                style="width:50px;height:50px"
+                lazy={true}
+                src={row.imageUrl}
+              />
+            ),
+          },
+          { label: '排序', prop: 'sortBy' },
+          { label: '子板块数量', formatter: (row) => row.childrenCount },
+          {
+            label: '是否显示',
+            formatter: (row) => (
+              <el-switch
+                value={row.shown}
+                onChange={this.onShownChange.bind(this, row)}
+              ></el-switch>
+            ),
+          },
+        ],
+        searchForm: [
+          {
+            type: 'row',
+            items: [{ label: '板块名称', id: 'name', type: 'input' }],
+          },
+        ],
+        form: [
+          {
+            label: '上级板块:',
+            id: 'parentName',
+            type: 'input',
+            readonly: true,
+            hidden: (row) => !row.parentName,
+          },
+          {
+            label: '板块名称:',
+            id: 'name',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入板块名称',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '板块图片:',
+            id: 'imageUrl',
+            type: 'bus-upload',
+            el: {
+              listType: 'picture-card',
+              limit: 1,
+              limitSize: 2,
+              tipText: '建议上传164*164的图片,大小不超过2M',
+              valueType: 'string',
+            },
+            rules: { required: false, message: '请上传板块图片' },
+          },
+          {
+            ...getSortConfig(),
+          },
+        ],
+      },
+    }
+  },
+  head() {
+    return {
+      title: '片场板块管理',
+    }
+  },
+  methods: {
+    onShownChange(row, e) {
+      const url = e
+        ? 'flower/api/film/category/tree/shown'
+        : 'film/api/film/category/tree/hidden'
+      const text = e ? '显示' : '隐藏'
+      this.$elBusUtil
+        .confirm(`确定要${text}这个板块吗?`)
+        .then(async () => {
+          const { code } = await this.$elBusHttp.request(url, {
+            params: {
+              id: row.id,
+            },
+          })
+          if (code === 0) {
+            this.$message.success(`${text}成功`)
+            this.$refs.crud.getList()
+          }
+        })
+        .catch(() => {})
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.category-list {
+}
+</style>

--
Gitblit v1.9.3