sceneGroupInEditor.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. <template>
  2. <div
  3. class="scene-group"
  4. >
  5. <div
  6. class="top-bar"
  7. :class="isConfirmingDeletion ? '' : 'show-icons-on-hover'"
  8. @click="onClickTopBar"
  9. @dragstart="onDragStart"
  10. @dragenter.self="onDragEnter"
  11. @dragend="onDragEnd"
  12. @dragleave.self="onDragLeave"
  13. draggable="true"
  14. :style="{
  15. paddingLeft: topBarPaddingLeft,
  16. }"
  17. >
  18. <div class="drag-image" ref="drag-image">
  19. <i class="iconfont icon-editor_folder_off"></i>
  20. </div>
  21. <i class="iconfont icon-edit_input_arrow icon-expand" :class="isExpanded ? '' : 'collapsed'"></i>
  22. <i v-show="isExpanded" class="iconfont icon-editor_folder_on folder_expanded"></i>
  23. <i v-show="!isExpanded" class="iconfont icon-editor_folder_off folder_collapsed"></i>
  24. <template v-if="!isRenaming">
  25. <span class="group-name" v-title="$i18n.t(`zh_key.${groupNode.name}`).indexOf('zh_key')>-1?groupNode.name:$i18n.t(`zh_key.${groupNode.name}`)">{{
  26. $i18n.t(`zh_key.${groupNode.name}`).indexOf('zh_key')>-1?groupNode.name:$i18n.t(`zh_key.${groupNode.name}`)}}</span>
  27. <i v-show="level === 1"
  28. class="iconfont icon-editor_list_add icon-add"
  29. v-tooltip="$i18n.t('navigation.add_two_group')"
  30. @click.stop="onRequestForAddGroup"
  31. >
  32. </i>
  33. <i
  34. class="iconfont icon-editor_list_image icon-image"
  35. v-tooltip="$i18n.t('navigation.add_pano_or_scene')"
  36. @click.stop="onRequestForAddScene"
  37. v-show="
  38. level === 2 ||
  39. (
  40. level === 1 &&
  41. (
  42. groupNode.children.length === 0 ||
  43. (
  44. groupNode.children.length === 1 &&
  45. (groupNode.children[0].name === '默认二级分组'||groupNode.children[0].name === $i18n.t('navigation.default_group_two'))
  46. )
  47. )
  48. )
  49. "
  50. >
  51. </i>
  52. <i
  53. class="iconfont icon-editor_list_edit icon-edit"
  54. v-tooltip="$i18n.t('navigation.rename')"
  55. @click.stop="onClickForRename"
  56. >
  57. </i>
  58. <i
  59. class="iconfont icon-editor_list_delete icon-delete"
  60. v-tooltip="$i18n.t('navigation.delete')"
  61. @click.stop="onRequestForDelete"
  62. >
  63. </i>
  64. <div class="deletion-confirm-wrap">
  65. <div class="deletion-confirm" :class="isConfirmingDeletion ? 'show' : 'hide'"
  66. v-clickoutside="onRequestForCancelDelete"
  67. @click.stop="onConfirmDelete"
  68. >
  69. {{$i18n.t('navigation.delete')}}
  70. </div>
  71. </div>
  72. </template>
  73. <input
  74. v-if="isRenaming"
  75. class="group-title-input"
  76. v-model.trim="newName"
  77. ref="input-for-rename"
  78. maxlength="50"
  79. :placeholder="$i18n.t('navigation.enter_name')"
  80. @blur="onInputNewNameComplete"
  81. @keydown.enter="onInputEnter"
  82. @click.stop
  83. />
  84. </div>
  85. <div class="group-content" v-if="isExpanded">
  86. <template v-if="!(groupNode.children.length === 1 &&
  87. (groupNode.children[0].name === '默认二级分组' || groupNode.children[0].name === $i18n.t('navigation.default_group_two')))">
  88. <InsertPositionTip
  89. position-debug="1"
  90. :indentLevel="level + 1"
  91. :topologyLevel="level + 1"
  92. :parentNode="groupNode"
  93. :index="0"
  94. ></InsertPositionTip>
  95. <div
  96. v-for="(item, index) of groupNode.children"
  97. :key=item.id
  98. >
  99. <component
  100. :is="'SceneGroup'"
  101. ref="scene-group"
  102. v-if="!item.type"
  103. :groupNode="item"
  104. :level="level + 1"
  105. @renameScene="onRenameScene"
  106. @deleteScene="onDeleteScene"
  107. @renameGroup="onInnerGroupRename"
  108. @deleteGroup="onInnerGroupConfirmDelete"
  109. />
  110. <SceneInGroup
  111. v-else
  112. :style="{
  113. paddingLeft: sceneItemPaddingLeft,
  114. }"
  115. :sceneInfo="item"
  116. @rename="onRenameScene"
  117. @delete="onDeleteScene"
  118. />
  119. <InsertPositionTip
  120. position-debug="2"
  121. :indentLevel="level + 1"
  122. :topologyLevel="level + 1"
  123. :parentNode="groupNode"
  124. :index="index + 1"
  125. ></InsertPositionTip>
  126. </div>
  127. </template>
  128. <template v-else>
  129. <!-- 自动生成的默认二级分组不显示,里边的内容显示成直属于一级分组的效果。 -->
  130. <InsertPositionTip
  131. position-debug="3"
  132. :indentLevel="level + 1"
  133. :topologyLevel="level + 2"
  134. :parentNode="groupNode.children[0]"
  135. :index="0"
  136. ></InsertPositionTip>
  137. <div
  138. v-for="(item, index) of groupNode.children[0].children"
  139. :key=item.id
  140. >
  141. <SceneInGroup
  142. :style="{
  143. paddingLeft: sceneItemPaddingLeft,
  144. }"
  145. :sceneInfo="item"
  146. @rename="onRenameScene"
  147. @delete="onDeleteScene"
  148. />
  149. <InsertPositionTip
  150. position-debug="4"
  151. :indentLevel="level + 1"
  152. :topologyLevel="level + 2"
  153. :parentNode="groupNode.children[0]"
  154. :index="index + 1"
  155. ></InsertPositionTip>
  156. </div>
  157. </template>
  158. </div>
  159. <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
  160. <MaterialSelector
  161. :title="$i18n.t('gather.select_material')"
  162. @cancle="isShowSelectionWindow = false"
  163. @submit="onSubmitFromMaterialSelector"
  164. :selectableType="['pano', '3D']"
  165. :initialMaterialType="'pano'"
  166. :isMultiSelection="true"
  167. />
  168. </div>
  169. </div>
  170. </template>
  171. <script>
  172. import SceneInGroup from "@/components/sceneInGroupInEditor.vue";
  173. import MaterialSelector from "@/components/materialSelectorForEditor.vue";
  174. import InsertPositionTip from "@/components/insertPositionTipInEditor.vue";
  175. import { mapGetters, mapMutations } from "vuex";
  176. export default {
  177. name: 'SceneGroup',
  178. components: {
  179. SceneInGroup,
  180. MaterialSelector,
  181. InsertPositionTip,
  182. },
  183. props: {
  184. groupNode: {
  185. type: Object,
  186. required: true,
  187. },
  188. level: {
  189. type: Number,
  190. default: 1,
  191. }
  192. },
  193. data() {
  194. return {
  195. isExpanded: false,
  196. isRenaming: false,
  197. newName: '',
  198. isConfirmingDeletion: false,
  199. isShowSelectionWindow: false,
  200. dragEnterTimerId: null,
  201. }
  202. },
  203. computed: {
  204. ...mapGetters({
  205. info: "info",
  206. dragInfo: 'editorNavDragInfo',
  207. }),
  208. topBarPaddingLeft() {
  209. return 12 + (this.level - 1) * 12 + 'px'
  210. },
  211. sceneItemPaddingLeft() {
  212. return 18 + this.level * 12 + 'px'
  213. },
  214. },
  215. methods: {
  216. ...mapMutations({
  217. recordDragType: 'setEditorNavDragType',
  218. recordDragNode: 'setEditorNavDragNode',
  219. clearDragInfo: 'clearEditorNavDragInfo',
  220. }),
  221. onClickTopBar() {
  222. if (this.isConfirmingDeletion) {
  223. return
  224. }
  225. this.isExpanded = !this.isExpanded
  226. if (this.isExpanded) {
  227. this.$bus.emit('scene-group-expanded', this.groupNode.id, this.level)
  228. }
  229. },
  230. onOtherSceneGroupExpanded(id, level) {
  231. if (id !== this.groupNode.id && level === this.level) {
  232. this.isExpanded = false
  233. }
  234. },
  235. onRenameScene(...params) {
  236. this.$emit('renameScene', ...params)
  237. },
  238. onDeleteScene(...params) {
  239. this.$emit('deleteScene', ...params)
  240. },
  241. onRequestForAddGroup() {
  242. this.$emit('addGroup', this.groupNode.id)
  243. },
  244. onRequestForAddScene() {
  245. this.isShowSelectionWindow = true
  246. },
  247. onClickForRename() {
  248. this.isRenaming = true
  249. console.log(this.groupNode.name);
  250. this.newName = this.groupNode.name
  251. this.$nextTick(() => {
  252. // this.$refs['input-for-rename'].focus()
  253. this.$refs['input-for-rename'].select()
  254. this.$refs['input-for-rename'].scrollIntoView({
  255. behavior: 'smooth',
  256. })
  257. })
  258. },
  259. onInputNewNameComplete() {
  260. this.isRenaming = false
  261. console.log(this.groupNode.name);
  262. if (!this.newName.trim()) {
  263. return
  264. }
  265. if (this.newName !== this.groupNode.name) {
  266. this.$emit('renameGroup', this.groupNode.id, this.level, this.newName)
  267. }
  268. this.newName = ''
  269. },
  270. onInputEnter() {
  271. this.isRenaming = false // 会导致input blur,进而触发onInputNewNameComplete
  272. },
  273. onInnerGroupRename(...params) {
  274. this.$emit('renameGroup', ...params)
  275. },
  276. onRequestForDelete() {
  277. this.isConfirmingDeletion = true
  278. },
  279. onRequestForCancelDelete() {
  280. if (!this.isConfirmingDeletion) {
  281. return
  282. }
  283. // 先保持isConfirmingDeletion不变,因为onClickTopBar可能要用到
  284. setTimeout(() => {
  285. this.isConfirmingDeletion = false
  286. }, 0);
  287. },
  288. onConfirmDelete() {
  289. this.$emit('deleteGroup', this.groupNode.id, this.level)
  290. this.isConfirmingDeletion = false
  291. },
  292. onInnerGroupConfirmDelete(...params) {
  293. this.$emit('deleteGroup', ...params)
  294. },
  295. onSubmitFromMaterialSelector(selected) {
  296. let newScenes = []
  297. for (const item of selected) {
  298. if (item.materialType === 'pano') {
  299. newScenes.push({
  300. icon: item.icon,
  301. sceneCode: item.sceneCode,
  302. sceneTitle: item.name,
  303. category: this.level === 1 ? this.groupNode.children[0].id : this.groupNode.id,
  304. type: "pano",
  305. id: 's_' + this.$randomWord(true, 8, 8)
  306. })
  307. } else if (item.materialType === '3D') {
  308. newScenes.push({
  309. icon: item.thumb,
  310. sceneCode: item.num,
  311. sceneTitle: item.sceneName,
  312. category: this.level === 1 ? this.groupNode.children[0].id : this.groupNode.id,
  313. type: "4dkk",
  314. id:'s_'+this.$randomWord(true,8,8)
  315. })
  316. }
  317. }
  318. let allSuccess = true
  319. newScenes.forEach((item, i) => {
  320. let temp = this.info.scenes.find(eachScene => {
  321. return eachScene.sceneCode === item.sceneCode
  322. })
  323. if (temp) {
  324. setTimeout(() => {
  325. this.$msg.message(
  326. `${item.type == '4dkk' ? this.$i18n.t('navigation.scene_name') : this.$i18n.t('navigation.pano')}${this.$i18n.t('navigation.already_exists',{msg:item.sceneTitle})}`)
  327. }, i * 100)
  328. allSuccess = false
  329. return
  330. }
  331. this.info.scenes.push(item)
  332. })
  333. this.isShowSelectionWindow = false
  334. if (allSuccess) {
  335. this.$msg.success(this.$i18n.t('gather.success'))
  336. }
  337. },
  338. onDragStart(e) {
  339. this.recordDragType(`topologyGroupLevel${this.level}`)
  340. this.recordDragNode(this.groupNode)
  341. e.dataTransfer.setDragImage(this.$refs['drag-image'], -10, -18)
  342. },
  343. onDragEnter(e) {
  344. if (e.target.contains(e.relatedTarget) || (this.level === 2 && this.dragInfo.type.includes('Group'))) {
  345. return
  346. }
  347. this.dragEnterTimerId = setTimeout(() => {
  348. if (!this.isExpanded) {
  349. this.isExpanded = true
  350. this.$bus.emit('scene-group-expanded', this.groupNode.id, this.level)
  351. }
  352. }, 700)
  353. },
  354. onDragEnd() {
  355. this.clearDragInfo()
  356. clearTimeout(this.dragEnterTimerId)
  357. },
  358. onDragLeave(e) {
  359. if (e.target.contains(e.relatedTarget)) {
  360. return
  361. }
  362. clearTimeout(this.dragEnterTimerId)
  363. }
  364. },
  365. mounted() {
  366. this.$bus.on('scene-group-expanded', this.onOtherSceneGroupExpanded)
  367. },
  368. destroyed() {
  369. this.$bus.removeListener('scene-group-expanded', this.onOtherSceneGroupExpanded)
  370. }
  371. }
  372. </script>
  373. <style lang="less" scoped>
  374. .scene-group {
  375. .top-bar {
  376. position: relative;
  377. color: rgba(255, 255, 255, 0.6);
  378. height: 40px;
  379. border-radius: 4px;
  380. display: flex;
  381. align-items: center;
  382. margin-left: -12px;
  383. margin-right: -10px;
  384. padding-right: 10px;
  385. &:hover {
  386. background: #313131;
  387. > .group-name {
  388. width: 120px;
  389. }
  390. }
  391. &.show-icons-on-hover:hover {
  392. > .icon-add, .icon-image, .icon-edit, .icon-delete {
  393. display: block;
  394. }
  395. }
  396. > .drag-image {
  397. position: absolute;
  398. width: 40px;
  399. height: 40px;
  400. background: #313131;
  401. box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.5);
  402. border-radius: 4px;
  403. display: flex;
  404. justify-content: center;
  405. align-items: center;
  406. z-index: -1;
  407. i {
  408. font-size: 16px;
  409. }
  410. }
  411. > .icon-expand {
  412. display: inline-block;
  413. font-size: 12px;
  414. transform: scale(0.7);
  415. &.collapsed {
  416. display: inline-block;
  417. transform: scale(0.7) rotate(-90deg);
  418. }
  419. }
  420. > .folder_expanded {
  421. font-size: 16px;
  422. margin-left: 7px;
  423. }
  424. > .folder_collapsed {
  425. font-size: 16px;
  426. margin-left: 7px;
  427. }
  428. > .group-name {
  429. margin-left: 6px;
  430. display: inline-block;
  431. text-overflow: ellipsis;
  432. overflow: hidden;
  433. white-space: nowrap;
  434. flex: 1 1 auto;
  435. }
  436. > .icon-add {
  437. margin-left: 12px;
  438. display: none;
  439. cursor: pointer;
  440. &:hover {
  441. color: #0076f6;
  442. }
  443. }
  444. > .icon-image {
  445. margin-left: 12px;
  446. display: none;
  447. cursor: pointer;
  448. &:hover {
  449. color: #0076f6;
  450. }
  451. }
  452. > .icon-edit {
  453. margin-left: 12px;
  454. display: none;
  455. cursor: pointer;
  456. &:hover {
  457. color: #0076f6;
  458. }
  459. }
  460. > .icon-delete {
  461. margin-left: 12px;
  462. display: none;
  463. cursor: pointer;
  464. &:hover {
  465. color: #fa5555;
  466. }
  467. }
  468. > .deletion-confirm-wrap {
  469. position: absolute;
  470. top: 0;
  471. bottom: 0;
  472. right: 0;
  473. width: 44px;
  474. overflow: hidden;
  475. pointer-events: none;
  476. border-top-right-radius: 4px;
  477. border-bottom-right-radius: 4px;
  478. > .deletion-confirm {
  479. position: absolute;
  480. top: 0;
  481. bottom: 0;
  482. width: 100%;
  483. background: #FA5555;
  484. transition: right 0.3s;
  485. cursor: pointer;
  486. text-align: center;
  487. font-size: 12px;
  488. color: #fff;
  489. pointer-events: auto;
  490. &::after {
  491. content: '';
  492. height: 100%;
  493. vertical-align: middle;
  494. display: inline-block;
  495. }
  496. &.show {
  497. right: 0;
  498. }
  499. &.hide {
  500. right: -44px;
  501. }
  502. }
  503. }
  504. > .group-title-input {
  505. margin-left: 6px;
  506. flex: 1 1 auto;
  507. width: 1px;
  508. height: 30px;
  509. background: #1A1B1D;
  510. border-radius: 2px;
  511. border: 1px solid #404040;
  512. color: #fff;
  513. font-size: 14px;
  514. padding: 0 10px 0 16px;
  515. &:focus {
  516. border-color: #0076F6;
  517. }
  518. }
  519. }
  520. .group-content {
  521. }
  522. }
  523. </style>