MiniMapConfig.vue 8.8 KB

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