瀏覽代碼

no message

DESKTOP-SVI9JE1\muzen 1 年之前
父節點
當前提交
776544857f

+ 19 - 1
src/api/business/list.js

@@ -27,7 +27,7 @@ export function remove(data) {
 }
 
 // 企业筛选
-export function options(data) {
+export function businessList(data) {
   return request({
     url: `/organization/organization/selectList`,
     method: 'post',
@@ -69,4 +69,22 @@ export function reset(data){
     method: 'put',
     data
   })
+}
+
+// 所属门店
+export function storeSelect(data) {
+  return request({
+    url: `/store/store/list`,
+    method: 'post',
+    data
+  })
+}
+
+// 设备列表
+export function deviceList(data) {
+  return request({
+    url: `/device/version/clientTypeList`,
+    method: 'post',
+    data
+  })
 }

+ 41 - 14
src/api/content/scene.js

@@ -1,36 +1,63 @@
-import request from '@/utils/request'
+import request from "@/utils/request";
 
 // 列表
 export function list(data) {
   return request({
     url: `/radio/tTakeList/takePage`,
-    method: 'post',
-    data
-  })
+    method: "post",
+    data,
+  });
 }
 
 // 新增
 export function submit(data) {
   return request({
     url: `/radio/tTakeList/editTakeList`,
-    method: 'post',
-    data
-  })
+    method: "post",
+    data,
+  });
 }
 
 // 详情
 export function detail(query) {
   return request({
     url: `/radio/tTakeList/queryDetailById`,
-    method: 'get',
-    params: query
-  })
+    method: "get",
+    params: query,
+  });
 }
 
-// 删除
+// 删除场景合集
 export function remove(id) {
   return request({
     url: `/radio/tTakeList/delete/${id}`,
-    method: 'delete'
-  })
-}
+    method: "delete",
+  });
+}
+
+// 时间轴
+export function timeList(data) {
+  return request({
+    url: `/radio/tPresetContent/list`,
+    method: "post",
+    data,
+  });
+}
+
+// 提交预设
+export function timeSubmit(data) {
+  return request({
+    url: `/radio/tPresetContent/edit/scene`,
+    method: "post",
+    data,
+  });
+}
+
+// 删除场景预设
+export function removeScene(query) {
+  return request({
+    url: `/radio/tPresetContent/deleteById`,
+    method: "get",
+    params: query,
+  });
+}

+ 10 - 0
src/api/content/setting.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 音频列表
+export function audioList(data) {
+  return request({
+    url: `/radio/tContentPlay/audio/page`,
+    method: 'post',
+    data
+  })
+}

+ 55 - 0
src/api/store/devices.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request' 
+
+// 列表
+export function list (data) {
+  return request({
+    url: `/store/device/page`,
+    method: 'post',
+    data
+  })
+}
+
+// 详情
+export function detail(data) {
+  return request({
+    url: `/store/device/get`,
+    method: 'post',
+    data
+  })
+}
+
+// 新增
+export function submit(data) {
+  return request({
+    url: `/store/device/add`,
+    method: 'post',
+    data
+  })
+}
+
+// 编辑
+export function edit(data){
+  return request({
+    url: `/store/device/edit`,
+    method: 'put',
+    data
+  })
+}
+
+// 状态
+export function change(data) {
+  return request({
+    url: `/store/device/changeStatus`,
+    method: 'put',
+    data
+  })
+}
+
+// 删除
+export function remove(data) {
+  return request({
+    url: `/store/device/remove`,
+    method: 'delete',
+    data
+  })
+}

+ 55 - 0
src/api/store/group.js

@@ -0,0 +1,55 @@
+import request from "@/utils/request";
+
+// 列表
+export function list(data) {
+  return request({
+    url: `/store/group/page`,
+    method: "post",
+    data,
+  });
+}
+
+// 新增
+export function submit(data) {
+  return request({
+    url: `/store/group/add`,
+    method: "post",
+    data,
+  });
+}
+
+// 编辑
+export function edit(data) {
+  return request({
+    url: `/store/group/edit`,
+    method: 'put',
+    data
+  })
+}
+
+// 详情
+export function detail(data) {
+  return request({
+    url: `/store/group/get`,
+    method: "post",
+    data
+  });
+}
+
+// 删除
+export function remove(data){
+  return request({
+    url: `/store/group/remove`,
+    method: 'delete',
+    data
+  })
+}
+
+// 筛选
+export function groupOptions(data) {
+  return request({
+    url: `/store/group/list`,
+    method: 'post',
+    data
+  })
+}

+ 54 - 0
src/api/store/list.js

@@ -0,0 +1,54 @@
+import request from "@/utils/request";
+
+// 列表
+export function list(data) {
+  return request({
+    url: `/store/store/page`,
+    method: "post",
+    data,
+  });
+}
+
+// 省、市、区
+export function areaOptions() {
+  return request({
+    url: `/common/query/province/city/area`,
+    method: "get",
+  });
+}
+
+// 详情
+export function detail(data) {
+  return request({
+    url: `/store/store/get`,
+    method: "post",
+    data,
+  });
+}
+
+// 新增
+export function submit(data) {
+  return request({
+    url: `/store/store/add`,
+    method: post,
+    data,
+  });
+}
+
+// 修改
+export function edit(data) {
+  return request({
+    url: `/store/store/edit`,
+    method: "put",
+    data,
+  });
+}
+
+// 删除
+export function remove(data) {
+  return request({
+    url: `/store/store/remove`,
+    method: "delete",
+    data,
+  });
+}

+ 9 - 0
src/api/system/user.js

@@ -133,3 +133,12 @@ export function deptTreeSelect() {
     method: 'get'
   })
 }
+
+// 用户信息下拉框
+export function userSelect(data) {
+  return request({
+    url: `/system/user/selectList`,
+    method: 'post',
+    data
+  })
+}

文件差異過大導致無法顯示
+ 16 - 0
src/assets/icons/svg/muzen.svg


+ 144 - 43
src/components/GanttChart/index.vue

@@ -1,37 +1,54 @@
 <template>
+  <el-form class="search" inline>
+    <el-form-item>
+      <el-select v-model="organizationIds" placeholder="请选择企业" style="width: 200px" filterable remote
+        :remote-method="businessRemote" remote-show-suffix>
+        <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name" />
+      </el-select>
+    </el-form-item>
+    <el-form-item>
+      <el-select v-model="storeId" placeholder="请选择门店" style="width: 200px" filterable remote
+        :remote-method="storeRemote" remote-show-suffix>
+        <el-option v-for="item in storeData.options" :key="item.id" :value="item.id" :label="item.name" />
+      </el-select>
+    </el-form-item>
+    <el-form-item style="float:right">
+      <el-button type="primary" icon="Plus" @click="getDetail()">新增预设</el-button>
+    </el-form-item>
+  </el-form>
   <div class="timer">
-    <div class="item" v-for="item in data.timeList" :key="item">
+    <div class="item" v-for="(item, index) in data.timeList" :key="item">
       <div class="value">
-        <span>{{ parseTime(item.timer, '{h}:{i}') }}</span>
+        <span>{{ item.time[0].slice(0, 5) }}</span>
         <span class="solid"></span>
       </div>
-      <draggable class="label_box" v-model="item.list" group="componentGroup" :item-key="item.value" :move="onMove"
-        @end="onEnd">
+      <draggable class="label_box" v-model="item.list" group="componentGroup" :item-key="index.toString()"
+        :move="onMove" @end="onEnd">
         <template #item="{ element, index }">
           <div @mousedown.stop style="width: 100%">
             <el-popover placement="bottom" trigger="click" width="300px" popper-class="popper"
               :popper-style="popperStyle" :hide-after="0">
               <div class="title">
-                <h2>双12活动内容</h2>
+                <h2>{{ element.takeName }}</h2>
                 <div style="display:flex; justify-content: space-between;">
-                  <span>1月23日22:00 - 23:00</span>
+                  <span>{{ element.listDate.startTime }} - {{ element.listDate.endTime }}</span>
                   <div style="display:flex; align-items: center">
                     <el-icon style="margin-right: 10px" @click="getDetail(element)">
                       <Edit />
                     </el-icon>
-                    <el-icon>
+                    <el-icon @click="getDelete(element)">
                       <Delete />
                     </el-icon>
                   </div>
                 </div>
               </div>
               <div class="main">
-                <span>当前门店:瑞幸中国</span>
-                <span>当前设备:猫2</span>
-                <span>当前状态:上架</span>
+                <span>当前门店:{{ storeData.options.find(i => i.id === element.storeId).name }}</span>
+                <span>当前设备:{{ element.deviceIds }}</span>
+                <span>当前状态:{{ proxy.selectDictLabel(sys_change_status, element.status) }}</span>
               </div>
               <template #reference>
-                <span class="label">{{ element.label }}</span>
+                <span class="label">{{ element.takeName }}</span>
               </template>
             </el-popover>
           </div>
@@ -42,44 +59,64 @@
 </template>
 
 <script setup>
+import { timeList, removeScene } from '@/api/content/scene'
+import { useBusinessSelect, useStoreSelect } from '@/hooks/index'
+
+const { storeData, getStore, storeRemote } = useStoreSelect()
+const { businessData, getBusiness, businessRemote } = useBusinessSelect(true)
+getBusiness()
+
 const { proxy } = getCurrentInstance();
