flex,editor

This commit is contained in:
liaoboping 2025-08-16 10:13:46 +08:00
parent 3e39f6c34e
commit 1ad29f234e
17 changed files with 766 additions and 167 deletions

View File

@ -16,6 +16,8 @@
"dependencies": {
"@ant-design-vue/pro-layout": "^1.0.11",
"@antv/data-set": "^0.10.2",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^1.0.2",
"ant-design-vue": "^1.7.8",
"axios": "^0.26.1",
"cesium": "1.101.0",

View File

@ -1,7 +1,7 @@
<template>
<a-config-provider :locale="locale" :autoInsertSpaceInButton="false">
<div id="app">
<router-view/>
<router-view />
</div>
</a-config-provider>
</template>
@ -11,53 +11,115 @@ import { domTitle, setDocumentTitle } from '@/utils/domUtil'
import { i18nRender } from '@/locales'
export default {
data () {
return {
}
data() {
return {}
},
computed: {
locale () {
locale() {
//
const { title } = this.$route.meta
title && (setDocumentTitle(`${i18nRender(title)} - ${domTitle}`))
title && setDocumentTitle(`${i18nRender(title)} - ${domTitle}`)
return this.$i18n.getLocaleMessage(this.$store.getters.lang).antLocale
}
}
},
},
}
</script>
<style>
.ant-table td { white-space: nowrap !important; }
.ant-table th { white-space: nowrap !important; }
.ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 8px 8px !important;
overflow-wrap: break-word;
font-size: 14px;
}
.flexRowStart {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.flexRowCenter {
display: flex;
flex-direction: row;
align-items: center;
}
.flexCenterCenter {
display: flex;
justify-content: center;
align-items: center;
}
.flexColumnCenterCenter{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.shaixuan_float_left{
float: left;
margin-right: 20px;
}
.ant-table td {
white-space: nowrap !important;
}
.ant-table th {
white-space: nowrap !important;
}
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 8px 8px !important;
overflow-wrap: break-word;
font-size: 14px;
}
.flexRowStart {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.flexRowCenter {
display: flex;
flex-direction: row;
align-items: center;
}
.flexCenterCenter {
display: flex;
justify-content: center;
align-items: center;
}
.flexColumnCenterCenter {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.shaixuan_float_left {
float: left;
margin-right: 20px;
}
.flex {
display: flex;
}
.flex-rr {
display: flex;
flex-direction: row-reverse;
}
.flex-c {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.flex-cr {
display: flex;
flex-direction: column-reverse;
}
.ai-fs {
align-items: flex-start;
}
.ai-s {
align-items: stretch;
}
.ai-fe {
align-items: flex-end;
}
.ai-c {
align-items: center;
}
.ai-s {
align-items: stretch;
}
.jc-c {
justify-content: center;
}
.jc-sb {
justify-content: space-between;
}
.jc-sa {
justify-content: space-around;
}
.jc-fs {
justify-content: flex-start;
}
.jc-fe {
justify-content: flex-end;
}
.fr-w {
flex-wrap: wrap;
}
.fr-n {
flex-wrap: nowrap;
}
.fr-wr {
flex-wrap: wrap-reverse;
}
</style>

View File

@ -32,7 +32,7 @@ export default class MyCesium {
// 未完成的在地图上移动类的操作
operations = []
constructor(dom) {
constructor(dom, options = {}) {
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: window._CONFIG.ImageryProviderUrl || MyCesium.ImageryProviderUrl,
tilingScheme: new Cesium.WebMercatorTilingScheme(),
@ -65,11 +65,13 @@ export default class MyCesium {
})
this.viewer = viewer
const { center = MyCesium.center } = options
// viewer.scene.globe.depthTestAgainstTerrain = true
viewer.imageryLayers.addImageryProvider(roadProvider)
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(...MyCesium.center),
destination: Cesium.Cartesian3.fromDegrees(...center),
})
// 不显示Cesium的Logo
@ -93,7 +95,7 @@ export default class MyCesium {
// 创建指北针小部件并将其添加到地图
new CesiumNavigation(viewer, {
// 用于在使用重置导航重置地图视图时设置默认视图控制。接受的值是Cesium.Cartographic 和Cesium.Rectangle.
defaultResetView: Cesium.Cartographic.fromDegrees(...MyCesium.center),
defaultResetView: Cesium.Cartographic.fromDegrees(...center),
// 用于启用或禁用罗盘。true是启用罗盘false是禁用罗盘。默认值为true。如果将选项设置为false则罗盘将不会添加到地图中。
enableCompass: true,
// 用于启用或禁用缩放控件。true是启用false是禁用。默认值为true。如果将选项设置为false则缩放控件 将不会添加到地图中。
@ -275,4 +277,26 @@ export default class MyCesium {
},
})
}
setViewerByAllCorner(leftUp, rightUp, rightDown, leftDown) {
const [leftUpLon, leftUpLat] = leftUp
const [rightUpLon, rightUpLat] = rightUp
const [rightDownLon, rightDownLat] = rightDown
const [leftDownLon, leftDownLat] = leftDown
// 计算矩形区域的边界
const west = Math.min(leftDownLon, leftUpLon, rightDownLon, rightUpLon)
const east = Math.max(leftDownLon, leftUpLon, rightDownLon, rightUpLon)
const south = Math.min(leftDownLat, leftUpLat, rightDownLat, rightUpLat)
const north = Math.max(leftDownLat, leftUpLat, rightDownLat, rightUpLat)
// 创建矩形范围
const rectangle = Cesium.Rectangle.fromDegrees(west, south, east, north)
this.viewer.camera.setView({
destination: rectangle,
orientation: {
heading: 0.0,
pitch: -Cesium.Math.PI_OVER_TWO,
roll: 0.0,
},
})
}
}

View File

@ -1,43 +0,0 @@
const keyMaps = {
c: 'center',
fs: 'flex-start',
fe: 'flex-end',
sb: 'space-between',
sa: 'space-around',
n: 'nowrap',
w: 'wrap',
wr: 'wrap-reverse',
}
export default {
bind(el, binding, vnode, oldVnode) {
// console.log('----bind----', el, binding, vnode, oldVnode)
el.style.display = 'flex'
if (binding.modifiers.v === true) {
el.style.flexDirection = 'column'
}
if (binding.value && binding.value instanceof Array) {
const [ai, jc, fw] = binding.value
if (ai) el.style.alignItems = keyMaps[ai] || ai
if (jc) el.style.justifyContent = keyMaps[jc] || jc
if (fw) el.style.flexWrap = keyMaps[fw] || fw
}
},
inserted(el, binding, vnode, oldVnode) {
// console.log('----inserted----', el, binding, vnode, oldVnode)
},
update(el, binding, vnode, oldVnode) {
// console.log('----update----', el, binding, vnode, oldVnode)
if (binding.value && binding.value instanceof Array) {
const [ai, jc] = binding.value
if (ai) el.style.alignItems = keyMaps[ai] || ai
if (jc) el.style.justifyContent = keyMaps[jc] || jc
}
},
componentUpdated(el, binding, vnode, oldVnode) {
// console.log('----componentUpdated----', el, binding, vnode, oldVnode)
},
unbind(el, binding, vnode, oldVnode) {
// console.log('----unbind----', el, binding, vnode, oldVnode)
},
}

View File

@ -9,6 +9,7 @@ function initLoadingDom(el) {
el.loadingDom.style.justifyContent = 'center'
}
function appendLoadingDom(el) {
if (document.body.contains(el.loadingDom)) return
const { top, left, width, height } = el.getBoundingClientRect()
el.loadingDom.style.top = `${top}px`
el.loadingDom.style.left = `${left}px`
@ -18,7 +19,9 @@ function appendLoadingDom(el) {
el.loadingDom.innerHTML = `<i aria-label="图标: loading" class="anticon anticon-loading"><svg viewBox="0 0 1024 1024" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="anticon-spin"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></i>`
}
function removeLoadingDom(el) {
document.body.removeChild(el.loadingDom)
if (document.body.contains(el.loadingDom)) {
document.body.removeChild(el.loadingDom)
}
}
export default {
@ -45,5 +48,6 @@ export default {
},
unbind(el, binding, vnode, oldVnode) {
// console.log('----unbind----', el, binding, vnode, oldVnode)
removeLoadingDom(el)
},
}

View File

@ -0,0 +1,137 @@
<template>
<div class="duration-picker">
<template v-if="_showDay">
<a-input-number v-model="day" :min="0" :step="1" />
<span></span>
</template>
<template v-if="_showHour">
<a-input-number v-model="hour" :min="minHour" :step="1" />
<span>小时</span>
</template>
<template v-if="_showMinute">
<a-input-number v-model="minute" :min="minMinute" :step="1" />
<span>分钟</span>
</template>
<template v-if="_showSecond">
<a-input-number v-model="second" :min="minSecond" :step="1" />
<span></span>
</template>
</div>
</template>
<script>
export default {
props: {
value: { type: Number, required: true },
countFormat: { validator: (v) => ['D', 'H', 'm', 's'].includes(v), default: 'm' },
showDay: { type: Boolean, default: true },
showHour: { type: Boolean, default: true },
showMinute: { type: Boolean, default: true },
showSecond: { type: Boolean, default: true },
},
data() {
return {
rateMap: {
D: 1 * 60 * 60 * 24,
H: 1 * 60 * 60,
m: 1 * 60,
s: 1,
},
}
},
computed: {
baseRate() {
return this.rateMap[this.countFormat]
},
day: {
get() {
return Math.floor((this.value * this.baseRate) / this.rateMap['D'])
},
set(v) {
this.$emit('input', this.value + ((v - this.day) * this.rateMap['D']) / this.baseRate)
},
},
hour: {
get() {
return Math.floor(((this.value * this.baseRate) % this.rateMap['D']) / this.rateMap['H'])
},
set(v) {
this.$emit('input', this.value + ((v - this.hour) * this.rateMap['H']) / this.baseRate)
},
},
minute: {
get() {
return Math.floor(((this.value * this.baseRate) % this.rateMap['H']) / this.rateMap['m'])
},
set(v) {
this.$emit('input', this.value + ((v - this.minute) * this.rateMap['m']) / this.baseRate)
},
},
second: {
get() {
return Math.floor(((this.value * this.baseRate) % this.rateMap['m']) / this.rateMap['s'])
},
set(v) {
this.$emit('input', this.value + ((v - this.second) * this.rateMap['s']) / this.baseRate)
},
},
_showDay() {
return this.showDay && ['D', 'H', 'm', 's'].includes(this.countFormat)
},
_showHour() {
return this.showHour && ['H', 'm', 's'].includes(this.countFormat)
},
_showMinute() {
return this.showMinute && ['m', 's'].includes(this.countFormat)
},
_showSecond() {
return this.showSecond && ['s'].includes(this.countFormat)
},
minHour() {
return this.day > 0 ? -1 : 0
},
minMinute() {
return this.day > 0 || this.hour > 0 ? -1 : 0
},
minSecond() {
return this.day > 0 || this.hour > 0 || this.minute > 0 ? -1 : 0
},
},
}
</script>
<style lang="less" scoped>
::v-deep {
.ant-input-number {
background-color: #0b293a;
border: solid 1px #1d5777;
color: #ffffff;
}
.has-error .ant-input-number:hover {
background-color: #0b293a;
}
.has-error .ant-input-number-affix-wrapper .ant-input-number,
.has-error .ant-input-number-affix-wrapper .ant-input-number:hover {
background-color: #0b293a;
}
.ant-input-number-handler-wrap {
background-color: #aaaaaa;
border-left: solid 1px #1d5777;
}
.ant-input-number-handler {
.anticon {
color: #ffffff;
}
}
.ant-input-number-handler:hover {
.anticon {
color: #40a9ff;
}
}
.ant-input-number-handler-up {
}
.ant-input-number-handler-down {
border-top: solid 1px #1d5777;
}
}
</style>

View File

@ -0,0 +1,152 @@
<template>
<div class="wang-editor">
<Toolbar class="wang-editor-toolbar" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
<Editor
class="wang-editor-textarea"
:style="{ height }"
v-model="_value"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
/>
</div>
</template>
<script>
import { Toolbar, Editor } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'
import audioMenuRegister from './audioMenuRegister'
export default {
components: { Toolbar, Editor },
props: {
value: { validator: () => true, default: '' }, // v-model
height: { type: String, default: '300px' }, // 300px
audio: { type: Boolean, default: false }, //
toolbarKeysEnable: { type: [Array, Boolean], default: true },
},
data() {
return {
editor: null,
toolbarConfig: {},
editorConfig: {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: { customUpload: this.customUpload },
uploadVideo: { customUpload: this.customUpload },
},
},
mode: 'default',
}
},
computed: {
_value: {
get() {
return this.value
},
set(val) {
this.$emit(
'input',
val.replace(/<video ([^>]*)>/g, (matchText, $1) => {
if ($1.includes('autoplay')) {
return matchText
} else {
return matchText.replace($1, 'autoplay="true" $&')
}
})
)
},
},
hasAudio() {
if (typeof this.toolbarKeysEnable === 'boolean') {
return this.toolbarKeysEnable
}
return this.toolbarKeysEnable.includes('uploadAudio')
},
},
beforeDestroy() {
if (this.editor == null) return
this.editor.destroy()
},
created() {
this.initToolbarKeys()
},
methods: {
initToolbarKeys() {
if (this.toolbarKeysEnable === true) {
return
}
if (this.toolbarKeysEnable === false) {
this.$set(this.toolbarConfig, 'toolbarKeys', [])
return
}
this.$set(this.toolbarConfig, 'toolbarKeys', this.toolbarKeysEnable)
},
onCreated(editor) {
if (this.hasAudio && this.audio) {
audioMenuRegister(editor, this.toolbarConfig, async ([file]) => {
this.customUpload(file, (filePath) => {
editor.insertNode({
type: 'video',
src: filePath,
autoplay: true,
controls: true,
width: '300px',
height: '50px',
children: [{ text: '' }],
})
})
})
}
this.editor = Object.seal(editor)
},
/**
* @params [file] 上传的文件
* @return Promise 最终生成的文件绝对路径
*/
async customUpload(file, insertFn) {
const formdata = new FormData()
formdata.append('file', file)
const res = await this.$http({
url: '/file/upload',
method: 'post',
data: formdata,
headers: { 'Content-Type': 'multipart/form-data' },
})
const filePath = res.data
insertFn(filePath)
},
},
}
</script>
<style lang="less" scoped>
.wang-editor {
border: 1px solid #d9d9d9;
border-radius: 5px;
::v-deep .button-container {
line-height: 1;
}
}
.wang-editor.w-e-full-screen-container {
z-index: 1000;
}
.wang-editor:not(.w-e-full-screen-container) {
::v-deep .w-e-toolbar {
background-color: transparent;
}
::v-deep .w-e-text-container {
background-color: transparent;
}
::v-deep .w-e-textarea-video-container > video {
width: 100%;
min-height: 60px;
}
}
.wang-editor-toolbar {
border-bottom: 1px solid #d9d9d9;
}
.wang-editor-textarea {
min-height: 300px;
}
</style>

View File

@ -0,0 +1,81 @@
#### WangEditor
基于wangeditor5二次封装的公用组件添加上传音频的功能
#### 官方文档
https://www.wangeditor.com/
#### 组件属性
1. value / v-model 内容
2. toolbarKeysEnable 可用工具栏,仅限初始化
```
布尔值 / 操作按钮key组成的数组
false / true / []<key>
```
3. audio 是否启用上传音频的功能
```
false / true
```
#### 工具栏keys配置
1. 新增功能按钮
```
上传音频 uploadAudio
```
2. wangeditor5工具栏默认keys配置
```
[
'headerSelect',
'blockquote',
'|',
'bold',
'underline',
'italic',
{
key: 'group-more-style',
menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'],
},
'color',
'bgColor',
'|',
'fontSize',
'fontFamily',
'lineHeight',
'|',
'bulletedList',
'numberedList',
'todo',
{
key: 'group-justify',
menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],
},
{
key: 'group-indent',
menuKeys: ['indent', 'delIndent'],
},
'|',
'emotion',
'insertLink',
{
key: 'group-image',
menuKeys: ['insertImage', 'uploadImage'],
},
{
key: 'group-video',
menuKeys: ['insertVideo', 'uploadVideo'],
},
'insertTable',
'codeBlock',
'divider',
'|',
'undo',
'redo',
'|',
'fullScreen',
]
```

View File

@ -0,0 +1,65 @@
import { Boot } from '@wangeditor/editor'
class AudioMenu {
title = ''
iconSvg = ''
tag = ''
constructor () {
this.title = '上传音频'
this.iconSvg =
'<svg t="1637634971457" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7981" width="16" height="16"><path d="M983.792981 0H40.211115A40.5504 40.5504 0 0 0 0.002048 40.96v942.08c0 22.664533 17.954133 40.96 40.209067 40.96h943.581866a40.5504 40.5504 0 0 0 40.209067-40.96V40.96c0-22.664533-17.954133-40.96-40.209067-40.96z m-235.383466 207.530667v118.784H581.702315v326.8608c0 81.92-62.190933 148.548267-138.8544 148.548266-76.663467 0-138.8544-63.351467-138.8544-141.448533 0-78.097067 62.122667-141.448533 138.8544-141.448533 31.607467 0 60.074667-2.730667 83.3536 16.110933v-327.68l222.208 0.273067z" fill="#999999" p-id="7982"></path></svg>'
this.tag = 'button'
}
getValue (editor) {
return ''
}
isActive (editor) {
return false
}
isDisabled () {
return false
}
exec (editor, value) {
if (this.isDisabled(editor)) return
const input = document.createElement('input')
input.type = 'file'
input.accept = 'audio/*'
input.style.cssText = `display: none;`
document.body.appendChild(input)
input.click()
input.addEventListener('change', (event) => {
editor.emit('selectFile', event.target.files)
})
const wf = () => {
setTimeout(() => {
document.body.removeChild(input)
window.removeEventListener('focus', wf)
}, 100)
}
window.addEventListener('focus', wf)
}
}
const MenusList = [{ key: 'uploadAudio', index: 24, Class: AudioMenu }]
const AudioMenuRegister = function (editor, toolbarConfig, callback = () => {}) {
const allRegisterMenu = editor.getAllMenuKeys()
const keys = []
for (const item of MenusList) {
if (allRegisterMenu.indexOf(item.key) < 0) {
Boot.registerMenu({
key: item.key,
factory () {
return new item.Class()
},
})
}
keys.push(item.key)
}
toolbarConfig.insertKeys = {
index: MenusList[0].index,
keys: keys,
}
editor.on('selectFile', callback)
}
export default AudioMenuRegister

View File

@ -2,9 +2,11 @@ import ModuleWrapper from './Layout/ModuleWrapper.vue'
import AntOriginSelect from './Form/AntOriginSelect.vue'
import AntOriginTreeSelect from './Form/AntOriginTreeSelect.vue'
import DurationPicker from './Form/DurationPicker.vue'
import WangEditor from './WangEditor/Index.vue'
import GridBox from './Directives/GridBox'
import Flex from './Directives/Flex'
import Loading from './Directives/Loading'
import MyCesium from './Cesium/index'
@ -15,9 +17,11 @@ export default {
Vue.component('AntOriginSelect', AntOriginSelect)
Vue.component('AntOriginTreeSelect', AntOriginTreeSelect)
Vue.component('DurationPicker', DurationPicker)
Vue.component('WangEditor', WangEditor)
Vue.directive('grid-box', GridBox)
Vue.directive('flex', Flex)
Vue.directive('loading', Loading)
window.MyCesium = MyCesium

View File

@ -44,3 +44,7 @@
line-height: 64px;
background: #001529;
}
.ant-layout-footer {
display: none;
}

View File

@ -5,8 +5,7 @@
<div
v-for="block in systemModules"
:key="block.moduleCode"
:class="['system-block', `system-${block.moduleStatus}`]"
v-flex.v="['c', 'sb']"
:class="['system-block', 'flex-c', 'ai-c', 'jc-sb', `system-${block.moduleStatus}`]"
>
<img class="icon" :src="systemIconMap[block.moduleCode]" alt="" />
<div class="title">{{ block.moduleName }}</div>
@ -17,7 +16,7 @@
</div>
</ModuleWrapper>
<ModuleWrapper v-if="!loadedScenario" title="全部想定" style="grid-column: 1 / 3">
<div class="normal" v-flex.v="['c']" v-loading="loading">
<div class="normal flex-c ai-c" v-loading="loading">
<div class="scenario-wrapper" v-grid-box="{ columns: [1, 1, 1, 1, 1], rows: [1, 1] }">
<div v-for="item in scenarioList" :key="item.id" class="scenario-item">
<img
@ -47,16 +46,16 @@
<template #extra>
<a-button type="primary" shape="circle" icon="close" @click="handleCloseSenario()" />
</template>
<div class="normal" v-flex.v>
<div v-flex="['c', 'sb']" style="margin-bottom: 10px">
<div v-flex="['c']">
<div class="normal flex-c">
<div class="flex ai-c jc-sb" style="margin-bottom: 10px">
<div class="flex ai-c">
<div style="color: #92bacb">运行方式</div>
<a-select style="width: 240px" v-model="runningMode">
<a-select-option value="1">人在回路</a-select-option>
<a-select-option value="2">人不在回路</a-select-option>
</a-select>
</div>
<div v-flex="['c']">
<div class="flex ai-c">
<a-button type="primary" icon="caret-right">开始</a-button>
<a-button type="primary" icon="pause">暂停</a-button>
<a-button type="primary" icon="stop">中止</a-button>
@ -64,27 +63,75 @@
<a-button type="primary" icon="step-forward">快进X1</a-button>
</div>
</div>
<div class="scenario-info-wrapper" v-flex.v>
<div class="scenario-info-wrapper flex-c">
<a-radio-group v-model="radioType" button-style="solid">
<a-radio-button value="xdsj">想定时间</a-radio-button>
<a-radio-button value="xdqy">想定区域</a-radio-button>
<a-radio-button value="xdsm">想定说明</a-radio-button>
</a-radio-group>
<a-row v-if="radioType === 'xdsj'">想定时间</a-row>
<a-row v-if="radioType === 'xdqy'">想定区域</a-row>
<a-row v-if="radioType === 'xdsm'" style="flex: 1; padding: 10px 16px; overflow-y: auto">
<a-col :span="12" v-flex="['c']">
<a-row v-show="radioType === 'xdsj'">
<a-col :span="24">
<div>想定当前时间</div>
<div>
<a-date-picker :value="scenarioDetail.planDate" valueFormat="YYYY-MM-DD" style="width: 100%" />
<a-time-picker :value="scenarioDetail.planTime" valueFormat="HH:mm:ss" style="width: 100%" />
</div>
</a-col>
<a-col :span="24">
<div>想定开始时间</div>
<div>
<a-date-picker :value="scenarioDetail.startDate" valueFormat="YYYY-MM-DD" style="width: 100%" />
<a-time-picker :value="scenarioDetail.startTime" valueFormat="HH:mm:ss" style="width: 100%" />
</div>
</a-col>
<a-col :span="24">
<div>想定持续时间</div>
<div>
<DurationPicker v-model="scenarioDetail.continueTime" />
</div>
</a-col>
<a-col :span="24">
<div>想定消耗时间</div>
<div>
<DurationPicker v-model="scenarioDetail.wasterTime" />
</div>
</a-col>
</a-row>
<div v-show="radioType === 'xdqy'" class="flex-1 flex">
<div style="width: 150px">
<div>经纬度坐标</div>
<div>左上</div>
<a-input v-model="scenarioDetail.leftUpLon" style="width: 100%" />
<a-input v-model="scenarioDetail.leftUpLat" style="width: 100%" />
<div>右上</div>
<a-input v-model="scenarioDetail.rightUpLon" style="width: 100%" />
<a-input v-model="scenarioDetail.rightUpLat" style="width: 100%" />
<div>右下</div>
<a-input v-model="scenarioDetail.rightDownLon" style="width: 100%" />
<a-input v-model="scenarioDetail.rightDownLat" style="width: 100%" />
<div>左下</div>
<a-input v-model="scenarioDetail.leftDownLon" style="width: 100%" />
<a-input v-model="scenarioDetail.leftDownLat" style="width: 100%" />
</div>
<div class="flex-1" style="position: relative">
<div id="jwd-cesium" style="width: 100%; height: 100%"></div>
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%"></div>
</div>
</div>
<a-row v-show="radioType === 'xdsm'" style="flex: 1; padding: 10px 16px; overflow-y: auto">
<a-col :span="12" class="flex ai-c">
<div>想定名称</div>
<div>{{ scenarioDetail.planName }}</div>
</a-col>
<a-col :span="12" v-flex="['c']">
<a-col :span="12" class="flex ai-c">
<div>作者</div>
<div>{{ scenarioDetail.author }}</div>
</a-col>
<a-col :span="24">
<div>想定说明</div>
<div class="xdsm-wrapper">
<iframe v-html="scenarioDetail.planDesc" frameborder="0"></iframe>
<WangEditor v-model="scenarioDetail.planDesc" />
<!-- <iframe v-html="scenarioDetail.planDesc" frameborder="0"></iframe> -->
</div>
</a-col>
</a-row>
@ -95,7 +142,7 @@
<template #extra>
<a-checkbox v-model="visible">显示</a-checkbox>
</template>
<div class="io-event-list" test="1" style="display: block">
<div class="io-event-list">
<div v-for="item in ioEventList" :key="item.id" class="io-event-item">
<span style="margin-right: 20px">{{ item.eventTime }}</span>
<span>{{ item.eventDesc }}</span>
@ -122,8 +169,9 @@ export default {
},
runningMode: '1',
radioType: 'xdsm',
radioType: 'xdsj',
scenarioDetail: {},
cesium: null,
visible: false,
ioEventList: [
@ -175,12 +223,30 @@ export default {
url: `/baseData/scenario/${item.id}`,
method: 'get',
})
this.scenarioDetail = JSON.parse(res.data.data.content)
this.scenarioDetail = JSON.parse(res.data.content)
console.log('----', this.scenarioDetail)
this.loadedScenario = item.id
this.$nextTick(() => {
this.initCesium()
})
} catch (error) {
console.log(error)
}
},
initCesium() {
const { leftUpLat, rightUpLat, rightDownLat, leftDownLat, leftUpLon, rightUpLon, rightDownLon, leftDownLon } =
this.scenarioDetail
const centerLon = (leftUpLon + rightUpLon + rightDownLon + leftDownLon) / 4
const centerLat = (leftUpLat + rightUpLat + rightDownLat + leftDownLat) / 4
this.cesium = new window.MyCesium('jwd-cesium', { center: [centerLon, centerLat] })
this.cesium.setViewerByAllCorner(
[leftUpLon, leftUpLat],
[rightUpLon, rightUpLat],
[rightDownLon, rightDownLat],
[leftDownLon, leftDownLat]
)
},
handleCloseSenario() {
this.loadedScenario = ''
},
@ -238,8 +304,6 @@ export default {
.scenario-item {
position: relative;
cursor: pointer;
width: 100%;
height: 100%;
.scenario-image {
width: 100%;
height: 100%;
@ -298,15 +362,51 @@ export default {
.xdsm-wrapper {
background-color: #ffffff;
color: #666666;
padding: 8px 10px;
p {
margin-bottom: 0;
}
}
}
*::-webkit-scrollbar {
display: none;
}
::v-deep {
.ant-calendar-picker-input,
.ant-time-picker-input {
background-color: #0b293a;
border: solid 1px #1d5777;
color: #ffffff;
}
}
.ant-calendar-picker {
color: #ffffff;
margin: 5px 0;
}
.ant-time-picker {
margin: 5px 0;
}
.ant-calendar-picker::v-deep {
.anticon {
color: #ffffff;
}
}
.ant-calendar-picker:hover::v-deep {
.anticon {
background-color: transparent;
}
}
.ant-time-picker::v-deep {
.anticon {
color: #ffffff;
}
}
.ant-time-picker:hover::v-deep {
.anticon {
background-color: transparent;
}
}
.ant-input {
background-color: #0b293a;
border: solid 1px #1d5777;
color: #ffffff;
}
}
.io-event-list {
@ -315,7 +415,6 @@ export default {
background-color: #082532;
padding: 20px;
overflow-y: scroll;
display: block !important;
.io-event-item {
padding: 10px 0;
font-size: 16px;

View File

@ -1,5 +1,5 @@
<template>
<div class="simulation-scene-page" v-flex.v>
<div class="simulation-scene-page flex-c">
<div v-if="title" class="simulation-scene-header">
{{ title }}
</div>

View File

@ -1,18 +1,23 @@
<template>
<div class="scene-editing-page" v-grid-box="{ columns: ['320px', 1, '320px'], rows: [45, 30, 25], gap: '0px' }">
<ModuleWrapper title="作战/保障力量" style="grid-column: 1 / 2; grid-row: 1 / 2">
<div class="normal" style="padding: 5px">
<a-tree :treeData="zb.zbTreeData"></a-tree>
<div class="normal" style="padding: 5px; overflow-y: auto">
<a-tree
:treeData="zb.zbTreeData"
:selectedKeys.sync="zb.selectedKeys"
:replaceFields="{ children: 'children', title: 'name', key: 'id' }"
@select="handleSelectTree"
></a-tree>
</div>
</ModuleWrapper>
<ModuleWrapper title="装备元素" style="grid-column: 1 / 2; grid-row: 2 / 4">
<div class="normal" style="padding: 5px" v-flex.v>
<div class="normal flex-c" style="padding: 5px">
<a-input-search v-model="ys.keyword" placeholder="输入关键词搜索..." @search="onSearch" />
<div style="height: 0; flex: 1; overflow-y: auto">
<a-row>
<template v-for="item in ys.ysList">
<a-col :key="item.id" :span="24">{{ item.title }}</a-col>
<a-col v-for="it in item.children" :key="it.id" :span="12" v-flex.v="['c']">
<a-col v-for="it in item.children" :key="it.id" :span="12" class="flex-c ai-c">
<img class="ys-image" :src="it.image" alt="" />
<span>{{ it.title }}</span>
</a-col>
@ -31,11 +36,11 @@
<div style="height: 100%; padding: 0 15px; overflow-y: auto">
<img class="image" :src="zt.image" />
<div class="name">{{ zt.name }}</div>
<div v-for="item in zt.ztList" :key="item.label" class="zt-item" v-flex="['c']">
<div v-for="item in zt.ztList" :key="item.label" class="zt-item flex ai-c">
<span>{{ item.label }}</span>
<img :src="item.icon" alt="" />
<span>{{ item.text }}</span>
<div v-if="'progress' in item" class="progress-bar" v-flex>
<div v-if="'progress' in item" class="progress-bar flex">
<div class="progress" :style="{ width: item.progress }"></div>
</div>
</div>
@ -45,7 +50,7 @@
<ModuleWrapper title="作战单元武器" style="grid-column: 3 / 4; grid-row: 3 / 4">
<div class="normal" style="padding: 15px 0">
<div style="height: 100%; padding: 0 15px; overflow-y: auto">
<div v-for="(item, index) in zzdywq.wqList" :key="index" class="wq-item" v-flex>
<div v-for="(item, index) in zzdywq.wqList" :key="index" class="wq-item flex">
<span>{{ item.title }}</span>
</div>
</div>
@ -60,42 +65,8 @@ export default {
data() {
return {
cesium: null,
zb: {
zbTreeData: [
{
key: '1',
title: '红方',
children: [
{
key: '1-1',
title: '作战力量',
children: [
{ key: '1-1-1', title: '左翼夺控群火力分队' },
{ key: '1-1-2', title: '左翼攻击分队' },
{ key: '1-1-3', title: '右翼攻击分队' },
{ key: '1-1-4', title: '右翼夺控群反装甲分队' },
{ key: '1-1-5', title: '防空火力队' },
{ key: '1-1-6', title: '左翼牵制群右翼攻击分队' },
{ key: '1-1-7', title: '电子对抗兵队超短波干扰分队' },
{ key: '1-1-8', title: '纵深夺控群火力分队' },
],
},
{
key: '1-2',
title: '保障力量',
},
],
},
{
key: '2',
title: '蓝方',
children: [
{ key: '1-1', title: '作战力量' },
{ key: '1-2', title: '保障力量' },
],
},
],
},
scenarioId: null,
zb: { zbTreeData: [], selectedKeys: [] },
ys: {
keyword: '',
ysList: [
@ -159,8 +130,46 @@ export default {
},
mounted() {
this.cesium = new window.MyCesium('cesium-container')
this.scenarioId = 2733
this.getZbTree()
},
methods: {
async getZbTree() {
try {
const res = await this.$http({
url: `/scenario/power/${this.scenarioId}`,
method: 'get',
})
this.zb.zbTreeData = [
{
id: '1',
name: '红方',
selectable: false,
children: [
{ id: '1-1', name: '作战力量', selectable: false, children: res.data.red.fight },
{ id: '1-2', name: '保障力量', selectable: false, children: res.data.red.guarantee },
{ id: '1-3', name: '指挥力量', selectable: false, children: res.data.red.command },
],
},
{
id: '2',
name: '蓝方',
selectable: false,
children: [
{ id: '2-1', name: '作战力量', selectable: false, children: res.data.red.fight },
{ id: '2-2', name: '保障力量', selectable: false, children: res.data.red.guarantee },
{ id: '2-3', name: '指挥力量', selectable: false, children: res.data.red.command },
],
},
]
} catch (error) {
console.log(error)
}
},
handleSelectTree(selectedKeys, { node }) {
console.log(...arguments)
// node.dataRef
},
onSearch(e) {
console.log('----', e, e.target.value)
},

View File

@ -37,7 +37,7 @@
<ModuleWrapper title="资源" style="grid-column: 3 / 4; grid-row: 3 / 4">
<div class="normal" style="padding: 15px 0">
<div style="height: 100%; padding: 0 15px; overflow-y: auto">
<div v-for="item in zy.zyList" :key="item.label" class="zy-item" v-flex>
<div v-for="item in zy.zyList" :key="item.label" class="zy-item flex">
<span>{{ item.label }}</span>
<span style="flex: 1; white-space: nowrap; overflow: hidden" v-html="zy.placeholder"></span>
<span>{{ item.text }}</span>

View File

@ -3,9 +3,8 @@
<div
v-for="(block, index) in systemModules"
:key="block.moduleCode"
:class="['system-block', `system-${block.moduleStatus}`]"
:class="['system-block', 'flex-c', 'ai-c', 'jc-sb', `system-${block.moduleStatus}`]"
:style="blockStyleMap[index]"
v-flex.v="['c', 'sb']"
@click="handleClick(block)"
>
<img class="icon" :src="systemIconMap[block.moduleCode]" alt="" />

View File

@ -395,13 +395,13 @@ export default {
}
}
::v-deep {
.ant-input-lg {
.ant-input {
height: 52px;
background-color: #0b293a;
border: solid 1px #1d5777;
color: #ffffff;
}
.ant-input-lg:-webkit-autofill {
.ant-input:-webkit-autofill {
-webkit-text-fill-color: #fff !important;
transition: background-color 5000s ease-in-out 0s;
caret-color: #acfff2;
@ -415,11 +415,11 @@ export default {
.ant-input-suffix .anticon:hover {
color: #ffffffcc;
}
.has-error .ant-input-lg:hover {
.has-error .ant-input:hover {
background-color: #0b293a;
}
.has-error .ant-input-affix-wrapper .ant-input-lg,
.has-error .ant-input-affix-wrapper .ant-input-lg:hover {
.has-error .ant-input-affix-wrapper .ant-input,
.has-error .ant-input-affix-wrapper .ant-input:hover {
background-color: #0b293a;
}
}