scene-select.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <template>
  2. <Modal
  3. width="800px"
  4. :title="$t('scene.add')"
  5. :visible="visible"
  6. @ok="okHandler"
  7. @cancel="visible = false"
  8. :okText="$t('sys.enter')"
  9. :cancelText="$t('sys.cancel')"
  10. class="model-table"
  11. >
  12. <div>
  13. <div className="model-header">
  14. <p class="header-desc">
  15. {{ $t("material.selectCount")
  16. }}<span>( {{ rowSelection.selectedRowKeys.length }} )</span>
  17. </p>
  18. <Search
  19. className="content-header-search"
  20. :placeholder="$t('material.search')"
  21. v-model:value="keyword"
  22. allow-clear
  23. style="width: 244px"
  24. />
  25. </div>
  26. <div class="table-layout" v-if="origin.length">
  27. <Tabs v-model:activeKey="type">
  28. <TabPane v-for="current in types" :key="current" :tab="current">
  29. <Table
  30. v-if="typeFilterScenes[current].length"
  31. :row-key="(record: Scene) => record.num"
  32. :columns="cloumns"
  33. :rowSelection="rowSelection"
  34. :data-source="typeFilterScenes[current]"
  35. :pagination="{ ...params, current: params.pageNum }"
  36. @change="handleTableChange"
  37. />
  38. <div style="padding: 1px" v-else>
  39. <Empty
  40. :description="$t('sys.unSearchData')"
  41. :image="Empty.PRESENTED_IMAGE_SIMPLE"
  42. className="ant-empty ant-empty-normal"
  43. />
  44. </div>
  45. </TabPane>
  46. </Tabs>
  47. </div>
  48. <div style="padding: 1px" v-else>
  49. <Empty
  50. :description="keyword.length ? $t('sys.unSearchData') : $t('sys.undata')"
  51. :image="Empty.PRESENTED_IMAGE_SIMPLE"
  52. className="ant-empty ant-empty-normal"
  53. />
  54. </div>
  55. </div>
  56. </Modal>
  57. <div class="slot-layout">
  58. <Dropdown placement="bottom">
  59. <slot></slot>
  60. <template #overlay>
  61. <Menu>
  62. <MenuItem @click="visible = true">{{ $t("scene.manage") }}</MenuItem>
  63. <MenuItem @click="selectModel">{{ $t("material.name") }}</MenuItem>
  64. </Menu>
  65. </template>
  66. </Dropdown>
  67. </div>
  68. </template>
  69. <script lang="ts" setup>
  70. import {
  71. Modal,
  72. Input,
  73. Table,
  74. Empty,
  75. Tabs,
  76. TabPane,
  77. Dropdown,
  78. Menu,
  79. MenuItem,
  80. TableProps,
  81. } from "ant-design-vue";
  82. import { computed, nextTick, ref, watch, watchEffect } from "vue";
  83. import { scenes, save, SceneTypeDesc } from "@/store";
  84. import { asyncTimeout, createLoadPack, diffArrayChange } from "@/utils";
  85. import {
  86. fuseModels,
  87. createFuseModels,
  88. addFuseModel,
  89. fuseModelsLoaded,
  90. initialScenes,
  91. } from "@/store";
  92. import { fetchScenesAll, SceneType, uploadMaterialToModel, type Scene } from "@/api";
  93. import { activeModel, getSceneModel } from "@/sdk";
  94. import { selectMaterials } from "@/components/materials/quisk";
  95. import { lang, ui18n } from "@/lang";
  96. import { custom } from "@/env";
  97. import { actionItems, currentItem } from "@/views/merge";
  98. type Key = string;
  99. const Search = Input.Search;
  100. const spStr = '----ll----'
  101. const selectIds = computed(() => fuseModels.value.filter(item => item.type !== SceneType.SWMX).map((item) => item.raw.isObj + spStr + item.raw.num));
  102. const visible = ref(false);
  103. const keyword = ref("");
  104. const SceneGroupTypeDesc: any = lang.scene.typeRaws
  105. const types = computed(() => [
  106. ...new Set(Object.values(SceneGroupTypeDesc) as any[]).values(),
  107. ]);
  108. const type = ref(types.value[0]);
  109. const handleTableChange: TableProps["onChange"] = (pag) => {
  110. params.value.pageSize = pag.pageSize!;
  111. params.value.pageNum = pag.current!;
  112. };
  113. const meshParams = ref({ isObj: 1, pageNum: 1, pageSize: 12, total: 0 })
  114. const cloudParams = ref({ isObj: 0, pageNum: 1, pageSize: 12, total: 0 })
  115. const meshList = ref<Scene[]>([])
  116. const cloudList = ref<Scene[]>([])
  117. const params = computed(() => type.value === ui18n.t('scene.typeRaws.0') ? meshParams.value : cloudParams.value)
  118. let loadCount = 0
  119. watchEffect(() => {
  120. const list = type.value === ui18n.t('scene.typeRaws.2') ? meshList : cloudList
  121. const currentCount = ++loadCount
  122. fetchScenesAll(params.value).then((data) => {
  123. console.log('====>', currentCount, loadCount, params.value)
  124. if (currentCount === loadCount) {
  125. params.value.total = data.total
  126. list.value = data.list.map(item => ({ ...item, type: type.value, num: item.isObj + spStr + item.num })) as any
  127. console.log(data.total, list.value)
  128. }
  129. })
  130. })
  131. const origin = computed(() => [...meshList.value, ...cloudList.value]);
  132. const typeFilterScenes = computed(() => {
  133. const typeScenes: any = {};
  134. for (const type of types.value) {
  135. typeScenes[type] = origin.value
  136. .filter((item) => item.name && item.num && item.name.includes(keyword.value))
  137. .filter((item) => item.type === type);
  138. }
  139. return typeScenes;
  140. });
  141. let cache = {} as any
  142. watchEffect(() => {
  143. if (!visible.value) {
  144. cache = {}
  145. }
  146. })
  147. const selects = ref<Key[]>(selectIds.value);
  148. watchEffect(() => console.error(selects.value), {flush: 'sync'})
  149. const rowSelection: any = ref({
  150. selectedRowKeys: selects,
  151. hideSelectAll: true,
  152. onChange: (ids: string[]) => {
  153. console.error(ids)
  154. ids = ids.filter(id => !selectIds.value.includes(id))
  155. cache[type.value] = ids
  156. const curIds = [...selectIds.value]
  157. for (const key in cache) {
  158. curIds.push(...cache[key])
  159. }
  160. selects.value = curIds
  161. },
  162. getCheckboxProps: (record: Scene) => ({
  163. disabled: selectIds.value.includes(record.num),
  164. }),
  165. });
  166. const cloumns = [
  167. {
  168. width: "400px",
  169. title: ui18n.t('scene.tabs.name'),
  170. dataIndex: "name",
  171. key: "name",
  172. },
  173. {
  174. title: ui18n.t('scene.tabs.type'),
  175. dataIndex: "type",
  176. key: "type",
  177. },
  178. {
  179. title: ui18n.t('scene.tabs.createTime'),
  180. dataIndex: "createTime",
  181. key: "createTime",
  182. },
  183. ];
  184. const addModelHandler = createLoadPack(async (attachs: any[]) => {
  185. await save();
  186. const items = attachs.map((attach) => {
  187. return {
  188. data: createFuseModels(),
  189. attach
  190. }
  191. });
  192. const addPromises = items.map(item => addFuseModel(item.data, item.attach));
  193. const addModels = await Promise.all(addPromises);
  194. await new Promise<void>((resolve) => {
  195. nextTick(() => {
  196. const stop = watchEffect(() => {
  197. if (fuseModelsLoaded.value) {
  198. nextTick(() => {
  199. stop();
  200. resolve();
  201. });
  202. }
  203. });
  204. });
  205. });
  206. items.forEach(item => {
  207. if (getSceneModel(item.data)) {
  208. item.data.rotation = getSceneModel(item.data)!.getDefaultRotation();
  209. }
  210. });
  211. await asyncTimeout(100);
  212. await save();
  213. activeModel({ showMode: 'fuse', active: addModels[addModels.length - 1] })
  214. // custom.currentModel = addModels[addModels.length - 1]
  215. currentItem.value = actionItems[0]
  216. });
  217. const okHandler = createLoadPack(async () => {
  218. const attachs = selects.value.map(item => {
  219. const isObj = Number(item.split(spStr)[0])
  220. const num = item.split(spStr)[1]
  221. return {isObj, num}
  222. }).filter(({isObj, num}) =>
  223. num && !fuseModels.value.some((model) => model.raw.isObj === isObj && model.raw.num === num)
  224. )
  225. await addModelHandler(attachs);
  226. visible.value = false;
  227. });
  228. watch(visible, (visible, oldvisible) => {
  229. if (visible !== oldvisible) {
  230. keyword.value = "";
  231. selects.value = selectIds.value;
  232. visible && initialScenes();
  233. }
  234. });
  235. const selectModel = async () => {
  236. let list = await selectMaterials({
  237. uploadFormat: ["zip"],
  238. format: ["obj", "ply", "las", "laz", "b3dm", "shp", "osgb", "glb"],
  239. maxSize: 2 * 1024 * 1024 * 1024,
  240. });
  241. if (!list?.length) return;
  242. list = list.filter(item => item.uploadId)
  243. // const modelList = await Promise.all(list.map(item => uploadMaterialToModel(item.uploadId!)))
  244. const attachs: any[] = []
  245. for (let i = 0; i < list.length; i++) {
  246. const uploadId = list[i].uploadId
  247. if (uploadId ) {
  248. attachs.push({ uploadId })
  249. }
  250. }
  251. await addModelHandler(attachs);
  252. };
  253. </script>
  254. <style lang="less" scoped>
  255. .model-header {
  256. display: flex;
  257. justify-content: space-between;
  258. padding-bottom: 24px;
  259. align-items: center;
  260. }
  261. .table-layout {
  262. max-height: 500px;
  263. overflow-y: auto;
  264. }
  265. .slot-layout {
  266. display: flex;
  267. align-items: center;
  268. height: 100%;
  269. }
  270. </style>
  271. <style lang="less">
  272. .model-header .header-desc {
  273. margin-bottom: 0;
  274. }
  275. .ant-modal-root .ant-table-tbody > tr > td {
  276. word-break: break-all;
  277. }
  278. </style>