+const { sys_change_status } = proxy.useDict("sys_change_status");
+
+const props = defineProps({
+  path: {
+    type: String,
+    default: null
+  }
+})
 
 const data = reactive({
   timeList: [
-    { value: '0', list: [{ value: 1, label: '双十一' }] },
-    { value: '1', list: [] },
-    { value: '2', list: [] },
-    { value: '3', list: [] },
-    { value: '4', list: [] },
-    { value: '5', list: [] },
-    { value: '6', list: [] },
-    { value: '7', list: [] },
-    { value: '8', list: [] },
-    { value: '9', list: [] },
-    { value: '10', list: [] },
-    { value: '11', list: [] },
-    { value: '12', list: [] },
-    { value: '13', list: [] },
-    { value: '14', list: [] },
-    { value: '15', list: [] },
-    { value: '16', list: [] },
-    { value: '17', list: [] },
-    { value: '18', list: [] },
-    { value: '19', list: [] },
-    { value: '20', list: [] },
-    { value: '21', list: [] },
-    { value: '22', list: [] },
-    { value: '23', list: [] },
-    { value: '24', list: [] }
-  ]
+    { time: ['00:00:00', '01:00:00'], list: [] },
+    { time: ['01:00:00', '02:00:00'], list: [] },
+    { time: ['02:00:00', '03:00:00'], list: [] },
+    { time: ['03:00:00', '04:00:00'], list: [] },
+    { time: ['04:00:00', '05:00:00'], list: [] },
+    { time: ['05:00:00', '06:00:00'], list: [] },
+    { time: ['06:00:00', '07:00:00'], list: [] },
+    { time: ['07:00:00', '08:00:00'], list: [] },
+    { time: ['08:00:00', '09:00:00'], list: [] },
+    { time: ['09:00:00', '10:00:00'], list: [] },
+    { time: ['10:00:00', '11:00:00'], list: [] },
+    { time: ['11:00:00', '12:00:00'], list: [] },
+    { time: ['12:00:00', '13:00:00'], list: [] },
+    { time: ['13:00:00', '14:00:00'], list: [] },
+    { time: ['14:00:00', '15:00:00'], list: [] },
+    { time: ['15:00:00', '16:00:00'], list: [] },
+    { time: ['16:00:00', '17:00:00'], list: [] },
+    { time: ['17:00:00', '18:00:00'], list: [] },
+    { time: ['18:00:00', '19:00:00'], list: [] },
+    { time: ['19:00:00', '20:00:00'], list: [] },
+    { time: ['20:00:00', '21:00:00'], list: [] },
+    { time: ['21:00:00', '22:00:00'], list: [] },
+    { time: ['22:00:00', '23:00:00'], list: [] },
+    { time: ['23:00:00', '00:00:00'], list: [] },
+    { time: ['00:00:00', '01:00:00'], list: [] }
+  ],
 })
 
+// 时间轴表单
+const organizationIds = ref(null)
+const storeId = ref(null)
+
 // 时间段
 function getTime() {
-  data.timeList.map(i => {
-    // i.list = []
-    i.timer = new Date(new Date(new Date().toLocaleDateString()).getTime() + i.value * 3600000).getTime()
-  })
+  for (let i = 0; i <= data.timeList.length - 1; i++) {
+    data.timeList[i].timer = []
+    data.timeList[i].timer[0] = proxy.hoursToSeconds(data.timeList[i].time[0])
+    data.timeList[i].timer[1] = proxy.hoursToSeconds(data.timeList[i].time[1])
+  }
 }
 getTime()
 
@@ -88,6 +125,30 @@ const popperStyle = ref({
   borderRadius: '10px'
 })
 
