MiniMapConfig.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <div class="page" @click="handleWholeClick">
  3. <div class="page-head flex items-center">
  4. <el-icon size="20"><ArrowLeft /></el-icon>
  5. <div class="head-opt flex-1 flex justify-between items-center">
  6. <div>
  7. <span>场景:</span>
  8. <el-tree-select
  9. v-model="selectedShopCode"
  10. :data="scenesTree"
  11. accordion
  12. :render-after-expand="false"
  13. :default-expand-all="true"
  14. :teleported="false"
  15. placeholder="请选择相关场景"
  16. @change="changeShop"
  17. />
  18. </div>
  19. <div class="flex">
  20. <!-- <el-button @click="mapEditor.toJson">tojson</el-button> -->
  21. <el-upload
  22. class="avatar-uploader flex justify-center items-center"
  23. action="/skyeye-admin-api/layout/uploadPicture"
  24. :show-file-list="false"
  25. :on-success="handleAvatarSuccess"
  26. :with-credentials="true"
  27. name="file"
  28. :data="{ workshopId: selectedShopDetail?.id }"
  29. >
  30. <el-button style="font-size: 12px" :icon="Refresh" :disabled="!hasBg">
  31. 替换照片
  32. </el-button>
  33. </el-upload>
  34. <el-button
  35. @click="handleSave"
  36. style="margin-left: 40px"
  37. type="primary"
  38. :disabled="!selectedShopCode"
  39. >保存为布局
  40. </el-button>
  41. </div>
  42. </div>
  43. </div>
  44. <div class="paint-tool flex">
  45. <div class="camera-list">
  46. <div>
  47. <span class="label-text flex">相机列表:</span>
  48. <ElInput
  49. class="search-put"
  50. style="margin: 10px 0; width: 230px"
  51. placeholder="请输入搜索内容"
  52. v-model="searchKey"
  53. :suffix-icon="Search"
  54. />
  55. </div>
  56. <span v-if="filterShopCameraList.length == 0" class="ml-1" style="color: #3f3f3f">
  57. 提示:请先选择相应场景和图片
  58. </span>
  59. <el-scrollbar v-else style="position: relative; height: calc(100% - 90px)">
  60. <div
  61. v-for="item in filterShopCameraList"
  62. :key="item.code"
  63. class="camera-item flex justify-start items-center"
  64. :class="{
  65. isAdded: isAddedCamera(item.code),
  66. isActive: item.code === activeCameraId,
  67. }"
  68. @click="handleAddCamera(item.code)"
  69. >
  70. <span class="camera-id">{{ item.name }}</span>
  71. <el-popover
  72. placement="bottom-start"
  73. trigger="hover"
  74. :content="item.name"
  75. :teleported="false"
  76. >
  77. <template #reference>
  78. <span class="space-name">{{ item.workSpaceName }}</span>
  79. </template>
  80. </el-popover>
  81. </div>
  82. </el-scrollbar>
  83. </div>
  84. <div ref="drawContainer" id="drawContainer" class="draw-container">
  85. <div id="editContainer" v-moveable:1></div>
  86. <el-upload
  87. v-if="!hasBg"
  88. class="upload-icon flex justify-center items-center"
  89. action="/skyeye-admin-api/layout/uploadPicture"
  90. :show-file-list="false"
  91. :before-upload="handleBeforeUpload"
  92. :on-success="handleAvatarSuccess"
  93. :with-credentials="true"
  94. name="file"
  95. :data="{ workshopId: selectedShopDetail?.id }"
  96. >
  97. <img src="~@/assets/images/img-upload.png" />
  98. </el-upload>
  99. </div>
  100. </div>
  101. </div>
  102. </template>
  103. <script setup lang="ts">
  104. import useMiniMap from './use-mini-map';
  105. import { storeToRefs } from 'pinia';
  106. import { ElMessage, ElInput } from 'element-plus';
  107. import { onMounted, ref } from 'vue';
  108. import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
  109. import { computed } from 'vue';
  110. import { Search, Refresh } from '@element-plus/icons-vue';
  111. import useMapEditor from './hooks/useMapEditor';
  112. const mapEditor = useMapEditor();
  113. const { activeCameraId, addedCameras, bgImgUrl } = mapEditor;
  114. const miniMap = useMiniMap();
  115. const { scenesTree, shopCameraList, selectedShopCode, selectedShopDetail } = storeToRefs(miniMap);
  116. const { getScenesTree, getShowCameras, getMapLayout } = miniMap;
  117. const drawContainer = ref<HTMLDivElement>();
  118. const searchKey = ref('');
  119. // 是否已有背景图
  120. const hasBg = ref(false);
  121. const handleBeforeUpload = () => {
  122. if (!selectedShopCode.value) {
  123. ElMessage.error({
  124. message: '请先选择车间',
  125. });
  126. return false;
  127. }
  128. };
  129. /** 判断相机是否已经添加 */
  130. const isAddedCamera = (cameraId: string) => {
  131. const index = addedCameras.value.findIndex((item) => item === cameraId);
  132. return index >= 0;
  133. };
  134. const handleAvatarSuccess = (e) => {
  135. bgImgUrl.value = e.data;
  136. mapEditor.addBg();
  137. hasBg.value = true;
  138. };
  139. const changeShop = (code: string) => {
  140. mapEditor.resetMap();
  141. getShopContent(code);
  142. hasBg.value = false;
  143. };
  144. const getShopContent = (code: string) => {
  145. getShowCameras(code);
  146. getMapLayout(code).then((res) => {
  147. if (!res) {
  148. return;
  149. }
  150. hasBg.value = true;
  151. mapEditor.createMap(res);
  152. });
  153. };
  154. const handleWholeClick = (e) => {
  155. if (e.button === 0) {
  156. mapEditor.destoryOptBlock();
  157. }
  158. };
  159. onMounted(() => {
  160. getScenesTree({ level: 2, valueKey: 'code', labelKey: 'name', disabled: true });
  161. mapEditor.initContainer({
  162. container: 'editContainer',
  163. width: drawContainer.value!.clientWidth,
  164. height: drawContainer.value!.clientHeight,
  165. });
  166. if (selectedShopCode.value) {
  167. getShopContent(selectedShopCode.value);
  168. }
  169. });
  170. const filterShopCameraList = computed(() => {
  171. const k = searchKey.value.trim();
  172. if (!k) return shopCameraList.value;
  173. return shopCameraList.value.filter((x) => x.code?.includes(k) || x.workSpaceName?.includes(k));
  174. });
  175. const handleAddCamera = (cameraId: string) => {
  176. if (!hasBg.value) {
  177. ElMessage.warning({
  178. message: '请先添加背景图片',
  179. });
  180. return;
  181. }
  182. mapEditor.addCamera(cameraId);
  183. };
  184. const handleSave = () => {
  185. const layout = mapEditor.toJson();
  186. updateMinMapViewLayoutApi({ layout, targetId: String(selectedShopDetail.value?.id) }).then(
  187. (res) => {
  188. console.log('updateMinMapViewLayoutApi', res);
  189. ElMessage.success('保存成功');
  190. },
  191. );
  192. };
  193. </script>
  194. <style scoped lang="scss">
  195. .page {
  196. }
  197. .page-head {
  198. height: 54px;
  199. padding-left: 15px;
  200. background-color: #ffffff;
  201. }
  202. .head-opt {
  203. margin-left: 20px;
  204. padding-right: 15px;
  205. font-size: 14px;
  206. color: #3f3f3f;
  207. }
  208. .avatar-uploader {
  209. /* width: 120px; */
  210. /* height: 30px; */
  211. /* border: 1px solid #eee; */
  212. border-radius: 4px;
  213. margin-left: 30px;
  214. }
  215. .upload-icon {
  216. position: absolute;
  217. top: 0;
  218. right: 0;
  219. left: 0;
  220. bottom: 0;
  221. margin: auto;
  222. }
  223. .paint-tool {
  224. position: relative;
  225. height: calc(100vh - 138px);
  226. margin-top: 2px;
  227. }
  228. .camera-list {
  229. width: 250px;
  230. padding: 0 10px;
  231. background-color: #ffffff;
  232. }
  233. .label-text {
  234. font-size: 14px;
  235. font-weight: 600;
  236. margin: 10px 0 5px 10px;
  237. }
  238. .camera-item {
  239. height: 32px;
  240. font-size: 14px;
  241. padding-left: 8px;
  242. font-weight: 400;
  243. color: #404040;
  244. line-height: 14px;
  245. cursor: pointer;
  246. &:hover {
  247. background-color: #e6f7ff;
  248. color: #1890ff;
  249. }
  250. }
  251. .isAdded {
  252. color: #1890ff;
  253. cursor: not-allowed;
  254. }
  255. .isActive {
  256. background-color: #e6f7ff;
  257. color: #1890ff;
  258. }
  259. .camera-item-disabled {
  260. color: #c6c6c6;
  261. }
  262. .camera-id {
  263. width: 110px;
  264. }
  265. .space-name {
  266. width: 120px;
  267. white-space: nowrap;
  268. overflow: hidden;
  269. text-overflow: ellipsis;
  270. }
  271. .draw-container {
  272. position: relative;
  273. width: calc(100% - 300px);
  274. margin: 20px;
  275. overflow: hidden;
  276. }
  277. :deep(.search-put .el-input__wrapper) {
  278. background-color: #f0f2f5;
  279. }
  280. :deep(.el-popper__arrow) {
  281. display: none;
  282. }
  283. :deep(.el-tree-node__content:hover) {
  284. background: #e6f4ff;
  285. }
  286. :deep(.el-button--primary) {
  287. --el-button-disabled-bg-color: #bfbfbf;
  288. }
  289. :deep(.el-popover) {
  290. width: unset !important;
  291. min-width: 110px;
  292. text-align: center;
  293. font-weight: 400;
  294. }
  295. </style>
  296. ./MapBase/useCameraMap ./MapBase/CameraMapBak