DataAnalysis.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <template>
  2. <DashboardLayout>
  3. <!-- 天气 -->
  4. <template #header-left>
  5. <WeatherWidget />
  6. </template>
  7. <!-- 日期 -->
  8. <template #header-right>
  9. <DateTimeWidget />
  10. </template>
  11. <!-- 地图 -->
  12. <template #map>
  13. <TongzhouTrafficMap
  14. amapKey="db2da7e3e248c3b2077d53fc809be63f"
  15. securityJsCode="a7413c674852c5eaf01d90813c5b7ef6"
  16. />
  17. </template>
  18. <template #left>
  19. <!-- 左侧Tab菜单栏 -->
  20. <div class="left-sidebar-wrap">
  21. <TechTabs v-model="activeLeftTab" type="underline">
  22. <TechTabPane label="总览" name="overview" class="menu-scroll-view">
  23. <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
  24. @node-click="handleMenuClick" />
  25. </TechTabPane>
  26. <TechTabPane label="路口" name="crossing" class="menu-scroll-view">
  27. <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
  28. @node-click="handleMenuClick" />
  29. </TechTabPane>
  30. <TechTabPane label="干线" name="trunkLine" class="menu-scroll-view">
  31. <MenuItem v-for="item in menuData" :key="item.id" :node="item" :level="0"
  32. @node-click="handleMenuClick" />
  33. </TechTabPane>
  34. <TechTabPane label="特勤" name="specialDuty" class="menu-scroll-view">
  35. <MenuItem v-for="item in menuData" :key="item.id" :node="item" :level="0"
  36. @node-click="handleMenuClick" />
  37. </TechTabPane>
  38. </TechTabs>
  39. </div>
  40. </template>
  41. <template #right>
  42. </template>
  43. <template #center>
  44. </template>
  45. <template #dialogs>
  46. <SmartDialog ref="dialogs" v-for="dialog in activeDialogs" :key="dialog.id" :id="dialog.id" :visible.sync="dialog.visible"
  47. :title="dialog.title" :defaultWidth="dialog.width || 400" :defaultHeight="dialog.height || 300"
  48. :center="dialog.center !== false" :position="dialog.position" :showClose="dialog.showClose"
  49. @close="handleDialogClose(dialog.id)">
  50. <component :is="dialog.componentName" v-bind="dialog.data"></component>
  51. </SmartDialog>
  52. </template>
  53. </DashboardLayout>
  54. </template>
  55. <script>
  56. import DashboardLayout from '@/layouts/DashboardLayout.vue';
  57. import WeatherWidget from '@/components/ui/WeatherWidget.vue';
  58. import DateTimeWidget from '@/components/ui/DateTimeWidget.vue';
  59. import TechTabs from '@/components/ui/TechTabs.vue';
  60. import TechTabPane from '@/components/ui/TechTabPane.vue';
  61. import TongzhouTrafficMap from '@/components/TongzhouTrafficMap.vue';
  62. import SmartDialog from '@/components/ui/SmartDialog.vue';
  63. import MenuItem from '@/components/ui/MenuItem.vue';
  64. import TrafficTimeSpace from '@/components/ui/TrafficTimeSpace.vue';
  65. import { makeTrafficTimeSpaceData } from '@/mock/data';
  66. export default {
  67. name: "HomePage",
  68. components: {
  69. DashboardLayout,
  70. WeatherWidget,
  71. DateTimeWidget,
  72. TechTabs,
  73. TechTabPane,
  74. SmartDialog,
  75. TongzhouTrafficMap,
  76. MenuItem,
  77. TrafficTimeSpace
  78. },
  79. data() {
  80. return {
  81. // 弹窗相关数据
  82. activeDialogs: [],
  83. // 左侧边栏数据
  84. activeLeftTab: 'overview',
  85. menuData: [
  86. {
  87. id: 'root-1',
  88. label: '主控中心',
  89. icon: 'el-icon-monitor', // 这里可以替换为你项目用的图标类名,比如 iconfont
  90. children: [
  91. {
  92. id: 'team-1',
  93. label: '北京市交警总队',
  94. children:
  95. [
  96. {
  97. id: 'dist-1',
  98. label: '通州区',
  99. children: [
  100. {
  101. id: 'street-1',
  102. label: '中仓街道',
  103. children: [
  104. { id: 'node-1', label: '新华东街 - 新华南路' },
  105. { id: 'node-2', label: '玉带河东街 - 车站路' },
  106. { id: 'node-3', label: '赵登禹大街 - 新华东街' }
  107. ]
  108. },
  109. {
  110. id: 'street-2',
  111. label: '新华街道',
  112. children: [
  113. { id: 'node-4', label: '新华南北街交叉口' },
  114. { id: 'node-5', label: '通胡大街 - 紫运中路' },
  115. { id: 'node-6', label: '芙蓉东路 - 通胡大街' }
  116. ]
  117. },
  118. {
  119. id: 'street-3',
  120. label: '北苑街道',
  121. children: [
  122. { id: 'node-7', label: '北苑路口' },
  123. { id: 'node-8', label: '新华西街 - 北苑南路' },
  124. { id: 'node-9', label: '新城南街 - 新华西街' }
  125. ]
  126. },
  127. {
  128. id: 'street-4',
  129. label: '玉桥街道',
  130. children: [
  131. { id: 'node-10', label: '玉桥西路 - 玉桥西里中街' },
  132. { id: 'node-11', label: '运河西大街 - 玉桥中路' },
  133. { id: 'node-12', label: '梨园南街 - 运河西大街' }
  134. ]
  135. },
  136. {
  137. id: 'street-5',
  138. label: '杨庄街道',
  139. children: [
  140. { id: 'node-13', label: '怡乐中街 - 九棵树西路' },
  141. { id: 'node-14', label: '翠屏西路 - 怡乐中街' },
  142. { id: 'node-15', label: '杨庄路 - 新华西街' }
  143. ]
  144. },
  145. {
  146. id: 'street-6',
  147. label: '九棵树街道',
  148. children: [
  149. { id: 'node-16', label: '九棵树东路 - 九棵树西路' },
  150. { id: 'node-17', label: '云景东路 - 九棵树东路' },
  151. { id: 'node-18', label: '群芳南街 - 云景东路' }
  152. ]
  153. },
  154. {
  155. id: 'street-7',
  156. label: '临河里街道',
  157. children: [
  158. { id: 'node-19', label: '梨园中街 - 九棵树东路' },
  159. { id: 'node-20', label: '临河里路 - 梨园中街' },
  160. { id: 'node-21', label: '万盛南街 - 临河里路' }
  161. ]
  162. },
  163. {
  164. id: 'street-8',
  165. label: '潞邑街道',
  166. children: [
  167. { id: 'node-22', label: '潞苑北大街 - 潞邑西路' },
  168. { id: 'node-23', label: '潞苑南大街 - 潞邑三路' },
  169. { id: 'node-24', label: '东六环 - 潞苑北大街' }
  170. ]
  171. },
  172. {
  173. id: 'street-9',
  174. label: '通运街道',
  175. children: [
  176. { id: 'node-25', label: '通胡大街 - 东六环' },
  177. { id: 'node-26', label: '运河东大街 - 通胡大街' },
  178. { id: 'node-27', label: '紫运中路 - 运河东大街' }
  179. ]
  180. },
  181. {
  182. id: 'street-10',
  183. label: '潞源街道',
  184. children: [
  185. { id: 'node-28', label: '宋梁路 - 运河东大街' },
  186. { id: 'node-29', label: '东六环 - 运河东大街' },
  187. { id: 'node-30', label: '潞源北街 - 宋梁路' }
  188. ]
  189. },
  190. {
  191. id: 'street-11',
  192. label: '文景街道',
  193. children: [
  194. { id: 'node-31', label: '环球大道 - 九棵树东路' },
  195. { id: 'node-32', label: '颐瑞东路 - 环球大道' },
  196. { id: 'node-33', label: '万盛南街 - 颐瑞东路' }
  197. ]
  198. }
  199. ]
  200. }
  201. ]
  202. }
  203. ]
  204. }
  205. ]
  206. };
  207. },
  208. created() {
  209. },
  210. mounted() {
  211. },
  212. methods: {
  213. handleMenuClick(nodeData) {
  214. console.log('父组件接收到了最底层路口点击事件:', nodeData);
  215. // 这里可以根据 nodeData 的经纬度来控制地图组件的视角
  216. this.testOpenSecurityRoute(nodeData);
  217. },
  218. openDialog(config) {
  219. // 1. 检查这个弹窗是否已经在数组中了
  220. const existingDialog = this.activeDialogs.find(
  221. d => String(d.id) === String(config.id)
  222. );
  223. if (existingDialog) {
  224. existingDialog.visible = true;
  225. this.$nextTick(() => {
  226. if (this.$refs.dialogs) {
  227. const dialogRefs = Array.isArray(this.$refs.dialogs)
  228. ? this.$refs.dialogs
  229. : [this.$refs.dialogs];
  230. const targetComponent = dialogRefs.find(
  231. vm => String(vm.id) === String(config.id)
  232. );
  233. if (targetComponent) {
  234. targetComponent.bringToFront();
  235. if (typeof targetComponent.playShake === 'function') {
  236. targetComponent.playShake();
  237. }
  238. } else {
  239. console.warn('没找到对应弹窗:', config.id);
  240. }
  241. }
  242. });
  243. return;
  244. }
  245. // 2. 如果不存在,则 push 一个新的弹窗对象进去
  246. this.activeDialogs.push({
  247. id: config.id, // 唯一标识 (例如路口ID 'node-101')
  248. title: config.title, // 弹窗左上角标题
  249. componentName: config.component, // 要加载的内部组件名
  250. visible: true, // 默认可见
  251. width: config.width || 450, // 自定义宽度
  252. height: config.height || 300, // 自定义高度
  253. center: config.center !== false, // 是否居中显示
  254. position: config.position || null, // 自定义坐标 {x, y}
  255. showClose: config.showClose !== false, // 是否显示关闭按钮
  256. data: config.data || {} // 传给内部组件的业务数据
  257. });
  258. window.dispatchEvent(new Event('resize'));
  259. },
  260. /**
  261. * 关闭弹窗的回调
  262. */
  263. handleDialogClose(dialogId) {
  264. // 性能优化:当用户点击 ✕ 关闭弹窗时,将其从数组中彻底移除,销毁内部组件释放内存
  265. this.activeDialogs = this.activeDialogs.filter(d => d.id !== dialogId);
  266. },
  267. // ================= 测试用例:模拟各种点击行为 =================
  268. // 模拟 1:打开特勤安保路线面板
  269. testOpenSecurityRoute(data) {
  270. this.openDialog({
  271. id: data.id, // 这里的 ID 可以根据实际业务场景动态生成,例如 'dev-security-route' 代表特勤安保路线弹窗
  272. title: data.label,
  273. component: 'TrafficTimeSpace',
  274. width: 1000,
  275. height: 500,
  276. center: true,
  277. showClose: true,
  278. // position: { x: 400, y: 450 },
  279. data: makeTrafficTimeSpaceData(),
  280. });
  281. },
  282. }
  283. }
  284. </script>
  285. <style scoped></style>