+// 时间轴
+function getTimeList() {
+  timeList({ storeId: storeId.value }).then(res => {
+    for (let i = 0; i < res.data.length; i++) {
+      for (let j = 0; j < res.data[i].listDate.length; j++) {
+        let start = proxy.hoursToSeconds(res.data[i].listDate[j].startTime)
+        let end = proxy.hoursToSeconds(res.data[i].listDate[j].endTime)
+        const index1 = data.timeList.findIndex(e => {
+          let st = e.timer[0]
+          let en = e.timer[1]
+          return st <= start && start <= en || st <= end && end <= en
+        })
+        data.timeList[index1].list.push({ ...res.data[i], listDate: res.data[i].listDate[j] })
+        const index2 = data.timeList.findIndex(e => {
+          let st = e.timer[0]
+          let en = e.timer[1]
+          return st >= start && en <= end || st >= end && en <= start
+        })
+        data.timeList[index2].list.push({ ...res.data[i], listDate: res.data[i].listDate[j] })
+      }
+    }
+  })
+}
+
 // 拖拽内容
 function onMove(params) {
   if (params.to.childElementCount < 1) {
@@ -111,10 +172,43 @@ function onEnd(params) {
   }
 }
 
-// 新增
+// 删除
+const getDelete = (row) => {
+  proxy.$modal.confirm(`是否删除预设名称为:${row.takeName}的数据?`).then(() => {
+    removeScene({ id: row.id }).then(res => {
+      if (res.code === 0) {
+        proxy.$modal.msgSuccess('删除成功!')
+        getTimeList()
+      }
+    })
+  }).catch(() => { })
+}
+
+watch(() => businessData.options, (val) => {
+  if (val) {
+    organizationIds.value = val[0].id
+  }
+})
+
+watch(organizationIds, (val) => {
+  if (val) {
+    storeData.form.tenantId = val
+    getStore()
+  }
+})
+
+watch(() => storeData.storeId, (val) => {
+  data.timeList.map(i => i.list = [])
+  storeId.value = val
+  if (val) {
+    getTimeList()
+  }
+})
+
+// 新增预设
 function getDetail(query) {
   proxy.$router.push({
-    path: `/content/setting/detail`,
+    path: props.path,
     query
   })
 }
@@ -126,6 +220,8 @@ function getDetail(query) {
   flex-direction: column;
   width: calc(100% - 50px);
   margin-left: 50px;
+  overflow-y: auto;
+  padding: 20px 20px 20px 50px;
 
   .item {
     width: 100%;
@@ -137,6 +233,7 @@ function getDetail(query) {
     align-items: center;
 
     .value {
+      width: 100px;
       margin-left: -50px;
 
       .solid {
@@ -193,4 +290,8 @@ function getDetail(query) {
     flex-direction: column;
   }
 }
+
+.search {
+  padding-left: 70px;
+}
 </style>

+ 24 - 0
src/directive/common/loadMore.js

@@ -0,0 +1,24 @@
+export default {
+  beforeMount(el, binding) {
+    const select_dom = document.querySelector(
+      `.${binding.arg} .el-select-dropdown__wrap`
+    );
+    function loadMore() {
+      const isBase = this.scrollHeight - this.scrollTop <= this.clientHeight;
+      if (isBase) {
+        binding.value && binding.value();
+      }
+    }
+    el.selectDomInfo = select_dom;
+    el.selectLoadMore = loadMore;
+    select_dom?.addEventListener("scroll", loadMore.bind(select_dom));
+  },
+
+  beforeUnmount(el) {
+    if (el.selectLoadMore) {
+      el.selectDomInfo.removeEventListener("scroll", el.selectLoadMore);
+      delete el.selectDomInfo;
+      delete el.selectLoadMore;
+    }
+  },
+};

+ 2 - 0
src/directive/index.js

@@ -1,9 +1,11 @@
 import hasRole from './permission/hasRole'
 import hasPermi from './permission/hasPermi'
 import copyText from './common/copyText'
+import loadMore from './common/loadMore'
 
 export default function directive(app){
   app.directive('hasRole', hasRole)
   app.directive('hasPermi', hasPermi)
   app.directive('copyText', copyText)
+  app.directive('loadMore', loadMore)
 }

+ 190 - 30
src/hooks/index.js

@@ -1,4 +1,4 @@
-import { ref, reactive } from "vue";
+import { ref, reactive, onMounted, onUnmounted } from "vue";
 
 // // 内容管理-标签分类
 import { list as contentClassList } from "@/api/content/class";
@@ -26,18 +26,13 @@ import { options as businessClassList } from "@/api/business/class";
 
 export function useBusinessClass() {
   const classOptions = ref([]);
-  function getClassOptions() {
+  const getClassOptions = () => {
     businessClassList().then((res) => {
       if (res.code === 0) {
-        res.data.map((i) => {
-          classOptions.value.push({
-            value: i.id,
-            label: i.name,
-          });
-        });
+        classOptions.value = res.data;
       }
     });
-  }
+  };
 
   // 挂载
   onMounted(() => getClassOptions());
@@ -47,37 +42,202 @@ export function useBusinessClass() {
   return { classOptions };
 }
 
-// // 企业管理-企业中心
-import { options as businessList } from "@/api/business/list";
+// 企业筛选
+import { businessList } from "@/api/business/list";
 
-export function useBusinessList(type) {
+export function useBusinessSelect(type) {
   /* type为筛选条件 */
-  const businessForm = ref({
-    pageNum: 1,
-    pageSize: 10,
+  const businessData = reactive({
+    form: {
+      pageNum: 1,
+      pageSize: 20,
+    },
+    options: [],
   });
-  const businessOptions = ref([]);
 
-  function getOptions() {
-    businessOptions.value = [];
-    businessList(businessForm).then((res) => {
+  const getBusiness = () => {
+    businessList(businessData.form).then((res) => {
       if (res.code === 0) {
-        res.data.forEach((i) => {
-          businessOptions.value.push({
-            value: i.id,
-            label: i.name,
-          });
-        });
+        businessData.options = res.data;
         if (type) {
-          businessOptions.value = businessOptions.value.slice(1);
+          businessData.options = businessData.options.filter((i) => i.id !== 0);
         }
       }
     });
-  }
+  };
+
+  const businessRemote = (e) => {
+    if (businessData.form.name) {
+      businessData.form.name = null;
+      getBusiness();
+    }
+    if (e) {
+      businessData.form.name = e;
+      getBusiness();
+    }
+  };
+
+  return { businessData, getBusiness, businessRemote };
+}
+
+// 门店筛选
+import { storeSelect } from "@/api/business/list";
+
+export function useStoreSelect(type) {
+  const storeData = reactive({
+    form: {
+      pageNum: 1,
+      pageSize: 20,
+    },
+    options: [],
+    storeId: null,
+  });
+
+  const getStore = () => {
+    storeSelect(storeData.form).then((res) => {
+      if (res.code === 0) {
+        storeData.options = res.data;
+        if (type && res.data.length > 1) {
+          storeData.options.unshift({
+            id: 0,
+            name: "全部",
+          });
+        }
+        storeData.storeId =
+          storeData.options.length > 0 ? storeData.options[0].id : null;
+      }
+    });
+  };
+
+  return { storeData, getStore };
+}
+
+// 分组筛选
+import { groupOptions } from "@/api/store/group";
+export function useGroupSelect() {
+  const groupData = reactive({
+    form: {
+      pageNum: 1,
+      pageSize: 20,
+    },
+    options: [],
+  });
+
+  const getGroup = () => {
+    groupOptions(groupData.form).then((res) => {
+      if (res.code === 0) {
+        groupData.options = res.data;
+      }
+    });
+  };
+
+  const groupRemote = (e) => {
+    if (groupData.form.name) {
+      groupData.form.name = null;
+      getGroup();
+    }
+    if (e) {
+      groupData.form.name = e;
+      getGroup();
+    }
+  };
+
+  return { groupData, getGroup, groupRemote };
+}
+
+// 设备列表
+import { deviceList } from "@/api/business/list";
+
+export function useDeviceList() {
+  const deviceOptions = ref([]);
+
+  const getDeviceOptions = () => {
+    deviceList({}).then((res) => {
+      if (res.code === 0) {
+        deviceOptions.value = res.data;
+      }
+    });
+  };
+
+  onMounted(() => getDeviceOptions());
+
+  onUnmounted(() => getDeviceOptions());
+
+  return { deviceOptions };
+}
+
+// 用户列表
+import { userSelect } from "../api/system/user";
 
-  onMounted(() => getOptions());
+export function useUserSelect() {
+  const userData = reactive({
+    form: {
+      pageNum: 1,
+      pageSize: 20,
+    },
+    options: [],
+  });
+
+  const getUser = () => {
+    userSelect(userData.form).then((res) => {
+      if (res.code === 0) {
+        userData.options = res.data;
+      }
+    });
+  };
+
+  const userRemote = (e) => {
+    if (userData.form.userName) {
+      userData.form.userName = null;
+      getUser();
+    }
+    if (e) {
+      userData.form.userName = e;
+      getUser();
+    }
+  };
+
+  return { userData, getUser, userRemote };
+}
 
-  onUnmounted(() => getOptions());
+// 省市区
+import { areaOptions } from "@/api/store/list";
+
+export function useAreaSelect() {
+  const areaData = reactive({
+    options: [],
+  });
+
+  const getArea = () => {
+    areaOptions().then((res) => {
+      if (res.code === 0) {
+        areaData.options = res.data;
+      }
+    });
+  };
+
+  return { areaData, getArea };
+}
+
+// 音频聚合列表
+import { audioList } from "../api/content/setting";
+
+export function useAudioSelect() {
+  const audioData = reactive({
+    form: {
+      pageNum: 1,
+      pageSize: 10,
+    },
+    options: [],
+  });
+
+  const getAudio = () => {
+    audioList(audioData.form).then((res) => {
+      if (res.code === 0) {
+        console.log(res);
+      }
+    });
+  };
 
-  return { businessForm, businessOptions };
+  return { audioData, getAudio };
 }

+ 3 - 0
src/main.js

@@ -27,6 +27,8 @@ import './permission' // permission control
 import { useDict } from '@/utils/dict'
 import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
 
+import { hoursToSeconds } from '@/utils/hts.js'
+
 // 分页组件
 import Pagination from '@/components/Pagination'
 // 自定义表格工具组件
@@ -59,6 +61,7 @@ app.config.globalProperties.handleTree = handleTree
 app.config.globalProperties.addDateRange = addDateRange
 app.config.globalProperties.selectDictLabel = selectDictLabel
 app.config.globalProperties.selectDictLabels = selectDictLabels
+app.config.globalProperties.hoursToSeconds = hoursToSeconds
 
 // 全局组件挂载
 app.component('DictTag', DictTag)

+ 4 - 0
src/utils/hts.js

@@ -0,0 +1,4 @@
+export function hoursToSeconds(hours) {
+  const parts = hours.split(':').map(Number);
+  return parts[0] * 3600 + parts[1] * 60 + parts[2];
+}

+ 9 - 18
src/views/business/list/index.vue

@@ -9,7 +9,7 @@
       </el-form-item>
       <el-form-item label="行业:">
         <el-select v-model="data.form.organizationIndustryId" placeholder="请选择行业" clearable>
-          <el-option v-for="item in classOptions" :key="item.value" :value="item.value" :label="item.label" />
+          <el-option v-for="item in classOptions" :key="item.id" :value="item.id" :label="item.name" />
         </el-select>
       </el-form-item>
       <el-form-item>
@@ -57,7 +57,7 @@
         </el-form-item>
         <el-form-item label="行业:" prop="organizationIndustryId">
           <el-select v-model="data.dialogForm.organizationIndustryId" placeholder="请选择行业">
-            <el-option v-for="item in classOptions" :key="item.value" :value="item.value" :label="item.label" />
+            <el-option v-for="item in classOptions" :key="item.id" :value="item.id" :label="item.name" />
           </el-select>
         </el-form-item>
         <el-form-item label="联系方式:" prop="phone">
@@ -66,9 +66,10 @@
         <el-form-item label="账号名称:" prop="userName">
           <el-input v-model="data.dialogForm.userName" placeholder="<企业名>@<用户名>,例如:yundong@admin" />
         </el-form-item>
-        <el-form-item label="归属企业:" prop="parentId">
-          <el-select v-model="data.dialogForm.parentId" filterable :filter-method="filterMethod" placeholder="请选择归属企业">
-            <el-option v-for="item in businessData.options" :key="item.value" :value="item.value" :label="item.label"
+        <el-form-item label="所属企业:" prop="parentId">
+          <el-select v-model="data.dialogForm.parentId" placeholder="请选择所属企业" filterable remote
+            :remote-method="businessRemote" remote-show-suffix>
+            <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name"
               :disabled="disabledBusiness(item)" />
           </el-select>
         </el-form-item>
@@ -97,7 +98,7 @@
 
 <script setup>
 import { list, detail, remove, submit, edit, change, reset } from '@/api/business/list'
-import { useBusinessClass, useBusinessList } from '@/hooks/index'
+import { useBusinessClass, useBusinessSelect } from '@/hooks/index'
 import { treeselect } from '@/api/system/menu'
 import { ElMessageBox } from 'element-plus';
 import { nextTick } from 'vue';
@@ -105,7 +106,8 @@ import { nextTick } from 'vue';
 let { proxy } = getCurrentInstance();
 
 const { classOptions } = useBusinessClass()
-const { businessData, getBusinessOptions } = useBusinessList()
+const { businessData, getBusiness, businessRemote } = useBusinessSelect()
+getBusiness()
 
 const reg = ref(/1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}/)
 const checkPhone = (rule, value, callback) => {
@@ -269,17 +271,6 @@ function upload(e) {
   data.dialogForm.businessLicense = e.file
 }
 
-// 归属企业搜索
-function filterMethod(query) {
-  if (query) {
-    setTimeout(() => {
-      businessData.form.pageNum = 1
-      businessData.form.name = query
-      getBusinessOptions()
-    }, 300)
-  }
-}
-
 // 关闭弹窗
 function getClose() {
   dialogVisible.value = false

+ 213 - 93
src/views/content/scene/detail.vue

@@ -1,43 +1,55 @@
 <template>
   <div class='app-container'>
     <el-form class="form" label-width="100px" ref="form" :model="data.form" :rules="data.rules">
-      <el-form-item class="audioList" label="选择音频:">
-        <div class="audio">
+      <el-form-item class="list" label="预设音频:" prop="takeId">
+        <div v-if="data.form.takeId" class="file" @click="getDialog">
           <img src="@/assets/icons/svg/file.svg" width="70" height="70" />
-          <span>双十一活动</span>
-          <span>3小时21分</span>
+          <span>{{ data.form.takeName }}</span>
         </div>
-        <el-button icon="Plus" @click="getDialog" style="width: 100%">新增</el-button>
+        <el-button v-else icon="Plus" @click="getDialog">新增</el-button>
       </el-form-item>
-      <el-form-item label="内容名称:">
-        <el-input v-model="data.form.name" placeholder="请输入内容名称" />
-      </el-form-item>
-      <el-form-item label="播放时间:">
-        <el-date-picker type="datetimerange" start-placeholder="开始时间" end-placeholder="结束时间"
-          format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" />
-        <el-button icon="Plus" style="width: 100%; margin-top: 15px">新增时段</el-button>
+      <el-form-item label="播放时间:" prop="listDate">
+        <div class="date" v-for="(item, index) in data.dateList">
+          <el-time-picker ref="picker" v-model="data.dateList[index]" is-range format="HH:mm:ss" value-format="HH:mm:ss"
+            start-placeholder="开始时间" end-placeholder="结束时间" @change="handleChangeDate($event, index)" />
+          <el-link v-show="data.dateList.length > 1" icon="CircleClose" :underline="false" @click="handleDeleteDate(index)" />
+          <el-link v-show="data.dateList.length - 1 === index" icon="CirclePlus" :underline="false"
+            @click="handlePushDate" />
+        </div>
       </el-form-item>
-      <el-form-item label="所属门店:" style="width: 800px">
-        <el-table :data="data.form.list">
-          <el-table-column label="所属" align="center">
+      <el-form-item label="关联设备:" style="width: 900px">
+        <el-table :data="data.form.tpresetContentInfoReq">
+          <el-table-column label="所属企业" align="center">
             <template #default="scope">
-              <el-select>
-                <!-- <el-option /> -->
-              </el-select>
+              <el-form-item :prop="`tpresetContentInfoReq.${scope.$index}.tenantId`"
+                :rules="{ required: true, trigger: 'change' }">
+                <el-select v-model="scope.row.tenantId" placeholder="请选择企业" style="width: 200px" filterable remote
+                  :remote-method="businessRemote" remote-show-suffix @change="handleChange">
+                  <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name" />
+                </el-select>
+              </el-form-item>
             </template>
           </el-table-column>
           <el-table-column label="所属门店" align="center">
             <template #default="scope">
-              <el-select>
-                <!-- <el-option /> -->
-              </el-select>
+              <el-form-item :prop="`tpresetContentInfoReq.${scope.$index}.storeId`"
+                :rules="{ required: true, trigger: 'change' }">
+                <el-select v-model="scope.row.storeId" placeholder="请选择门店" style="width: 200px" filterable remote
+                  :remote-method="storeRemote" remote-show-suffix>
+                  <el-option v-for="item in storeData.options" :key="item.id" :value="item.id" :label="item.name" />
+                </el-select>
+              </el-form-item>
             </template>
           </el-table-column>
           <el-table-column label="所属设备" align="center">
             <template #default="scope">
-              <el-select>
-                <!-- <el-option /> -->
-              </el-select>
+              <el-form-item :prop="`tpresetContentInfoReq.${scope.$index}.deviceType`"
+                :rules="{ required: true, trigger: 'change' }">
+                <el-select v-model="scope.row.deviceType" placeholder="请选择所属设备">
+                  <el-option v-for="item in deviceOptions" :key="item.clientTypeId" :value="item.clientTypeId"
+                    :label="item.name" />
+                </el-select>
+              </el-form-item>
             </template>
           </el-table-column>
           <el-table-column label="操作" align="center">
@@ -48,83 +60,163 @@
         </el-table>
       </el-form-item>
       <el-form-item style="width: 800px">
-        <el-button icon="Plus" style="width: 100%">新增关联</el-button>
+        <el-button icon="Plus" @click="getAdd">新增关联</el-button>
       </el-form-item>
     </el-form>
     <div class="form-btn">
-      <el-button>取消</el-button>
-      <el-button type="primary">确定</el-button>
+      <el-button @click="getClose">取消</el-button>
+      <el-button type="primary" @click="getSubmit">确定</el-button>
     </div>
 
-    <el-dialog v-model="dialogVisible" title="导入内容">
+    <el-dialog v-model="dialogVisible" title="导入内容" width="970">
       <el-form inline>
-        <el-form-item label="内容分类:">
-          <el-select v-model="data.dialogForm.radio" placeholder="请选择内容分类">
-            <el-option v-for="item in data.radioOptions" :key="item.value" :value="item.value" :label="item.label" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="名称:">
-          <el-input placeholder="请输入音频名称" />
-        </el-form-item>
-        <el-form-item label="分类:">
-          <el-select placeholder="请选择音频分类">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item label="当前状态:">
-          <el-select placeholder="请选择当前状态">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" icon="Plus">搜索</el-button>
-          <el-button icon="Refresh">重置</el-button>
+        <el-form-item v-for="item in data.tableData" :key="item.takeId">
+          <div class="file hover" @click="handleChecked(item)">
+            <img src="@/assets/icons/svg/file.svg" width="70" height="70" />
+            <span>{{ item.takeName }}</span>
+          </div>
         </el-form-item>
       </el-form>
-      <el-table>
-        <el-table-column label="ID" align="center"></el-table-column>
-        <el-table-column label="名称" align="center"></el-table-column>
-        <el-table-column label="时长" align="center"></el-table-column>
-        <el-table-column label="分类" align="center"></el-table-column>
-        <el-table-column label="当前状态" align="center"></el-table-column>
-        <el-table-column label="更新时间" align="center"></el-table-column>
-        <el-table-column label="操作" align="center">
-          <template #default="scope">
-            <el-button type="primary" link>选择</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
+import { list, timeSubmit } from '@/api/content/scene.js'
+import { useBusinessSelect, useStoreSelect, useDeviceList } from '@/hooks/index.js'
+
+// 公共方法
+const { deviceOptions } = useDeviceList()
+const { storeData, getStore, storeRemote } = useStoreSelect(true)
+const { businessData, getBusiness, businessRemote } = useBusinessSelect(true)
+getBusiness()
+
+const { proxy } = getCurrentInstance();
+
+// 遮罩层
+const loading = ref(false)
+
 const data = reactive({
   form: {
-
+    listDate: [],
+    tpresetContentInfoReq: [{}]
+  },
+  rules: {
+    takeId: [{ required: true, message: '请选择预设音频', trigger: 'change' }],
+    listDate: [{ type: 'array', required: true, message: '请选择播放时间', trigger: 'change' }]
   },
-  rules: {},
-  radioOptions: [{
-    value: 1,
-    label: '播放内容'
-  }, {
-    value: 2,
-    label: 'Ohplay歌曲'
-  }, {
-    value: 3,
-    label: '上传新内容'
-  }],
   dialogForm: {
-    radio: 1
-  }
+    pageNum: 1,
+    pageSize: 10
+  },
+  // 播放时间数组
+  dateList: [[]],
+  tableData: []
 })
 
+const total = ref(0)
+
+// 列表
+function getList() {
+  loading.value = true
+  list(data.dialogForm).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data.records
+      total.value = res.data.total
+      loading.value = false
+    }
+  })
+}
+
 // 弹窗
 const dialogVisible = ref(false)
 // 打开弹窗
 function getDialog() {
   dialogVisible.value = true
+  getList()
+}
+// 选择预设音频
+function handleChecked(item) {
+  data.form.takeId = item.takeId
+  data.form.takeName = item.takeName
+  dialogVisible.value = false
+  proxy.$modal.msgSuccess('选择成功!')
+}
+
+// 增加时间
+function handlePushDate() {
+  const boolean = ref(false)
+  data.dateList.map(i => boolean.value = i.length === 0 ? true : false)
+  if (boolean.value) {
+    proxy.$modal.msgError('有时间段未填')
+  } else {
+    data.dateList.push([])
+  }
+}
+
+// 删除时间
+function handleDeleteDate(index) {
+  data.dateList.splice(index, 1)
 }
+
+// 校验时间
+const handleChangeDate = (e, index) => {
+  if (e !== null) {
+    data.listDate = []
+    data.form.listDate = []
+    const start = proxy.hoursToSeconds(e[0])
+    const end = proxy.hoursToSeconds(e[1])
+    for (let i = 0; i < data.dateList.length; i++) {
+      if (i !== index) {
+        let st = proxy.hoursToSeconds(data.dateList[i][0])
+        let en = proxy.hoursToSeconds(data.dateList[i][1])
+        if (start >= st && start <= en || end >= st && end <= en || start <= st && end >= en) {
+          data.dateList.splice(index, 1)
+          proxy.$modal.msgError("该时间段已设置!")
+          return false
+        }
+      }
+    }
+    data.dateList.map(i => {
+      data.form.listDate.push({
+        startTime: i[0],
+        endTime: i[1]
+      })
+    })
+  }
+}
+
+// 添加门店
+const getAdd = () => {
+  data.form.tpresetContentInfoReq.push([])
+}
+
+const handleChange = (e) => {
+  storeData.form.tenantId = e
+  getStore()
+}
+
+// 提交
+const getSubmit = () => {
+  proxy.$refs.form.validate(valid => {
+    if (valid) {
+      timeSubmit(data.form).then(res => {
+        if (res.code === 0) {
+          proxy.$modal.msgSuccess('提交成功!')
+          getClose()
+        }
+      })
+    } else {
+      return false
+    }
+  })
+}
+
+// 取消
+const getClose = () => {
+  proxy.$tab.closeOpenPage('/content/scene')
+}
+
 </script>
 
 <style lang="scss" scoped>
@@ -132,34 +224,62 @@ function getDialog() {
   .el-form-item {
     width: 500px;
   }
+
+  .el-table__row {
+    .el-form-item {
+      width: 100%;
+    }
+  }
 }
 
 .form-btn {
   margin-left: 100px;
 }
 
-.audioList {
+.list {
   :deep(.el-form-item__content) {
     display: flex;
     flex-direction: column;
     align-items: flex-start;
+  }
+}
 
-    .audio {
-      width: 200px;
-      height: 200px;
-      border: 1px solid #e9e9eb;
-      border-radius: 8px;
-      margin-bottom: 15px;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      align-items: center;
-      box-shadow: 0 0 6px 0 #e9e9eb;
-      line-height: 10px;
-      span{
-        margin-top: 20px;
-      }
-    }
+.file {
+  width: 200px;
+  height: 200px;
+  border: 1px solid #e9e9eb;
+  border-radius: 8px;
+  margin-bottom: 15px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  box-shadow: 0 0 6px 0 #e9e9eb;
+  line-height: 10px;
+  cursor: pointer;
+
+  span {
+    margin-top: 20px;
   }
 }
+
+.hover:hover {
+  box-shadow: 0 0 10px 2px #a0cfff;
+  transition: all 0.5s;
+}
+
+.date {
+  display: flex;
+  align-items: center;
+  margin-bottom: 15px;
+
+  .el-link {
+    margin-left: 10px;
+    font-size: 16px;
+  }
+}
+
+.date:last-child {
+  margin: 0;
+}
 </style>

+ 11 - 35
src/views/content/scene/index.vue

@@ -7,9 +7,9 @@
           <el-button icon="Search" @click="getSearch" />
         </template>
       </el-input>
-      <draggable class="contentList" v-model="data.tableData" item-key="id" chosenClass="chosenClass"
-        dragClass="dragClass" data-id="content" :group="{ name: 'componentGroup', pull: 'clone', put: false }"
-        :sort="false" :move="onMove" @end="onEnd" forceFallback v-loading="loading">
+      <draggable class="contentList" v-model="data.tableData" item-key="id" chosenClass="chosenClass" data-id="content"
+        :group="{ name: 'componentGroup', pull: 'clone', put: false }" :sort="false" :move="onMove" @end="onEnd"
+        forceFallback v-loading="loading">
         <template #item="{ element, index }">
           <div class="item" @click="getDialog(element.takeId)">
             <el-icon class="close" size="18" @click.stop="getDelete(element)">
@@ -24,19 +24,7 @@
         v-model:page="data.form.pageNum" v-model:limit="data.form.pageSize" @pagination="getList" />
     </div>
     <div class="right">
-      <el-form class="search" inline>
-        <el-form-item>
-          <el-select filterable placeholder="请选择门店" style="width: 200px" @change="getSearch">
-            <el-option v-for="item in businessOptions" :key="item.value" :value="item.value" :label="item.label" />
-          </el-select>
-        </el-form-item>
-        <el-form-item style="float:right">
-          <el-button type="primary" icon="Plus" @click="getRouter()">新增预设</el-button>
-        </el-form-item>
-      </el-form>
-      <div class="chart">
-        <GanttChart />
-      </div>
+      <gantt-chart path="/content/scene/detail" />
     </div>
 
     <!-- 弹窗 -->
@@ -64,9 +52,6 @@
 <script setup>
 import { list, submit, detail, remove } from '@/api/content/scene'
 import GanttChart from '@/components/GanttChart/index.vue'
-import { useBusinessList } from '@/hooks/index'
-
-const { businessForm, businessOptions } = useBusinessList(true)
 
 const baseUrl = ref(import.meta.env.VITE_APP_BASE_API)
 const { proxy } = getCurrentInstance();
@@ -92,12 +77,7 @@ const data = reactive({
     takeName: [{
       required: true, message: '请输入场景名称', trigger: 'blur'
     }]
-  },
-  businessForm: {
-    pageNum: 1,
-    pageSize: 10
-  },
-  options: []
+  }
 })
 
 // 总数据
@@ -244,8 +224,14 @@ function getRouter(query) {
         position: absolute;
         right: 35px;
         top: 15px;
+        opacity: 0;
       }
     }
+
+    .item:hover .close {
+      opacity: 1;
+      transition: all 1s;
+    }
   }
 }
 
@@ -255,16 +241,6 @@ function getRouter(query) {
   min-width: 700px;
   width: calc(100% - 400px);
   height: 792px;
-
-  .search {
-    padding-left: 70px;
-  }
-
-  .chart {
-    overflow-y: auto;
-    padding: 20px;
-    display: flex;
-  }
 }
 
 draggable,

+ 52 - 19
src/views/content/setting/detail.vue

@@ -10,15 +10,20 @@
           <span>双十一活动</span>
           <span>3小时21分</span>
         </div>
-        <el-button icon="Plus" @click="getDialog" style="width: 100%">新增</el-button>
+        <el-button icon="Plus" @click="getDialog">新增</el-button>
       </el-form-item>
       <el-form-item label="内容名称:">
         <el-input v-model="data.form.name" placeholder="请输入内容名称" />
       </el-form-item>
       <el-form-item label="播放时间:">
-        <el-date-picker type="datetimerange" start-placeholder="开始时间" end-placeholder="结束时间"
-          format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" />
-        <el-button icon="Plus" style="width: 100%; margin-top: 15px">新增时段</el-button>
+        <div class="date" v-for="(item, index) in data.dateList">
+          <el-date-picker type="datetimerange" start-placeholder="开始时间" end-placeholder="结束时间"
+            format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" />
+          <el-link v-show="data.dateList.length > 1" icon="CircleClose" :underline="false"
+            @click="handleDeleteDate(index)" />
+          <el-link v-show="data.dateList.length - 1 === index" icon="CirclePlus" :underline="false"
+            @click="handlePushDate" />
+        </div>
       </el-form-item>
       <el-form-item label="所属门店:" style="width: 800px">
         <el-table :data="data.form.list">
@@ -51,11 +56,11 @@
         </el-table>
       </el-form-item>
       <el-form-item style="width: 800px">
-        <el-button icon="Plus" style="width: 100%">新增关联</el-button>
+        <el-button icon="Plus">新增关联</el-button>
       </el-form-item>
     </el-form>
     <div class="form-btn">
-      <el-button>取消</el-button>
+      <el-button @click="getClose">取消</el-button>
       <el-button type="primary">确定</el-button>
     </div>
 
@@ -102,28 +107,23 @@
 </template>
 
 <script setup>
+const { proxy } = getCurrentInstance();
+
 const data = reactive({
   form: {
-    audioList: [{
-      audioName: '测试111111111111111111'
-    }, {
-      audioName: '测试111111111111111111'
-    }, {
-      audioName: '测试111111111111111111'
-    }, {
-      audioName: '测试111111111111111111'
-    }]
+
   },
+  dateList: [[]],
   rules: {},
   radioOptions: [{
     value: 1,
-    label: '播放内容'
+    label: '内容列表'
   }, {
     value: 2,
-    label: 'Ohplay歌曲'
+    label: 'Ohplay音频'
   }, {
     value: 3,
-    label: '上传新内容'
+    label: '场景预设'
   }],
   dialogForm: {
     radio: 1
@@ -136,6 +136,27 @@ const dialogVisible = ref(false)
 function getDialog() {
   dialogVisible.value = true
 }
+
+// 增加时间
+function handlePushDate() {
+  const boolean = ref(false)
+  data.dateList.map(i => boolean.value = i.length === 0 ? true : false)
+  if (boolean.value) {
+    proxy.$modal.msgError('有时间段未填')
+  } else {
+    data.dateList.push([])
+  }
+}
+
+// 删除时间
+function handleDeleteDate(index) {
+  data.dateList.splice(index, 1)
+}
+
+// 取消
+const getClose = () => {
+  proxy.$tab.closeOpenPage('/content/setting')
+}
 </script>
 
 <style lang="scss" scoped>
@@ -171,10 +192,22 @@ function getDialog() {
       align-items: center;
       box-shadow: 0 0 6px 0 #e9e9eb;
       line-height: 10px;
-      span{
+
+      span {
         margin-top: 20px;
       }
     }
   }
 }
+
+.date {
+  display: flex;
+  align-items: center;
+  margin-bottom: 15px;
+
+  .el-link {
+    margin-left: 10px;
+    font-size: 16px;
+  }
+}
 </style>

+ 180 - 79
src/views/content/setting/index.vue

@@ -1,70 +1,92 @@
 <template>
   <div class='app-container'>
-    <el-row style="height: 100%;">
-      <el-col :span="6" class="left">
-        <el-calendar ref="calendar" @click="getDate">
-          <template #header="{ date }">
-            <span>{{ date }}</span>
-            <el-button link icon="ArrowLeftBold" @click="selectDate('prev-month')" />
-            <el-button link icon="ArrowRightBold" @click="selectDate('next-month')" />
-          </template>
-        </el-calendar>
-        <div class="list">
-          <el-input placeholder="搜索" suffix-icon="Search" />
-          <el-tabs v-model="activeName">
-            <el-tab-pane label="内容列表" name="first">
-              <el-scrollbar :height="270">
-                <draggable class="contentList" v-model="data.contentList" item-key="id" chosen-class="chosen"
-                  data-id="content" :group="{ name: 'componentGroup', pull: 'clone', put: false }" :sort="false"
-                  :move="onMove" @end="onEnd">
-                  <template #item="{ element, index }">
-                    <span class="item">
-                      <el-icon>
+    <div class="left">
+      <el-calendar ref="calendar" @click="getDate">
+        <template #header="{ date }">
+          <span>{{ date }}</span>
+          <el-button link icon="ArrowLeftBold" @click="selectDate('prev-month')" />
+          <el-button link icon="ArrowRightBold" @click="selectDate('next-month')" />
+        </template>
+      </el-calendar>
+      <div class="list">
+        <el-input placeholder="搜索" suffix-icon="Search" />
+        <el-tabs v-model="activeName" @tab-click="handleChangeTabs">
+          <el-tab-pane v-for="item in data.tabOptions" :key="item.value" :label="item.label" :name="item.value"
+            v-loading="loading">
+            <el-scrollbar :height="220">
+              <draggable :class="['contentList', activeName !== 'scene' ? 'noScene' : 'scene']" v-model="data.tableData"
+                item-key="id" chosen-class="chosenClass" data-id="content"
+                :group="{ name: 'componentGroup', pull: 'clone', put: false }" :sort="false" :move="onMove"
+                @end="onEnd">
+                <template #item="{ element, index }">
+                  <div class="item">
+                    <span v-show="activeName !== 'scene'" class="item_line" :title="element.name">
+                      <el-icon style="margin-right: 10px; line-height:40px">
                         <VideoPlay />
                       </el-icon>
-                      {{ element.label }}
+                      {{ element.name }}
                     </span>
-                  </template>
-                </draggable>
-              </el-scrollbar>
-            </el-tab-pane>
-            <el-tab-pane label="场景预设" name="second">Config</el-tab-pane>
-            <el-tab-pane label="Ohplay音频" name="third">Config</el-tab-pane>
-          </el-tabs>
-        </div>
-      </el-col>
-      <el-col :span="18" class="right">
-        <el-form class="search" inline>
-          <el-form-item>
-            <el-select placeholder="请选择门店" style="width: 200px">
-              <!-- <el-option /> -->
-            </el-select>
-          </el-form-item>
-          <el-form-item style="float:right">
-            <el-button type="primary" icon="Plus" @click="getDetail">新增</el-button>
-          </el-form-item>
-        </el-form>
-        <div class="chart">
-          <gantt-chart />
-        </div>
-      </el-col>
-    </el-row>
+                    <span v-show="activeName === 'scene'" class="item_box">
+                      <img src="@/assets/icons/svg/file.svg" width="50" height="50" />
+                      <span class="takeName">{{ element.takeName }}</span>
+                    </span>
+                  </div>
+                </template>
+              </draggable>
+            </el-scrollbar>
+            <el-pagination v-show="total > 10" layout="prev,pager,next" :total="total"
+              @current-change="handleChangeCurrent" />
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+    <div class="right">
+      <gantt-chart path="/content/setting/detail" />
+    </div>
   </div>
 </template>
 
 <script setup>
+import { list as contentList } from '@/api/content/list'
+import { list as ohplayList } from '@/api/content/ohplay'
+import { list as sceneList } from '@/api/content/scene'
+import { useAudioSelect } from '@/hooks/index'
 import GanttChart from '@/components/GanttChart'
 
 const { proxy } = getCurrentInstance();
 
-const data = reactive({
-  contentList: [
-    { value: 1123123, label: '你就不要想起我' }, { value: 2124123124, label: '双11活动' }
-  ],
+const { audioData, getAudio } = useAudioSelect()
+getAudio()
 
+// 遮罩层
+const loading = ref(false)
+// tab页
+const activeName = ref("content")
+// 数据
+const data = reactive({
+  // tab数组
+  tabOptions: [{
+    value: 'content',
+    label: '内容列表'
+  }, {
+    value: 'ohplay',
+    label: 'ohplay音频'
+  }, {
+    value: 'scene',
+    label: '场景预设'
+  }],
+  // 表单
+  form: {
+    pageNum: 1,
+    pageSize: 10
+  },
+  // 列表
+  tableData: []
 })
 
-const activeName = ref("first")
+// 总数据
+const total = ref(0)
+
 
 // 日历
 const calendar = ref()
@@ -78,9 +100,70 @@ function getDate(e) {
   console.log(new Date().getTime());
 }
 
+const handleChangeTabs = (e) => {
+  activeName.value = e.props.name
+  data.form = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  data.tableData = []
+  modalChange(e.props.name)
+}
+
+// 内容列表
+const getContentList = () => {
+  loading.value = true
+  contentList(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data.records
+      total.value = res.data.total
+      loading.value = false
+    }
+  })
+}
+getContentList()
+
+// Ohplay音频
+const getOhplayList = () => {
+  loading.value = true
+  ohplayList(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data.records
+      total.value = res.data.total
+      loading.value = false
+    }
+  })
+}
+
+// 场景预设
+const getSceneList = () => {
+  loading.value = true
+  sceneList(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data.records
+      total.value = res.data.total
+      loading.value = false
+    }
+  })
+}
+
+const handleChangeCurrent = (e) => {
+  data.form.pageNum = e
+  modalChange(activeName.value)
+}
+
+const modalChange = (e) => {
+  if (e === 'content') {
+    getContentList()
+  } else if (e === 'ohplay') {
+    getOhplayList()
+  } else {
+    getSceneList()
+  }
+}
+
 // 拖拽内容
 function onMove(params) {
-  console.log(params);
   if (params.to.childElementCount < 1) {
     return true
   } else {
@@ -98,12 +181,12 @@ function onEnd(params) {
       value: params.item._underlying_vm_.value,
       label: params.item._underlying_vm_.label
     }
-    getDetail(query)
+    getRouter(query)
   }
 }
 
 // 新增
-function getDetail(query) {
+function getRouter(query) {
   proxy.$router.push({
     path: `/content/setting/detail`,
     query
@@ -114,10 +197,11 @@ function getDetail(query) {
 
 <style lang="scss" scoped>
 .app-container {
-  overflow-y: auto;
+  display: flex;
 }
 
 .left {
+  width: 400px;
   background-color: #f4f4f5;
   display: flex;
   flex-direction: column;
@@ -126,41 +210,60 @@ function getDetail(query) {
     height: 42px;
   }
 
+  :deep(.el-tabs__nav) {
+    justify-content: space-around;
+    float: none;
+  }
+
   .list {
     padding: 20px;
 
     .contentList {
       display: flex;
-      flex-direction: column;
-      width: 100%;
 
       .item {
         display: flex;
-        align-items: center;
-        padding: 8px 10px;
-        background-color: #FFF;
-        margin-bottom: 10px;
-        border-radius: 5px;
-        color: #3979F9;
+
+        .item_line {
+          width: 100%;
+          padding: 8px 10px;
+          background-color: #FFF;
+          margin-bottom: 10px;
+          border-radius: 5px;
+          color: #3979F9;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+
+        .item_box {
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          align-items: center;
+          padding: 0px 10px;
+          font-size: 14px;
+          margin-bottom: 20px;
+        }
       }
     }
   }
 }
 
+.noScene {
+  flex-direction: column;
+}
+
+.scene {
+  flex-wrap: wrap;
+}
+
 .right {
   display: flex;
   flex-direction: column;
+  min-width: 700px;
+  width: calc(100% - 400px);
   height: 792px;
-
-  .search {
-    padding-left: 70px;
-  }
-
-  .chart {
-    overflow-y: auto;
-    padding: 20px;
-    display: flex;
-  }
 }
 
 draggable,
@@ -168,13 +271,11 @@ draggable,
   user-select: none;
 }
 
-.ghost {
-  padding: 8px 10px;
+.chosenClass {
+  background: none !important;
 }
 
-.chosen {
-  flex: 1;
-  height: 60px !important;
-  line-height: 60px;
+.el-scrollbar {
+  margin-bottom: 20px;
 }
 </style>

+ 0 - 174
src/views/device/control/index.vue

@@ -1,174 +0,0 @@
-<template>
-  <div class='app-container'>
-    <el-form inline>
-      <el-form-item label="设备名称:">
-        <el-input placeholder="请输入设备名称" />
-      </el-form-item>
-      <el-form-item label="设备分类:">
-        <el-select placeholder="请选择设备分类">
-          <el-option />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="所属门店:">
-        <el-select placeholder="请选择门店">
-          <el-option />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="所属城市:">
-        <el-select placeholder="请选择所属城市">
-          <el-option />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="当前状态:">
-        <el-select placeholder="请选择当前状态">
-          <el-option />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search">搜索</el-button>
-        <el-button icon="Refresh">重置</el-button>
-        <el-button type="primary" plain icon="Plus" @click="getDialog()">关联设备</el-button>
-      </el-form-item>
-      <el-form-item style="float: right">
-        <el-button icon="Expand" @click="grid = false" />
-        <el-button icon="Grid" @click="grid = true" />
-      </el-form-item>
-    </el-form>
-    <!-- 列表 -->
-    <el-table v-if="!grid" :data="tableData" v-loading="loading">
-      <el-table-column label="设备名称" align="center"></el-table-column>
-      <el-table-column label="设备型号" align="center"></el-table-column>
-      <el-table-column label="设备图片" align="center"></el-table-column>
-      <el-table-column label="设备分类" align="center"></el-table-column>
-      <el-table-column label="所属门店" align="center"></el-table-column>
-      <el-table-column label="所属城市" align="center"></el-table-column>
-      <el-table-column label="当前状态" align="center"></el-table-column>
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button type="primary" link>编辑</el-button>
-          <el-button type="primary" link>上架</el-button>
-          <el-button type="primary" link>下架</el-button>
-          <el-button type="danger" link>删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <div v-else class="card" v-for="item in data.tableData" :key="item">
-      <div class="card-header">
-        <i class="state" />
-        <el-dropdown>
-          <el-icon>
-            <MoreFilled />
-          </el-icon>
-          <template #dropdown>
-            <el-dropdown-menu>
-              <el-dropdown-item>查看</el-dropdown-item>
-              <el-dropdown-item>编辑</el-dropdown-item>
-              <el-dropdown-item>删除</el-dropdown-item>
-              <el-dropdown-item>上架</el-dropdown-item>
-              <el-dropdown-item>下架</el-dropdown-item>
-            </el-dropdown-menu>
-          </template>
-        </el-dropdown>
-      </div>
-      <el-image class="icon" src="" />
-      <span class="name">猫王音响·Mate X1</span>
-    </div>
-    <!-- 弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="title" width="500" :before-close="close">
-      <el-form label-width="100px">
-        <el-form-item label="设备型号:">
-          <el-input placeholder="请输入设备型号" />
-        </el-form-item>
-        <el-form-item label="设备序列号:">
-          <el-input placeholder="请输入设备序列号" />
-        </el-form-item>
-        <el-form-item label="设备名称:">
-          <el-input placeholder="请输入设备名称" />
-        </el-form-item>
-        <el-form-item label="设备分类:">
-          <el-select placeholder="请选择设备分类">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item label="所属门店:">
-          <el-select placeholder="请选择所属门店">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item label="所在城市:">
-          <el-select placeholder="请选择所在城市">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="close">取消</el-button>
-        <el-button type="primary">确定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-// 遮罩层
-const loading = ref(false)
-
-const data = reactive({
-  // 表单
-  form: {},
-  // 列表
-  tableData: [{}],
-  // 弹窗表单
-  dialogForm: {}
-})
-
-const grid = ref(false)
-
-// 弹窗
-const dialogVisible = ref(false)
-// 弹窗标题
-const title = ref('')
-// 打开弹窗
-function getDialog() {
-  dialogVisible.value = true
-  title.value = '关联'
-}
-</script>
-
-<style lang="scss" scoped>
-.card {
-  display: flex;
-  flex-direction: column;
-  padding: 10px 10px 20px;
-  width: 185px;
-  border-radius: 10px;
-  box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
-  margin-right: 40px;
-  margin-bottom: 30px;
-
-  .card-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-
-    .state {
-      width: 10px;
-      height: 10px;
-      background-color: #8aff5b;
-      border-radius: 50%;
-      display: inline-block;
-    }
-  }
-
-  .icon {
-    padding: 0 20px;
-    height: 110px;
-    margin: 15px 0;
-  }
-
-  .name {
-    font-size: 14px;
-    text-align: center;
-  }
-}
-</style>

+ 0 - 63
src/views/device/grouping/index.vue

@@ -1,63 +0,0 @@
-<template>
-  <div class='app-container'>
-    <el-form>
-      <el-form-item>
-        <el-button type="primary" icon="Plus" plain @click="getDialog">新增分组</el-button>
-      </el-form-item>
-    </el-form>
-    <el-table :data="tableData" v-loading="loading">
-      <el-table-column label="分组名称" align="center"></el-table-column>
-      <el-table-column label="相关设备" align="center"></el-table-column>
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button>编辑</el-button>
-          <el-button>删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="title" width="500">
-      <el-form>
-        <el-form-item label="分组名称:">
-          <el-input placeholder="请输入分组名称" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button>取消</el-button>
-        <el-button type="primary">确定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-let { proxy } = getCurrentInstance()
-
-const goBack = () => {
-  proxy.$tab.closeOpenPage('/device/control')
-}
-
-// 遮罩层
-const loading = ref(false)
-
-// 数据
-const data = reactive({
-  tableData: []
-})
-
-// 弹窗
-const dialogVisible = ref(false)
-const title = ref("新增")
-
-function getDialog() {
-  dialogVisible.value = true
-}
-</script>
-
-<style lang="scss" scoped>
-.header {
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 20px;
-}
-</style>

+ 0 - 102
src/views/device/store/index.vue

@@ -1,102 +0,0 @@
-<template>
-  <div class='app-container'>
-    <el-form inline>
-      <el-form-item label="门店名称:">
-        <el-input placeholder="请输入门店名称" />
-      </el-form-item>
-      <el-form-item label="门店编码:">
-        <el-input placeholder="请输入门店编码" />
-      </el-form-item>
-      <el-form-item label="门店分组:">
-        <el-select placeholder="请选择门店分组">
-          <!-- <el-option /> -->
-        </el-select>
-      </el-form-item>
-      <el-form-item label="组织架构:">
-        <el-select placeholder="请选择组织架构">
-          <!-- <el-option /> -->
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search">搜索</el-button>
-        <el-button icon="Refresh">重置</el-button>
-        <el-button type="primary" plain icon="Plus" @click="getDialog()">新增</el-button>
-      </el-form-item>
-    </el-form>
-    <!-- 列表 -->
-    <el-table :data="data.tableData" v-loading="loading">
-      <el-table-column label="门店名称" align="center"></el-table-column>
-      <el-table-column label="门店编码" align="center"></el-table-column>
-      <el-table-column label="门店地址" align="center"></el-table-column>
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button>编辑</el-button>
-          <el-button>删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="title" width="500">
-      <el-form label-width="100px">
-        <el-form-item label="门店名称:">
-          <el-input placeholder="请输入门店名称" />
-        </el-form-item>
-        <el-form-item label="门店编码:">
-          <el-input placeholder="请输入门店编码" />
-        </el-form-item>
-        <el-form-item label="归属账号:">
-          <el-select placeholder="请选择归属账号">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item label="上级组织:">
-          <el-select placeholder="请选择上级组织">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item label="所在城市:">
-          <el-select placeholder="请选择所在城市">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-        <el-form-item label="门店分组:">
-          <el-select placeholder="请选择门店分组">
-            <!-- <el-option /> -->
-          </el-select>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button>取消</el-button>
-        <el-button type="primary">确定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-// 遮罩层
-const loading = ref(false)
-
-const data = reactive({
-  // 表单
-  form: {
-    pageNum: 1,
-    pageSize: 10
-  },
-  // 列表
-  tableData: [],
-  // 弹窗表单
-  dialogForm: {}
-})
-
-// 弹窗
-const dialogVisible = ref(false)
-// 弹窗标题
-const title = ref('')
-// 打开弹窗
-function getDialog() {
-  dialogVisible.value = true
-  title.value = '新增'
-}
-
-</script>

+ 37 - 2
src/views/index.vue

@@ -1,5 +1,20 @@
 <template>
-  <div class="app-container"></div>
+  <div class="app-container">
+    <el-skeleton class="skeleton" :rows="1" animated>
+      <template #template>
+        <div class="item" style="height: 150px">
+          <el-skeleton-item v-for="item in 5" :key="item" />
+        </div>
+        <div class="item" style="height: 320px">
+          <el-skeleton-item style="width: 720px;" />
+          <el-skeleton-item />
+        </div>
+        <div class="item" style="height: calc(100% - 150px - 320px - 40px)">
+          <el-skeleton-item />
+        </div>
+      </template>
+    </el-skeleton>
+  </div>
 </template>
 
 <script setup>
@@ -7,6 +22,26 @@
 </script>
 
 <style scoped lang="scss">
+.app-container {
+  height: calc(100vh - 84px);
+}
 
-</style>
+.skeleton {
+  height: 100%;
+
+  .item {
+    display: flex;
+    margin-bottom: 20px;
+    flex: 1;
 
+    .el-skeleton__item {
+      height: 100%;
+      margin-right: 20px;
+    }
+
+    .el-skeleton__item:last-child{
+      margin: 0;
+    }
+  }
+}
+</style>

+ 325 - 0
src/views/store/devices/index.vue

@@ -0,0 +1,325 @@
+<template>
+  <div class='app-container'>
+    <el-form inline>
+      <el-form-item label="设备名称:">
+        <el-input v-model="data.form.name" placeholder="请输入设备名称" />
+      </el-form-item>
+      <el-form-item label="设备分类:">
+        <el-select v-model="data.form.deviceCategoryId" placeholder="请选择设备分类">
+          <!-- <el-option /> -->
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属企业:">
+        <el-select v-model="data.form.tenantId" placeholder="请选择所属企业">
+          <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="当前状态:">
+        <el-select v-model="data.form.status" placeholder="请选择当前状态">
+          <el-option v-for="item in sys_change_status" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="getSearch">搜索</el-button>
+        <el-button icon="Refresh" @click="getRefresh">重置</el-button>
+        <el-button type="primary" plain icon="Plus" @click="getDialog()">关联设备</el-button>
+      </el-form-item>
+      <el-form-item style="float: right">
+        <el-button icon="Expand" @click="grid = false" />
+        <el-button icon="Grid" @click="grid = true" />
+      </el-form-item>
+    </el-form>
+    <!-- 列表 -->
+    <el-table v-if="!grid" :data="data.tableData" v-loading="loading">
+      <el-table-column label="设备名称" prop="deviceName" align="center"></el-table-column>
+      <el-table-column label="设备型号" prop="clientType" align="center"></el-table-column>
+      <el-table-column label="设备图片" align="center" width="100px">
+        <template #default="scope">
+          <el-image :src="scope.row.devicePic" />
+        </template>
+      </el-table-column>
+      <el-table-column label="设备分类" prop="deviceCategoryName" align="center"></el-table-column>
+      <el-table-column label="所属门店" prop="storeName" align="center"></el-table-column>
+      <el-table-column label="所属城市" align="center">
+        <template #default="scope">
+          <span>{{ scope.row.provinceName }}</span>
+          <span v-if="scope.row.cityName"> - {{ scope.row.cityName }}</span>
+          <span v-if="scope.row.areaName"> - {{ scope.row.areaName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="当前状态" prop="status" align="center">
+        <template #default="scope">
+          <dict-tag :options="sys_change_status" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="getDialog(scope.row.id)">编辑</el-button>
+          <el-button v-if="scope.row.status === 1" type="primary" link
+            @click="getChange(scope.row.id, '上架')">上架</el-button>
+          <el-button v-else type="primary" link @click="getChange(scope.row.id, '下架')">下架</el-button>
+          <el-button type="danger" link @click="getDelete(item)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div v-else class="card_box">
+      <div class="card" v-for="item in data.tableData" :key="item">
+        <div class="card-header">
+          <i class="state" />
+          <el-dropdown>
+            <el-icon>
+              <MoreFilled />
+            </el-icon>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="getDialog(item.id)">编辑</el-dropdown-item>
+                <el-dropdown-item @click="getDelete(item)">删除</el-dropdown-item>
+                <el-dropdown-item v-if="item.status === 1" @click="getChange(scope.row.id, '上架')">上架</el-dropdown-item>
+                <el-dropdown-item v-else @click="getChange(scope.row.id, '下架')">下架</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+        <el-image class="icon" src="" />
+        <span class="name">猫王音响·Mate X1</span>
+      </div>
+    </div>
+    <pagination v-show="total > 0" :total="total" v-model:page="data.form.pageNum" v-model:limit="data.form.pageSize"
+      @pagination="getList" />
+
+    <!-- 弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="title" width="500" :before-close="getClose">
+      <el-form :model="data.dialogForm" ref="dialogForm" :rules="data.rules" label-width="auto">
+        <el-form-item label="设备型号:" prop="clientType">
+          <el-select v-model="data.dialogForm.clientType" filterable placeholder="请输入设备型号">
+            <el-option v-for="item in deviceOptions" :key="item.clientTypeId" :value="item.clientTypeId"
+              :label="item.name" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="设备序列号:" prop="deviceMac">
+          <el-input v-model="data.dialogForm.deviceMac" placeholder="请输入设备序列号" />
+        </el-form-item>
+        <el-form-item label="所属企业:" prop="tenantId">
+          <el-select v-model="data.dialogForm.tenantId" placeholder="请输入所属企业" filterable remote
+            :remote-method="businessRemote" remote-show-suffix>
+            <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="所属门店:" prop="storeId">
+          <el-select v-model="data.dialogForm.storeId" placeholder="请选择所属门店" filterable remote
+            :remote-method="storeRemote" remote-show-suffix>
+            <el-option v-for="item in storeData.options" :key="item.id" :value="item.id" :label="item.name" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="getClose">取消</el-button>
+        <el-button type="primary" @click="getSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { list, detail, submit, edit, change, remove } from "@/api/store/devices"
+import { useBusinessSelect, useStoreSelect, useDeviceList } from "@/hooks/index"
+
+const { businessData, getBusiness, businessRemote } = useBusinessSelect(true)
+getBusiness()
+const { storeData, getStore, storeRemote } = useStoreSelect()
+const { deviceOptions } = useDeviceList()
+
+const { proxy } = getCurrentInstance()
+
+const { sys_change_status } = proxy.useDict("sys_change_status");
+// 遮罩层
+const loading = ref(false)
+
+const data = reactive({
+  // 表单
+  form: {
+    pageNum: 1,
+    pageSize: 10
+  },
+  // 列表
+  tableData: [],
+  // 弹窗表单
+  dialogForm: {},
+  // 校验
+  rules: {
+    clientType: [{
+      required: true, message: '请选择设备型号', trigger: 'change'
+    }],
+    deviceMac: [{
+      required: true, message: '请输入设备序列号', trigger: 'blur'
+    }],
+    tenantId: [{
+      required: true, message: '请选择所属企业', trigger: 'change'
+    }],
+    storeId: [{
+      required: true, message: '请选择所属门店', trigger: 'change'
+    }]
+  }
+})
+
+const total = ref(0)
+
+const grid = ref(false)
+
+// 列表
+const getList = () => {
+  loading.value = true
+  list(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data
+      total.value = res.total
+      loading.value = false
+    }
+  })
+}
+getList()
+
+// 搜索
+const getSearch = () => {
+  data.form.pageNum = 1
+  getList()
+}
+
+// 重置
+const getRefresh = () => {
+  data.form = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  getList()
+}
+
+// 弹窗
+const dialogVisible = ref(false)
+// 弹窗标题
+const title = ref('')
+// 打开弹窗
+function getDialog(id) {
+  dialogVisible.value = true
+  title.value = '关联'
+  if (id) {
+    getDetail(id)
+  }
+}
+
+// 详情
+const getDetail = (id) => {
+  title.value = '编辑'
+  detail({ id: id }).then(res => {
+    if (res.code === 0) {
+      data.dialogForm = res.data
+    }
+  })
+}
+
+// 取消
+const getClose = () => {
+  dialogVisible.value = false
+  data.dialogForm = {}
+  proxy.$refs.dialogForm.resetFields()
+}
+
+// 提交
+const getSubmit = () => {
+  proxy.$refs.dialogForm.validate(async valid => {
+    if (valid) {
+      if (data.dialogForm.id) {
+        await edit(data.dialogForm).then(res => {
+          if (res.code !== 0) {
+            return false
+          }
+        })
+      } else {
+        await submit(data.dialogForm).then(res => {
+          if (res.code !== 0) {
+            return false
+          }
+        })
+      }
+      proxy.$modal.msgSuccess('提交成功!')
+      getClose()
+      getList()
+    } else {
+      return false
+    }
+  })
+}
+
+// 修改状态
+const getChange = (id, title) => {
+  change({ id: id }).then(res => {
+    if (res.code === 0) {
+      proxy.$modal.msgSuccess(`${title}成功!`)
+      getList()
+    }
+  })
+}
+
+// 删除
+const getDelete = (row) => {
+  proxy.$modal.confirm(`是否删除设备名称为:${row.deviceName}的数据?`).then(() => {
+    remove({ id: row.id }).then(res => {
+      if (res.cdoe === 0) {
+        proxy.$modal.msgSuccess('删除成功!')
+        getList()
+      }
+    })
+  })
+}
+
+
+// 监听
+watch(() => data.dialogForm.tenantId, (val) => {
+  if (val) {
+    storeData.form.tenantId = val
+    getStore()
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.card_box {
+  display: flex;
+}
+
+.card {
+  display: flex;
+  flex-direction: column;
+  padding: 10px 10px 20px;
+  width: 185px;
+  border-radius: 10px;
+  box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
+  margin-right: 40px;
+  margin-bottom: 30px;
+
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .state {
+      width: 10px;
+      height: 10px;
+      background-color: #8aff5b;
+      border-radius: 50%;
+      display: inline-block;
+    }
+  }
+
+  .icon {
+    padding: 0 20px;
+    height: 110px;
+    margin: 15px 0;
+  }
+
+  .name {
+    font-size: 14px;
+    text-align: center;
+  }
+}
+</style>

+ 157 - 0
src/views/store/group/index.vue

@@ -0,0 +1,157 @@
+<template>
+  <div class='app-container'>
+    <el-form>
+      <el-form-item>
+        <el-button type="primary" icon="Plus" plain @click="getDialog()">新增分组</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table :data="data.tableData" v-loading="loading">
+      <el-table-column label="分组名称" prop="name" align="center"></el-table-column>
+      <el-table-column label="相关设备" prop="deviceNum" align="center"></el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="getDialog(scope.row.id)">编辑</el-button>
+          <el-button type="danger" link @click="getDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" v-model:page="data.form.pageNum" v-model:limit="data.form.pageSize"
+      @pagination="getList" />
+    <!-- 弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="title" width="500" :before-close="getClose">
+      <el-form :model="data.dialogForm" ref="dialogForm" :rules="data.rules">
+        <el-form-item label="分组名称:" prop="name">
+          <el-input v-model="data.dialogForm.name" placeholder="请输入分组名称" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="getClose">取消</el-button>
+        <el-button type="primary" @click="getSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { list, submit, detail, remove, edit } from "@/api/store/group"
+let { proxy } = getCurrentInstance()
+
+const goBack = () => {
+  proxy.$tab.closeOpenPage('/device/control')
+}
+
+// 遮罩层
+const loading = ref(false)
+
+// 数据
+const data = reactive({
+  // 表单
+  form: {
+    pageNum: 1,
+    pageSize: 10
+  },
+  // 列表
+  tableData: [],
+  // 弹窗表单
+  dialogForm: {},
+  // 校验
+  rules: {
+    name: [{
+      required: true, message: '请输入分组名称', trigger: 'blur'
+    }]
+  }
+})
+
+// 总数据
+const total = ref(0)
+
+// 列表
+const getList = () => {
+  loading.value = true
+  list(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data
+      total.value = res.total
+      loading.value = false
+    }
+  })
+}
+getList()
+
+// 弹窗
+const dialogVisible = ref(false)
+// 弹窗标题
+const title = ref('')
+// 打开弹窗
+function getDialog(id) {
+  dialogVisible.value = true
+  title.value = '新增'
+  if (id) {
+    getDetail(id)
+  }
+}
+
+// 详情
+const getDetail = (id) => {
+  title.value = '编辑'
+  detail({ id: id }).then(res => {
+    if (res.code === 0) {
+      data.dialogForm = res.data
+    }
+  })
+}
+
+// 新增
+const getSubmit = () => {
+  proxy.$refs.dialogForm.validate(valid => {
+    if (valid) {
+      if (data.dialogForm.id) {
+        edit(data.dialogForm).then(res => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess('编辑成功!')
+            getClose()
+            getList()
+          }
+        })
+      } else {
+        submit(data.dialogForm).then(res => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess('新增成功!')
+            getClose()
+            getList()
+          }
+        })
+      }
+    } else {
+      return false
+    }
+  })
+}
+
+// 关闭
+const getClose = () => {
+  dialogVisible.value = false
+  data.dialogForm = {}
+  proxy.$refs.dialogForm.resetFields()
+}
+
+// 删除
+const getDelete = (row) => {
+  proxy.$modal.confirm(`是否删除分组名称为:${row.name}的数据?`).then(() => {
+    remove({ id: row.id }).then(res => {
+      if (res.code === 0) {
+        proxy.$modal.msgSuccess('删除成功!')
+        getList()
+      }
+    })
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20px;
+}
+</style>

+ 232 - 0
src/views/store/list/index.vue

@@ -0,0 +1,232 @@
+<template>
+  <div class='app-container'>
+    <el-form inline>
+      <el-form-item label="门店名称或编码:">
+        <el-input v-model="data.form.name" placeholder="请输入门店名称或编码" clearable />
+      </el-form-item>
+      <el-form-item label="门店分组:">
+        <el-select v-model="data.form.groupId" placeholder="请选择门店分组" filterable remote :remote-method="groupRemote"
+          remote-show-suffix>
+          <el-option v-for="item in groupData.options" :key="item.id" :value="item.id" :label="item.name" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属企业:">
+        <el-select v-model="data.form.tenantId" placeholder="请选择所属企业" style="width: 200px" filterable remote
+          :remote-method="businessRemote" remote-show-suffix clearable>
+          <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="getSearch">搜索</el-button>
+        <el-button icon="Refresh" @click="getRefresh">重置</el-button>
+        <el-button type="primary" plain icon="Plus" @click="getDialog()">新增</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 列表 -->
+    <el-table :data="data.tableData" v-loading="loading">
+      <el-table-column label="门店名称" prop="name" align="center"></el-table-column>
+      <el-table-column label="门店编码" prop="code" align="center"></el-table-column>
+      <el-table-column label="门店地区" align="center">
+        <template #default="scope">
+          {{ scope.row.provinceName }} - {{ scope.row.cityName }} - {{ scope.row.areaName }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="getDialog(scope.row.id)">编辑</el-button>
+          <el-button type="danger" link @click="getDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" v-model:page="data.form.pageNum" v-model:limit="data.form.pageSize"
+      @pagination="getList" />
+    <!-- 弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="title" width="500">
+      <el-form label-width="100px" :model="data.dialogForm" ref="dialogForm" :rules="data.rules">
+        <el-form-item label="门店名称:" prop="name">
+          <el-input v-model="data.dialogForm.name" placeholder="请输入门店名称" />
+        </el-form-item>
+        <el-form-item label="门店编码:" prop="code">
+          <el-input v-model="data.dialogForm.code" placeholder="请输入门店编码" />
+        </el-form-item>
+        <el-form-item label="所属账号:" prop="userId">
+          <el-select v-model="data.dialogForm.userId" placeholder="请选择所属账号" filterable remote
+            :remote-method="userRemote" remote-show-suffix>
+            <el-option v-for="item in userData.options" :key="item.userId" :value="item.userId"
+              :label="item.userName" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="所属企业:" prop="tenantId">
+          <el-select v-model="data.dialogForm.tenantId" placeholder="请选择所属企业" filterable remote
+            :remote-method="businessRemote" remote-show-suffix>
+            <el-option v-for="item in businessData.options" :key="item.id" :value="item.id" :label="item.name" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="所属城市:" prop="provinceId">
+          <el-cascader v-model="data.areaList" :options="areaData.options" placeholder="请选择所属城市"
+            :props="{ value: 'id', label: 'name', children: 'childList', expandTrigger: 'hover' }"
+            style="width: 100%;" />
+        </el-form-item>
+        <el-form-item label="门店分组:" prop="groupId">
+          <el-select v-model="data.dialogForm.groupId" placeholder="请选择门店分组" filterable remote
+            :remote-method="groupRemote" remote-show-suffix>
+            <el-option v-for="item in groupData.options" :key="item.id" :value="item.id" :label="item.name" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button>取消</el-button>
+        <el-button type="primary">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { list, detail, remove, submit, edit } from '@/api/store/list'
+
+import { useBusinessSelect, useUserSelect, useGroupSelect, useAreaSelect } from '@/hooks/index'
+
+const { proxy } = getCurrentInstance()
+
+const { businessData, getBusiness, businessRemote } = useBusinessSelect(true)
+getBusiness()
+const { userData, getUser, userRemote } = useUserSelect()
+getUser()
+const { groupData, getGroup, groupRemote } = useGroupSelect()
+getGroup()
+const { areaData, getArea } = useAreaSelect()
+getArea()
+
+// 遮罩层
+const loading = ref(false)
+
+const data = reactive({
+  // 表单
+  form: {
+    pageNum: 1,
+    pageSize: 10
+  },
+  // 列表
+  tableData: [],
+  // 弹窗表单
+  dialogForm: {},
+  rules: {
+    name: [{
+      required: true, message: '请输入门店名称', trigger: 'blur'
+    }],
+    code: [{
+      required: true, message: '请输入门店编码', trigger: 'blur'
+    }],
+    userId: [{
+      required: true, message: '请选择所属账号', trigger: 'change'
+    }],
+    tenantId: [{
+      required: true, message: '请选择所属企业', trigger: 'change'
+    }],
+    provinceId: [{
+      required: true, message: '请选择所属城市', trigger: 'change'
+    }],
+    groupId: [{
+      required: true, message: '请选择门店分组', trigger: 'change'
+    }]
+  }
+})
+
+// 总数据
+const total = ref(0)
+
+// 列表
+const getList = () => {
+  loading.value = true
+  list(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data
+      total.value = res.total
+      loading.value = false
+    }
+  })
+}
+getList()
+
+// 搜索
+const getSearch = () => {
+  data.form.pageNum = 1
+  getList()
+}
+
+// 重置
+const getRefresh = () => {
+  data.form = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  getList()
+}
+
+
+
+
+
+// 弹窗
+const dialogVisible = ref(false)
+// 弹窗标题
+const title = ref('')
+// 打开弹窗
+function getDialog(id) {
+  dialogVisible.value = true
+  title.value = '新增'
+  if (id) {
+    getDetail(id)
+  }
+}
+
+// 详情
+const getDetail = (id) => {
+  title.value = '编辑'
+  detail({ id: id }).then(res => {
+    if (res.code === 0) {
+      data.dialogForm = res.data
+    }
+  })
+}
+
+// 删除
+const getDelete = (row) => {
+  proxy.$modal.confirm(`是否删除门店名称为:${row.name}的数据?`).then(() => {
+    remove({ id: row.id }).then(res => {
+      if (res.code === 0) {
+        proxy.$modal.msgSuccess('删除成功!')
+        getList()
+      }
+    })
+  })
+}
+
+// 提交
+const getSubmit = () => {
+  proxy.$refs.dialogForm.validate(valid => {
+    if (valid) {
+      if (data.dialogForm.id) {
+        edit(data.dialogForm.id).then(res => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess('编辑成功!')
+            getClose()
+            getList()
+          }
+        })
+      } else {
+        submit().then(res => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess('新增成功!')
+            getClose()
+            getList()
+          }
+        })
+      }
+    } else {
+      return false
+    }
+  })
+}
+</script>

+ 1 - 1
vite.config.js

@@ -31,7 +31,7 @@ export default defineConfig(({ mode, command }) => {
       proxy: {
         // https://cn.vitejs.dev/config/#server-proxy
         '/dev-api': {
-          // target: 'https://o3tapi.radio1964.com/admin',
+          // target: 'http://testclient.cloud.airsmartser.com/admin',
           // target: 'http://192.168.198.255:2130', // 付
           target: 'http://192.168.0.174:2130', // 陈
           changeOrigin: true,