|
|
|
|
<template>
|
|
|
|
|
<!-- 4T 悬浮窗:空军 4T 作战任务面板样式 -->
|
|
|
|
|
<div v-show="visible" class="four-t-panel" :class="{ 'four-t-panel-ready': layoutReady }">
|
|
|
|
|
<div class="panel-container" :style="panelStyle">
|
|
|
|
|
<!-- 十字分割线 -->
|
|
|
|
|
<div class="cross-line-v"></div>
|
|
|
|
|
<div class="cross-line-h"></div>
|
|
|
|
|
<!-- 十字中心点 -->
|
|
|
|
|
<div class="cross-center"></div>
|
|
|
|
|
|
|
|
|
|
<!-- 顶部操作栏:左侧编辑/完成,右侧关闭 -->
|
|
|
|
|
<div class="panel-actions" @mousedown="onDragStart">
|
|
|
|
|
<button
|
|
|
|
|
class="btn-edit"
|
|
|
|
|
:class="{ complete: isEditMode }"
|
|
|
|
|
@mousedown.stop
|
|
|
|
|
@click="isEditMode ? saveAndExitEdit() : enterEditMode()"
|
|
|
|
|
>
|
|
|
|
|
{{ isEditMode ? '完成' : '编辑' }}
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn-close" @mousedown.stop @click="$emit('update:visible', false)">×</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 四个象限 -->
|
|
|
|
|
<div class="quadrant quadrant-top-left">
|
|
|
|
|
<div class="quadrant-title">THREAT</div>
|
|
|
|
|
<textarea
|
|
|
|
|
class="quadrant-content"
|
|
|
|
|
:readonly="!isEditMode"
|
|
|
|
|
v-model="localData.threat.text"
|
|
|
|
|
placeholder="请输入威胁描述..."
|
|
|
|
|
></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="quadrant quadrant-top-right">
|
|
|
|
|
<div class="quadrant-title">TARGET</div>
|
|
|
|
|
<textarea
|
|
|
|
|
class="quadrant-content"
|
|
|
|
|
:readonly="!isEditMode"
|
|
|
|
|
v-model="localData.target.text"
|
|
|
|
|
placeholder="请输入打击目标..."
|
|
|
|
|
></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="quadrant quadrant-bottom-left">
|
|
|
|
|
<div class="quadrant-title">TASK</div>
|
|
|
|
|
<textarea
|
|
|
|
|
class="quadrant-content"
|
|
|
|
|
v-model="localData.task.text"
|
|
|
|
|
:readonly="!isEditMode"
|
|
|
|
|
placeholder="请输入任务内容..."
|
|
|
|
|
></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="quadrant quadrant-bottom-right">
|
|
|
|
|
<div class="quadrant-title">TACTIC</div>
|
|
|
|
|
<textarea
|
|
|
|
|
class="quadrant-content"
|
|
|
|
|
v-model="localData.tactic.text"
|
|
|
|
|
:readonly="!isEditMode"
|
|
|
|
|
placeholder="请输入战术方案..."
|
|
|
|
|
></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 右下角拖拽调整大小 -->
|
|
|
|
|
<div
|
|
|
|
|
class="four-t-resize-handle"
|
|
|
|
|
@mousedown="onResizeStart"
|
|
|
|
|
title="拖动调整大小"
|
|
|
|
|
></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { save4TData, get4TData } from '@/api/system/routes'
|
|
|
|
|
|
|
|
|
|
const SECTIONS = [
|
|
|
|
|
{ key: 'threat', title: 'THREAT' },
|
|
|
|
|
{ key: 'task', title: 'TASK' },
|
|
|
|
|
{ key: 'target', title: 'TARGET' },
|
|
|
|
|
{ key: 'tactic', title: 'TACTIC' }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const defaultData = () => ({
|
|
|
|
|
threat: { text: '', images: [] },
|
|
|
|
|
task: { text: '', images: [] },
|
|
|
|
|
target: { text: '', images: [] },
|
|
|
|
|
tactic: { text: '', images: [] }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'FourTPanel',
|
|
|
|
|
props: {
|
|
|
|
|
visible: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
|
|
|
|
roomId: {
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
default: null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
sections: SECTIONS,
|
|
|
|
|
localData: defaultData(),
|
|
|
|
|
isEditMode: false,
|
|
|
|
|
editDataBackup: null,
|
|
|
|
|
isDragging: false,
|
|
|
|
|
dragStartX: 0,
|
|
|
|
|
dragStartY: 0,
|
|
|
|
|
panelLeft: null,
|
|
|
|
|
panelTop: null,
|
|
|
|
|
panelWidth: 420,
|
|
|
|
|
panelHeight: 480,
|
|
|
|
|
isResizing: false,
|
|
|
|
|
resizeStartX: 0,
|
|
|
|
|
resizeStartY: 0,
|
|
|
|
|
resizeStartW: 0,
|
|
|
|
|
resizeStartH: 0,
|
|
|
|
|
layoutReady: false,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
panelStyle() {
|
|
|
|
|
const left = this.panelLeft != null ? this.panelLeft : (window.innerWidth - this.panelWidth) - 20
|
|
|
|
|
const top = this.panelTop != null ? this.panelTop : 80
|
|
|
|
|
return {
|
|
|
|
|
left: `${left}px`,
|
|
|
|
|
top: `${top}px`,
|
|
|
|
|
width: `${this.panelWidth}px`,
|
|
|
|
|
height: `${this.panelHeight}px`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
visible: {
|
|
|
|
|
handler(val) {
|
|
|
|
|
if (val && this.roomId) {
|
|
|
|
|
this.loadData()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
immediate: true
|
|
|
|
|
},
|
|
|
|
|
roomId: {
|
|
|
|
|
handler(val) {
|
|
|
|
|
if (val && this.visible) {
|
|
|
|
|
this.loadData()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
immediate: true
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
async loadData() {
|
|
|
|
|
this.layoutReady = false
|
|
|
|
|
if (!this.roomId) {
|
|
|
|
|
this.layoutReady = true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const res = await get4TData({ roomId: this.roomId })
|
|
|
|
|
let d = res && res.data
|
|
|
|
|
if (d) {
|
|
|
|
|
if (typeof d === 'string') {
|
|
|
|
|
try {
|
|
|
|
|
d = JSON.parse(d)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.layoutReady = true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.localData = {
|
|
|
|
|
threat: { text: d.threat?.text || '', images: d.threat?.images || [] },
|
|
|
|
|
task: { text: d.task?.text || '', images: d.task?.images || [] },
|
|
|
|
|
target: { text: d.target?.text || '', images: d.target?.images || [] },
|
|
|
|
|
tactic: { text: d.tactic?.text || '', images: d.tactic?.images || [] }
|
|
|
|
|
}
|
|
|
|
|
if (d.panelSize) {
|
|
|
|
|
const w = Number(d.panelSize.width)
|
|
|
|
|
const h = Number(d.panelSize.height)
|
|
|
|
|
if (!isNaN(w) && w >= 320 && w <= 800) this.panelWidth = w
|
|
|
|
|
if (!isNaN(h) && h >= 300 && h <= window.innerHeight - 60) this.panelHeight = h
|
|
|
|
|
}
|
|
|
|
|
if (d.panelPosition) {
|
|
|
|
|
const left = Number(d.panelPosition.left)
|
|
|
|
|
const top = Number(d.panelPosition.top)
|
|
|
|
|
if (!isNaN(left) && left >= 0) this.panelLeft = Math.min(left, window.innerWidth - this.panelWidth)
|
|
|
|
|
if (!isNaN(top) && top >= 0) this.panelTop = Math.min(top, window.innerHeight - this.panelHeight)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('加载4T数据失败:', e)
|
|
|
|
|
} finally {
|
|
|
|
|
this.layoutReady = true
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
enterEditMode() {
|
|
|
|
|
this.editDataBackup = JSON.parse(JSON.stringify(this.localData))
|
|
|
|
|
this.isEditMode = true
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
saveAndExitEdit() {
|
|
|
|
|
if (!this.roomId) {
|
|
|
|
|
this.$message.warning('请先进入任务房间后再保存')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.isEditMode = false
|
|
|
|
|
this.editDataBackup = null
|
|
|
|
|
this.$message.success('保存成功')
|
|
|
|
|
this.saveData().catch(() => {})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
cancelEdit() {
|
|
|
|
|
if (this.editDataBackup) {
|
|
|
|
|
this.localData = JSON.parse(JSON.stringify(this.editDataBackup))
|
|
|
|
|
}
|
|
|
|
|
this.isEditMode = false
|
|
|
|
|
this.editDataBackup = null
|
|
|
|
|
this.$message.info('已取消编辑')
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async saveData() {
|
|
|
|
|
if (!this.roomId) {
|
|
|
|
|
this.$message.warning('请先进入任务房间后再保存')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const payload = {
|
|
|
|
|
...this.localData,
|
|
|
|
|
panelSize: { width: this.panelWidth, height: this.panelHeight }
|
|
|
|
|
}
|
|
|
|
|
if (this.panelLeft != null && this.panelTop != null) {
|
|
|
|
|
payload.panelPosition = { left: this.panelLeft, top: this.panelTop }
|
|
|
|
|
}
|
|
|
|
|
await save4TData({
|
|
|
|
|
roomId: this.roomId,
|
|
|
|
|
data: JSON.stringify(payload)
|
|
|
|
|
})
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('保存4T失败:', e)
|
|
|
|
|
this.$message.error('保存4T内容失败,请检查网络或权限')
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
addImage(sectionKey) {
|
|
|
|
|
this.pendingAddSection = sectionKey
|
|
|
|
|
this.$refs.fileInput && this.$refs.fileInput.click()
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onFileSelected(e) {
|
|
|
|
|
const file = e.target.files && e.target.files[0]
|
|
|
|
|
if (!file || !file.type.startsWith('image/')) return
|
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
reader.onload = (ev) => {
|
|
|
|
|
const base64 = ev.target.result
|
|
|
|
|
if (this.pendingAddSection && this.localData[this.pendingAddSection]) {
|
|
|
|
|
this.localData[this.pendingAddSection].images.push(base64)
|
|
|
|
|
}
|
|
|
|
|
this.pendingAddSection = null
|
|
|
|
|
}
|
|
|
|
|
reader.readAsDataURL(file)
|
|
|
|
|
e.target.value = ''
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
removeImage(sectionKey, index) {
|
|
|
|
|
if (this.localData[sectionKey] && this.localData[sectionKey].images) {
|
|
|
|
|
this.localData[sectionKey].images.splice(index, 1)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onDragStart(e) {
|
|
|
|
|
// 关闭按钮已用 @mousedown.stop 阻止冒泡,此处直接开始拖动
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
this.isDragging = true
|
|
|
|
|
const currentLeft = this.panelLeft != null ? this.panelLeft : (window.innerWidth - this.panelWidth - 20)
|
|
|
|
|
const currentTop = this.panelTop != null ? this.panelTop : 80
|
|
|
|
|
this.dragStartX = e.clientX - currentLeft
|
|
|
|
|
this.dragStartY = e.clientY - currentTop
|
|
|
|
|
document.addEventListener('mousemove', this.onDragMove)
|
|
|
|
|
document.addEventListener('mouseup', this.onDragEnd)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onDragMove(e) {
|
|
|
|
|
if (!this.isDragging) return
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
let left = e.clientX - this.dragStartX
|
|
|
|
|
let top = e.clientY - this.dragStartY
|
|
|
|
|
left = Math.max(0, Math.min(window.innerWidth - this.panelWidth, left))
|
|
|
|
|
top = Math.max(0, Math.min(window.innerHeight - this.panelHeight, top))
|
|
|
|
|
this.panelLeft = left
|
|
|
|
|
this.panelTop = top
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onDragEnd() {
|
|
|
|
|
this.isDragging = false
|
|
|
|
|
document.removeEventListener('mousemove', this.onDragMove)
|
|
|
|
|
document.removeEventListener('mouseup', this.onDragEnd)
|
|
|
|
|
if (this.roomId) this.saveData()
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onResizeStart(e) {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
this.isResizing = true
|
|
|
|
|
this.resizeStartX = e.clientX
|
|
|
|
|
this.resizeStartY = e.clientY
|
|
|
|
|
this.resizeStartW = this.panelWidth
|
|
|
|
|
this.resizeStartH = this.panelHeight
|
|
|
|
|
document.addEventListener('mousemove', this.onResizeMove)
|
|
|
|
|
document.addEventListener('mouseup', this.onResizeEnd)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onResizeMove(e) {
|
|
|
|
|
if (!this.isResizing) return
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
const dx = e.clientX - this.resizeStartX
|
|
|
|
|
const dy = e.clientY - this.resizeStartY
|
|
|
|
|
let w = Math.max(320, Math.min(800, this.resizeStartW + dx))
|
|
|
|
|
let h = Math.max(300, Math.min(window.innerHeight - 60, this.resizeStartH + dy))
|
|
|
|
|
this.panelWidth = w
|
|
|
|
|
this.panelHeight = h
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onResizeEnd() {
|
|
|
|
|
this.isResizing = false
|
|
|
|
|
document.removeEventListener('mousemove', this.onResizeMove)
|
|
|
|
|
document.removeEventListener('mouseup', this.onResizeEnd)
|
|
|
|
|
if (this.roomId) this.saveData()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* 根容器:覆盖全屏,居中显示 4T 面板;遮罩不阻挡地图点击,便于打开4T时仍可绘制航线 */
|
|
|
|
|
.four-t-panel {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
background: transparent; /* 无遮罩,不使屏幕变暗 */
|
|
|
|
|
z-index: 200;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
transition: opacity 0.15s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.four-t-panel.four-t-panel-ready {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
pointer-events: none; /* 遮罩穿透:点击地图区域可继续绘制航线 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.four-t-panel.four-t-panel-ready .panel-container {
|
|
|
|
|
pointer-events: auto; /* 仅面板本身可点击、拖动 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 面板容器 */
|
|
|
|
|
.panel-container {
|
|
|
|
|
position: fixed;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
height: 600px;
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
box-shadow: 0 8px 24px rgba(25, 148, 254, 0.1);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border: 1px solid #e0edff;
|
|
|
|
|
z-index: 201;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 十字分割线 */
|
|
|
|
|
.cross-line-v {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 50%;
|
|
|
|
|
top: 20px;
|
|
|
|
|
bottom: 20px;
|
|
|
|
|
width: 1px;
|
|
|
|
|
background-color: #1994fe;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
z-index: 10;
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cross-line-h {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 50%;
|
|
|
|
|
left: 20px;
|
|
|
|
|
right: 20px;
|
|
|
|
|
height: 1px;
|
|
|
|
|
background-color: #1994fe;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
z-index: 10;
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 十字中心点 */
|
|
|
|
|
.cross-center {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 50%;
|
|
|
|
|
left: 50%;
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
background-color: #1994fe;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
z-index: 15;
|
|
|
|
|
box-shadow: 0 0 6px rgba(25, 148, 254, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 象限容器 */
|
|
|
|
|
.quadrant {
|
|
|
|
|
position: absolute;
|
|
|
|
|
width: 50%;
|
|
|
|
|
height: 50%;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-top-left {
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-top-right {
|
|
|
|
|
top: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-bottom-left {
|
|
|
|
|
bottom: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-bottom-right {
|
|
|
|
|
bottom: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 标题样式 */
|
|
|
|
|
.quadrant-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #0078e0;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 文本区域样式 */
|
|
|
|
|
.quadrant-content {
|
|
|
|
|
width: 92%;
|
|
|
|
|
height: 80%;
|
|
|
|
|
border: 1px solid #cce5ff;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
color: #263238;
|
|
|
|
|
resize: none;
|
|
|
|
|
background-color: #f9fcff;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-content:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
border-color: #1994fe;
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
box-shadow: 0 0 0 4px rgba(25, 148, 254, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 只读状态 */
|
|
|
|
|
.quadrant-content[readonly] {
|
|
|
|
|
background-color: #e8f4ff;
|
|
|
|
|
color: #0078e0;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
cursor: default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 操作栏 */
|
|
|
|
|
.panel-actions {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 20px;
|
|
|
|
|
left: 20px;
|
|
|
|
|
right: 20px;
|
|
|
|
|
z-index: 20;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
/* 放大可拖动区域,并使用十字拖拽光标 */
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
cursor: move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 编辑按钮 */
|
|
|
|
|
.btn-edit {
|
|
|
|
|
background-color: #f0f7ff;
|
|
|
|
|
color: #1994fe;
|
|
|
|
|
border: 1px solid #cce5ff;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-edit.complete {
|
|
|
|
|
background-color: #f0fff4;
|
|
|
|
|
color: #00c853;
|
|
|
|
|
border: 1px solid #ccffdd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-edit:hover {
|
|
|
|
|
background-color: #e8f4ff;
|
|
|
|
|
border-color: #1994fe;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-edit.complete:hover {
|
|
|
|
|
background-color: #e6fffa;
|
|
|
|
|
border-color: #00c853;
|
|
|
|
|
color: #00a142;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 关闭按钮 */
|
|
|
|
|
.btn-close {
|
|
|
|
|
background-color: #f0f7ff;
|
|
|
|
|
border: 1px solid #cce5ff;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #1994fe;
|
|
|
|
|
width: 30px;
|
|
|
|
|
height: 30px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-close:hover {
|
|
|
|
|
background-color: #e8f4ff;
|
|
|
|
|
color: #0078e0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式适配 */
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.panel-container {
|
|
|
|
|
height: 500px;
|
|
|
|
|
max-width: 95%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-title {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quadrant-content {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
width: 95%;
|
|
|
|
|
height: 75%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-edit {
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-close {
|
|
|
|
|
width: 28px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 右下角拖拽调整大小手柄 */
|
|
|
|
|
.four-t-resize-handle {
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
cursor: nwse-resize;
|
|
|
|
|
user-select: none;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
background: linear-gradient(to top left, transparent 50%, rgba(25, 148, 254, 0.2) 50%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.four-t-resize-handle:hover {
|
|
|
|
|
background: linear-gradient(to top left, transparent 50%, rgba(25, 148, 254, 0.4) 50%);
|
|
|
|
|
}
|
|
|
|
|
</style>
|