cloudroam
2025-06-26 03fbf454453daf7b3a45064ea781cf4bdcc76832
add 景点管理
已重命名1个文件
已添加2个文件
692 ■■■■■ 文件已修改
pages/film/film-location.vue 403 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/film/filmset.vue 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/goods/film-category-list.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>
pages/film/filmset.vue
文件名从 pages/content/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"
        />
      );
    }
  },
}
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>