You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
996 lines
32 KiB
996 lines
32 KiB
<template>
|
|
<div
|
|
v-show="visible"
|
|
class="sixsteps-overlay"
|
|
:class="{ 'is-draggable': draggable }"
|
|
>
|
|
<div class="overlay-main">
|
|
<div class="overlay-header" ref="headerRef">
|
|
<div class="header-left">
|
|
<span class="overlay-title">{{ currentStepTitle }}</span>
|
|
<!-- 理解步骤:子标题(点名、接收解析任务、XXXX、XXXX)放在理解和插入之间 -->
|
|
<template v-if="currentStepIndex === 0 && !overrideTitle">
|
|
<div
|
|
v-for="(sub, idx) in understandingSubTitles"
|
|
:key="idx"
|
|
class="header-sub-title"
|
|
:class="{ active: activeUnderstandingSubIndex === idx }"
|
|
@click="activeUnderstandingSubIndex = idx"
|
|
@contextmenu.prevent="onSubTitleContextMenu('understanding', idx, $event)"
|
|
>
|
|
{{ sub }}
|
|
</div>
|
|
<el-dropdown trigger="click" @command="handleUnderstandingInsertCommand" class="header-insert">
|
|
<el-button size="small" type="primary" plain>
|
|
<i class="el-icon-plus"></i> 插入
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item command="subTitle">
|
|
<i class="el-icon-menu"></i> 小标题
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="background">
|
|
<i class="el-icon-picture-outline"></i> 背景
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="removeBackground" :disabled="!sixStepsSharedBackground">
|
|
<i class="el-icon-delete"></i> 删除背景
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="icon">
|
|
<i class="el-icon-s-opportunity"></i> 图标
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="textbox">
|
|
<i class="el-icon-edit-outline"></i> 文本框
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</template>
|
|
<!-- 后五步(判断、规划、准备、执行、评估):小标题 + 插入按钮 -->
|
|
<template v-else-if="currentStepIndex >= 1 && currentStepIndex <= 5">
|
|
<div
|
|
v-for="(sub, idx) in getStepContent(currentStepIndex).subTitles"
|
|
:key="idx"
|
|
class="header-sub-title"
|
|
:class="{ active: activeStepSubIndex === idx }"
|
|
@click="activeStepSubIndex = idx"
|
|
@contextmenu.prevent="onSubTitleContextMenu('step', idx, $event)"
|
|
>
|
|
{{ sub }}
|
|
</div>
|
|
<el-dropdown trigger="click" @command="handleStepInsertCommand" class="header-insert">
|
|
<el-button size="small" type="primary" plain>
|
|
<i class="el-icon-plus"></i> 插入
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item command="subTitle">
|
|
<i class="el-icon-menu"></i> 小标题
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="background">
|
|
<i class="el-icon-picture-outline"></i> 背景
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="removeBackground" :disabled="!sixStepsSharedBackground">
|
|
<i class="el-icon-delete"></i> 删除背景
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="icon">
|
|
<i class="el-icon-s-opportunity"></i> 图标
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="textbox">
|
|
<i class="el-icon-edit-outline"></i> 文本框
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</template>
|
|
<!-- 任务页:小标题 + 插入按钮 -->
|
|
<template v-else-if="overrideTitle === '任务'">
|
|
<div
|
|
v-for="(sub, idx) in taskSubTitles"
|
|
:key="idx"
|
|
class="header-sub-title"
|
|
:class="{ active: activeTaskSubIndex === idx }"
|
|
@click="activeTaskSubIndex = idx"
|
|
@contextmenu.prevent="onSubTitleContextMenu('task', idx, $event)"
|
|
>
|
|
{{ sub }}
|
|
</div>
|
|
<el-dropdown trigger="click" @command="handleInsertCommand" class="header-insert">
|
|
<el-button size="small" type="primary" plain>
|
|
<i class="el-icon-plus"></i> 插入
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item command="subTitle">
|
|
<i class="el-icon-menu"></i> 小标题
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="background">
|
|
<i class="el-icon-picture-outline"></i> 背景
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="removeBackground" :disabled="!taskPageBackground">
|
|
<i class="el-icon-delete"></i> 删除背景
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="icon">
|
|
<i class="el-icon-s-opportunity"></i> 图标
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="textbox">
|
|
<i class="el-icon-edit-outline"></i> 文本框
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="overlay-body" :style="overlayBodyStyle">
|
|
<div class="overlay-content" :class="{
|
|
'task-page': overrideTitle === '任务',
|
|
'understanding-page': currentStepIndex === 0 && !overrideTitle,
|
|
'step-page': currentStepIndex >= 1 && currentStepIndex <= 5
|
|
}">
|
|
<!-- 任务页:插入工具栏 + 可编辑画布 -->
|
|
<task-page-content
|
|
v-if="overrideTitle === '任务'"
|
|
ref="taskPage"
|
|
:room-id="roomId"
|
|
:background-image="taskPageBackground"
|
|
:task-sub-titles="taskSubTitles"
|
|
@background-change="taskPageBackground = $event"
|
|
@task-sub-titles-change="taskSubTitles = $event"
|
|
@save-request="debouncedSave"
|
|
@task-page-data="lastTaskPageData = $event; saveToRedis()"
|
|
class="task-page-body"
|
|
/>
|
|
<!-- 理解步骤:4 子标题 + 可编辑画布 -->
|
|
<understanding-step-content
|
|
v-else-if="currentStepIndex === 0"
|
|
ref="understandingStep"
|
|
:background-image="sixStepsSharedBackground"
|
|
:active-sub-index="activeUnderstandingSubIndex"
|
|
:sub-titles="understandingSubTitles"
|
|
@background-change="sixStepsSharedBackground = $event"
|
|
@save-request="debouncedSave"
|
|
@understanding-data="lastUnderstandingData = $event; saveToRedis()"
|
|
class="understanding-page-body"
|
|
/>
|
|
<!-- 判断、规划、准备、执行、评估:可编辑画布,六步共享背景 -->
|
|
<step-canvas-content
|
|
v-else-if="currentStepIndex >= 1 && currentStepIndex <= 5"
|
|
ref="stepCanvas"
|
|
:key="currentStepIndex"
|
|
:content="getStepContent(currentStepIndex)"
|
|
:active-sub-index="activeStepSubIndex"
|
|
:background-image="sixStepsSharedBackground"
|
|
@background-change="sixStepsSharedBackground = $event"
|
|
class="step-canvas-body"
|
|
/>
|
|
<div v-else class="blank-placeholder">
|
|
<i class="el-icon-document"></i>
|
|
<p>{{ currentStepTitle }} - 内容区域</p>
|
|
<p class="hint">此处为空白页,后续可添加具体功能</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 小标题右键菜单 -->
|
|
<div
|
|
v-if="subTitleContextMenu.visible"
|
|
ref="subTitleContextMenuRef"
|
|
class="sub-title-context-menu"
|
|
:style="{ left: subTitleContextMenu.x + 'px', top: subTitleContextMenu.y + 'px' }"
|
|
@click.stop
|
|
>
|
|
<div class="context-menu-item" @click="editSubTitle">
|
|
<i class="el-icon-edit"></i>
|
|
<span>编辑</span>
|
|
</div>
|
|
<div class="context-menu-item" @click="insertNewPage">
|
|
<i class="el-icon-document-add"></i>
|
|
<span>插入新的一页</span>
|
|
</div>
|
|
<div class="context-menu-item context-menu-item-danger" @click="deleteSubTitle">
|
|
<i class="el-icon-delete"></i>
|
|
<span>删除</span>
|
|
</div>
|
|
</div>
|
|
<!-- 右侧栏:任务 + 1-6 步,垂直排列 -->
|
|
<div class="overlay-sidebar">
|
|
<div class="sidebar-steps">
|
|
<div
|
|
class="sidebar-task"
|
|
:class="{ active: taskBlockActive }"
|
|
@click="$emit('select-task')"
|
|
>
|
|
<span>任务</span>
|
|
</div>
|
|
<div
|
|
v-for="(step, index) in sixStepsData"
|
|
:key="index"
|
|
class="sidebar-step"
|
|
:class="{ active: step.active, completed: step.completed }"
|
|
@click="$emit('select-step', index)"
|
|
>
|
|
<div class="sidebar-step-num">{{ index + 1 }}</div>
|
|
<span class="sidebar-step-title">{{ step.title }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import TaskPageContent from './TaskPageContent.vue'
|
|
import UnderstandingStepContent from './UnderstandingStepContent.vue'
|
|
import StepCanvasContent from './StepCanvasContent.vue'
|
|
import {
|
|
createRollCallTextBoxes,
|
|
createSubTitleTemplate,
|
|
createIntentBriefingTemplate,
|
|
createTaskPlanningTemplate,
|
|
createSimpleTitleTemplate,
|
|
SUB_TITLE_TEMPLATE_NAMES,
|
|
SIMPLE_TITLE_NAMES
|
|
} from './rollCallTemplate'
|
|
import { ensurePagesStructure, createEmptySubContent } from './subContentPages'
|
|
import { getSixStepsData, saveSixStepsData, getTaskPageData } from '@/api/system/routes'
|
|
|
|
export default {
|
|
name: 'SixStepsOverlay',
|
|
components: { TaskPageContent, UnderstandingStepContent, StepCanvasContent },
|
|
props: {
|
|
roomId: {
|
|
type: [Number, String],
|
|
default: null
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
currentStepIndex: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
stepTitles: {
|
|
type: Array,
|
|
default: () => ['理解', '判断', '规划', '准备', '执行', '评估']
|
|
},
|
|
/** 覆盖标题,如「任务」独立步骤 */
|
|
overrideTitle: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
sixStepsData: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
taskBlockActive: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
initialActiveUnderstandingSubIndex: { type: Number, default: 0 },
|
|
initialActiveTaskSubIndex: { type: Number, default: 0 },
|
|
initialActiveStepSubIndex: { type: Number, default: 0 },
|
|
draggable: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
taskPageBackground: null,
|
|
sixStepsSharedBackground: null,
|
|
understandingSubTitles: ['点名', '任务目标', '自身任务', '对接任务'],
|
|
activeUnderstandingSubIndex: this.initialActiveUnderstandingSubIndex,
|
|
taskSubTitles: [],
|
|
activeTaskSubIndex: this.initialActiveTaskSubIndex,
|
|
activeStepSubIndex: this.initialActiveStepSubIndex,
|
|
stepContents: {},
|
|
subTitleContextMenu: {
|
|
visible: false,
|
|
x: 0,
|
|
y: 0,
|
|
target: null,
|
|
index: -1
|
|
},
|
|
_saveTimer: null,
|
|
lastTaskPageData: null,
|
|
lastUnderstandingData: null,
|
|
taskPageLoadComplete: false,
|
|
_isRestoring: false
|
|
}
|
|
},
|
|
created() {
|
|
this._isRestoring = this.initialActiveUnderstandingSubIndex !== 0 || this.initialActiveTaskSubIndex !== 0 || this.initialActiveStepSubIndex !== 0
|
|
},
|
|
watch: {
|
|
currentStepIndex(val) {
|
|
if (this._isRestoring) {
|
|
this.$nextTick(() => { this._isRestoring = false })
|
|
return
|
|
}
|
|
this.activeStepSubIndex = 0
|
|
if (val === 0 && !this.overrideTitle) {
|
|
this.$nextTick(() => {
|
|
if (this.$refs.understandingStep && this.lastUnderstandingData) {
|
|
this.$refs.understandingStep.loadFromData(this.lastUnderstandingData)
|
|
}
|
|
})
|
|
}
|
|
},
|
|
overrideTitle(val) {
|
|
if (this._isRestoring) {
|
|
this.$nextTick(() => { this._isRestoring = false })
|
|
return
|
|
}
|
|
this.activeTaskSubIndex = 0
|
|
if (val === '任务') {
|
|
this.taskPageLoadComplete = false
|
|
this.$nextTick(() => {
|
|
if (this.$refs.taskPage) {
|
|
if (this.lastTaskPageData) {
|
|
this.$refs.taskPage.loadFromData(this.lastTaskPageData)
|
|
}
|
|
this.loadFromRedis()
|
|
}
|
|
})
|
|
}
|
|
if (!val && this.currentStepIndex === 0) {
|
|
this.$nextTick(() => {
|
|
if (this.$refs.understandingStep) {
|
|
if (this.lastUnderstandingData) {
|
|
this.$refs.understandingStep.loadFromData(this.lastUnderstandingData)
|
|
}
|
|
this.loadFromRedis()
|
|
}
|
|
})
|
|
}
|
|
},
|
|
roomId: {
|
|
handler(val) {
|
|
if (val != null && this.visible) this.loadFromRedis()
|
|
},
|
|
immediate: false
|
|
},
|
|
visible: {
|
|
handler(val) {
|
|
if (val && this.roomId != null) {
|
|
if (this.overrideTitle === '任务') this.taskPageLoadComplete = false
|
|
this.loadFromRedis()
|
|
}
|
|
},
|
|
immediate: false
|
|
},
|
|
taskPageBackground: { handler() { this.debouncedSave() }, deep: false },
|
|
sixStepsSharedBackground: { handler() { this.debouncedSave() }, deep: false },
|
|
understandingSubTitles: { handler() { this.debouncedSave() }, deep: true },
|
|
taskSubTitles: { handler() { this.debouncedSave() }, deep: true },
|
|
stepContents: { handler() { this.debouncedSave() }, deep: true }
|
|
},
|
|
mounted() {
|
|
document.addEventListener('click', this.onDocumentClickForSubTitleMenu)
|
|
if (this.roomId != null && this.visible) {
|
|
if (this.overrideTitle === '任务') this.taskPageLoadComplete = false
|
|
this.loadFromRedis()
|
|
}
|
|
},
|
|
beforeDestroy() {
|
|
document.removeEventListener('click', this.onDocumentClickForSubTitleMenu)
|
|
if (this._saveTimer) {
|
|
clearTimeout(this._saveTimer)
|
|
this._saveTimer = null
|
|
}
|
|
this.saveToRedis()
|
|
},
|
|
computed: {
|
|
currentStepTitle() {
|
|
if (this.overrideTitle) return this.overrideTitle
|
|
return this.stepTitles[this.currentStepIndex] || '六步法'
|
|
},
|
|
overlayBodyStyle() {
|
|
if (this.overrideTitle === '任务') return {}
|
|
if (this.currentStepIndex >= 1 && this.currentStepIndex <= 5) return {}
|
|
return {}
|
|
}
|
|
},
|
|
methods: {
|
|
getProgress() {
|
|
return {
|
|
activeUnderstandingSubIndex: this.activeUnderstandingSubIndex,
|
|
activeTaskSubIndex: this.activeTaskSubIndex,
|
|
activeStepSubIndex: this.activeStepSubIndex
|
|
}
|
|
},
|
|
close() {
|
|
this.$emit('close', this.getProgress())
|
|
},
|
|
async loadFromRedis() {
|
|
if (this.roomId == null) return
|
|
try {
|
|
let res = await getSixStepsData({ roomId: this.roomId })
|
|
let data = res && res.data
|
|
if (!data) {
|
|
res = await getTaskPageData({ roomId: this.roomId })
|
|
data = res && res.data
|
|
if (data) {
|
|
const tp = typeof data === 'string' ? (() => { try { return JSON.parse(data) } catch (_) { return null } })() : data
|
|
if (tp) data = { taskPage: tp }
|
|
}
|
|
}
|
|
if (!data) return
|
|
const raw = typeof data === 'string' ? (() => { try { return JSON.parse(data) } catch (_) { return null } })() : data
|
|
if (!raw) return
|
|
if (raw.taskPage) {
|
|
this.taskPageBackground = raw.taskPage.background || null
|
|
if (Array.isArray(raw.taskPage.taskSubTitles)) this.taskSubTitles = raw.taskPage.taskSubTitles
|
|
this.lastTaskPageData = raw.taskPage
|
|
this.taskPageLoadComplete = true
|
|
this.$nextTick(() => {
|
|
if (this.$refs.taskPage) this.$refs.taskPage.loadFromData(raw.taskPage)
|
|
})
|
|
} else if (this.overrideTitle === '任务') {
|
|
this.taskPageLoadComplete = true
|
|
}
|
|
if (raw.sixStepsSharedBackground) this.sixStepsSharedBackground = raw.sixStepsSharedBackground
|
|
if (Array.isArray(raw.understanding?.subTitles)) this.understandingSubTitles = raw.understanding.subTitles
|
|
if (raw.understanding?.subContents) {
|
|
this.lastUnderstandingData = raw.understanding
|
|
this.$nextTick(() => {
|
|
if (this.$refs.understandingStep) this.$refs.understandingStep.loadFromData(raw.understanding)
|
|
})
|
|
}
|
|
if (raw.steps && typeof raw.steps === 'object') {
|
|
Object.keys(raw.steps).forEach(k => {
|
|
const step = raw.steps[k]
|
|
if (step && (step.subTitles || step.subContents)) {
|
|
this.$set(this.stepContents, parseInt(k, 10), {
|
|
subTitles: step.subTitles || [],
|
|
subContents: (step.subContents || []).map(sc => {
|
|
ensurePagesStructure(sc)
|
|
return sc
|
|
})
|
|
})
|
|
}
|
|
})
|
|
}
|
|
} catch (e) {
|
|
console.warn('SixSteps loadFromRedis failed:', e)
|
|
} finally {
|
|
if (this.overrideTitle === '任务') this.taskPageLoadComplete = true
|
|
}
|
|
},
|
|
saveToRedis() {
|
|
if (this.roomId == null) return
|
|
let taskPageData
|
|
if (this.$refs.taskPage) {
|
|
if (this.taskPageLoadComplete) {
|
|
taskPageData = this.lastTaskPageData = this.$refs.taskPage.getDataForSave()
|
|
} else {
|
|
taskPageData = this.lastTaskPageData || { background: this.taskPageBackground, icons: [], textBoxes: [], taskSubTitles: this.taskSubTitles }
|
|
}
|
|
} else {
|
|
taskPageData = this.lastTaskPageData || { background: this.taskPageBackground, icons: [], textBoxes: [], taskSubTitles: this.taskSubTitles }
|
|
}
|
|
const payload = {
|
|
taskPage: taskPageData,
|
|
sixStepsSharedBackground: this.sixStepsSharedBackground,
|
|
understanding: (() => {
|
|
if (this.$refs.understandingStep) {
|
|
this.lastUnderstandingData = { subTitles: this.understandingSubTitles, subContents: this.$refs.understandingStep.getDataForSave() }
|
|
}
|
|
return {
|
|
subTitles: this.understandingSubTitles,
|
|
subContents: this.lastUnderstandingData?.subContents || []
|
|
}
|
|
})(),
|
|
steps: {}
|
|
}
|
|
Object.keys(this.stepContents).forEach(k => {
|
|
const step = this.stepContents[k]
|
|
if (step && (step.subTitles || step.subContents)) {
|
|
payload.steps[k] = {
|
|
subTitles: step.subTitles || [],
|
|
subContents: (step.subContents || []).map(sc => {
|
|
ensurePagesStructure(sc)
|
|
return {
|
|
pages: (sc.pages || []).map(p => ({
|
|
icons: (p.icons || []).map(i => ({ id: i.id, x: i.x, y: i.y, width: i.width, height: i.height, rotation: i.rotation || 0, src: i.src })),
|
|
textBoxes: (p.textBoxes || []).map(t => ({ id: t.id, x: t.x, y: t.y, width: t.width, height: t.height, text: t.text || '', placeholder: t.placeholder, rotation: t.rotation || 0, fontSize: t.fontSize, fontFamily: t.fontFamily, color: t.color, fontWeight: t.fontWeight }))
|
|
})),
|
|
currentPageIndex: sc.currentPageIndex || 0
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
saveSixStepsData({
|
|
roomId: this.roomId,
|
|
data: JSON.stringify(payload)
|
|
}).catch(e => {
|
|
console.warn('SixSteps saveToRedis failed:', e)
|
|
})
|
|
},
|
|
debouncedSave() {
|
|
if (this._saveTimer) clearTimeout(this._saveTimer)
|
|
this._saveTimer = setTimeout(() => {
|
|
this._saveTimer = null
|
|
this.saveToRedis()
|
|
}, 300)
|
|
},
|
|
getStepContent(stepIndex) {
|
|
let step = this.stepContents[stepIndex]
|
|
if (step) {
|
|
if (!step.subContents && step.subTitles) {
|
|
step.subContents = step.subTitles.map(() => createEmptySubContent())
|
|
if (step.icons?.length || step.textBoxes?.length) {
|
|
step.subContents[0] = createEmptySubContent()
|
|
step.subContents[0].pages[0] = { icons: step.icons || [], textBoxes: step.textBoxes || [] }
|
|
}
|
|
}
|
|
return step
|
|
}
|
|
{
|
|
const defaultSubTitles = stepIndex === 1
|
|
? ['相关规定', '敌情', '意图通报', '威胁判断']
|
|
: stepIndex === 2
|
|
? ['任务规划', '职责分工', '点名', '第一次进度检查', '点名', '第二次进度检查', '点名', '产品生成']
|
|
: stepIndex === 3
|
|
? ['点名', '集体协同']
|
|
: stepIndex === 4
|
|
? ['任务执行']
|
|
: stepIndex === 5
|
|
? ['评估']
|
|
: []
|
|
this.$set(this.stepContents, stepIndex, {
|
|
subTitles: defaultSubTitles,
|
|
subContents: defaultSubTitles.map(() => createEmptySubContent())
|
|
})
|
|
return this.stepContents[stepIndex]
|
|
}
|
|
},
|
|
async addSubTitle(target) {
|
|
try {
|
|
const { value } = await this.$prompt('请输入小标题名称', '插入小标题', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
inputValue: '新标题',
|
|
inputPattern: /\S+/,
|
|
inputErrorMessage: '请输入小标题名称'
|
|
})
|
|
if (value && value.trim()) {
|
|
if (target === 'understanding') {
|
|
this.understandingSubTitles.push(value.trim())
|
|
} else if (target === 'task') {
|
|
this.taskSubTitles.push(value.trim())
|
|
} else if (target === 'step') {
|
|
const step = this.getStepContent(this.currentStepIndex)
|
|
step.subTitles.push(value.trim())
|
|
step.subContents.push(createEmptySubContent())
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
},
|
|
handleInsertCommand(cmd) {
|
|
if (cmd === 'subTitle') {
|
|
this.addSubTitle('task')
|
|
return
|
|
}
|
|
if (this.$refs.taskPage) {
|
|
this.$refs.taskPage.handleInsertCommand(cmd)
|
|
}
|
|
},
|
|
handleUnderstandingInsertCommand(cmd) {
|
|
if (cmd === 'subTitle') {
|
|
this.addSubTitle('understanding')
|
|
return
|
|
}
|
|
if (this.$refs.understandingStep) {
|
|
this.$refs.understandingStep.handleInsertCommand(cmd)
|
|
}
|
|
},
|
|
handleStepInsertCommand(cmd) {
|
|
if (cmd === 'subTitle') {
|
|
this.addSubTitle('step')
|
|
return
|
|
}
|
|
if (this.$refs.stepCanvas) {
|
|
this.$refs.stepCanvas.handleInsertCommand(cmd)
|
|
}
|
|
},
|
|
onSubTitleContextMenu(target, index, event) {
|
|
const arr = target === 'understanding' ? this.understandingSubTitles
|
|
: target === 'task' ? this.taskSubTitles
|
|
: this.getStepContent(this.currentStepIndex).subTitles
|
|
const sourceName = arr && arr[index] ? arr[index] : ''
|
|
this.subTitleContextMenu = {
|
|
visible: true,
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
target,
|
|
index,
|
|
sourceName
|
|
}
|
|
},
|
|
closeSubTitleContextMenu() {
|
|
this.subTitleContextMenu.visible = false
|
|
},
|
|
onDocumentClickForSubTitleMenu(e) {
|
|
if (!this.subTitleContextMenu.visible) return
|
|
const el = this.$refs.subTitleContextMenuRef
|
|
if (el && el.contains(e.target)) return
|
|
this.closeSubTitleContextMenu()
|
|
},
|
|
getSubTitleArray(target) {
|
|
const t = target ?? this.subTitleContextMenu?.target
|
|
if (t === 'understanding') return this.understandingSubTitles
|
|
if (t === 'task') return this.taskSubTitles
|
|
if (t === 'step') return this.getStepContent(this.currentStepIndex).subTitles
|
|
return []
|
|
},
|
|
async editSubTitle() {
|
|
const { target, index } = this.subTitleContextMenu
|
|
const arr = this.getSubTitleArray(target)
|
|
this.closeSubTitleContextMenu()
|
|
if (index < 0 || index >= arr.length) return
|
|
try {
|
|
const { value } = await this.$prompt('请输入小标题名称', '编辑小标题', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
inputValue: arr[index],
|
|
inputPattern: /\S+/,
|
|
inputErrorMessage: '请输入小标题名称'
|
|
})
|
|
if (value && value.trim()) {
|
|
this.$set(arr, index, value.trim())
|
|
}
|
|
} catch (_) {}
|
|
},
|
|
getTemplateBoxes(sourceName, newName, canvas) {
|
|
const w = canvas ? canvas.offsetWidth : 800
|
|
const h = canvas ? canvas.offsetHeight : 500
|
|
if (sourceName === '点名') return createRollCallTextBoxes(w, h)
|
|
if (sourceName === '意图通报') return createIntentBriefingTemplate(w, h)
|
|
if (sourceName === '任务规划') return createTaskPlanningTemplate(w, h)
|
|
if (SIMPLE_TITLE_NAMES.includes(sourceName)) return createSimpleTitleTemplate(newName, w, h)
|
|
if (SUB_TITLE_TEMPLATE_NAMES.includes(sourceName)) return createSubTitleTemplate(newName, w, h)
|
|
return []
|
|
},
|
|
async insertNewPage() {
|
|
const { target, index, sourceName } = this.subTitleContextMenu
|
|
this.closeSubTitleContextMenu()
|
|
if (!sourceName) return
|
|
const subIdx = index
|
|
if (target === 'task') {
|
|
this.$message.info('任务页暂不支持多页')
|
|
return
|
|
}
|
|
if (target === 'understanding') {
|
|
this.$nextTick(() => {
|
|
const canvas = this.$refs.understandingStep?.$refs?.canvas
|
|
const boxes = this.getTemplateBoxes(sourceName, sourceName, canvas)
|
|
if (this.$refs.understandingStep) {
|
|
this.$refs.understandingStep.addPageToSubIndex(subIdx, boxes)
|
|
}
|
|
})
|
|
} else if (target === 'step') {
|
|
const step = this.getStepContent(this.currentStepIndex)
|
|
const sub = step.subContents[subIdx]
|
|
if (!sub) return
|
|
ensurePagesStructure(sub)
|
|
const canvas = this.$refs.stepCanvas?.$refs?.canvas
|
|
const boxes = this.getTemplateBoxes(sourceName, sourceName, canvas)
|
|
sub.pages.push({ icons: [], textBoxes: [...boxes] })
|
|
sub.currentPageIndex = sub.pages.length - 1
|
|
}
|
|
},
|
|
deleteSubTitle() {
|
|
const { target, index } = { ...this.subTitleContextMenu }
|
|
this.closeSubTitleContextMenu()
|
|
const arr = this.getSubTitleArray(target)
|
|
if (index < 0 || index >= arr.length) return
|
|
arr.splice(index, 1)
|
|
if (target === 'step') {
|
|
const step = this.getStepContent(this.currentStepIndex)
|
|
if (step.subContents && step.subContents.length > index) {
|
|
step.subContents.splice(index, 1)
|
|
}
|
|
}
|
|
if (target === 'understanding') {
|
|
if (this.activeUnderstandingSubIndex >= arr.length && arr.length > 0) {
|
|
this.activeUnderstandingSubIndex = arr.length - 1
|
|
} else if (arr.length === 0) {
|
|
this.activeUnderstandingSubIndex = 0
|
|
} else if (this.activeUnderstandingSubIndex > index) {
|
|
this.activeUnderstandingSubIndex--
|
|
}
|
|
} else if (target === 'task') {
|
|
if (this.activeTaskSubIndex >= arr.length && arr.length > 0) {
|
|
this.activeTaskSubIndex = arr.length - 1
|
|
} else if (arr.length === 0) {
|
|
this.activeTaskSubIndex = 0
|
|
} else if (this.activeTaskSubIndex > index) {
|
|
this.activeTaskSubIndex--
|
|
}
|
|
} else if (target === 'step') {
|
|
if (this.activeStepSubIndex >= arr.length && arr.length > 0) {
|
|
this.activeStepSubIndex = arr.length - 1
|
|
} else if (arr.length === 0) {
|
|
this.activeStepSubIndex = 0
|
|
} else if (this.activeStepSubIndex > index) {
|
|
this.activeStepSubIndex--
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 悬浮窗:左侧顶到屏幕最左侧,右侧顶到屏幕最右侧,下边框与时间轴上边框对齐 */
|
|
.sixsteps-overlay {
|
|
position: fixed;
|
|
top: 60px;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 71px;
|
|
background: #ffffff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.15);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
z-index: 999;
|
|
display: flex;
|
|
flex-direction: row;
|
|
overflow: hidden;
|
|
animation: overlayFadeIn 0.3s ease;
|
|
}
|
|
|
|
.overlay-main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.overlay-sidebar {
|
|
width: 100px;
|
|
flex-shrink: 0;
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 250, 252, 0.98) 100%);
|
|
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-start;
|
|
padding: 12px 8px;
|
|
}
|
|
|
|
.sidebar-steps {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.sidebar-task {
|
|
padding: 10px 12px;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
text-align: center;
|
|
background: rgba(255, 255, 255, 0.6);
|
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.sidebar-task:hover {
|
|
background: rgba(255, 255, 255, 0.9);
|
|
border-color: rgba(148, 163, 184, 0.4);
|
|
}
|
|
|
|
.sidebar-task.active {
|
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.02) 100%);
|
|
border-color: rgba(59, 130, 246, 0.3);
|
|
color: #1e40af;
|
|
}
|
|
|
|
.sidebar-step {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 10px;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
background: rgba(255, 255, 255, 0.6);
|
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.sidebar-step:hover {
|
|
background: rgba(255, 255, 255, 0.9);
|
|
border-color: rgba(148, 163, 184, 0.4);
|
|
}
|
|
|
|
.sidebar-step.active {
|
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.02) 100%);
|
|
border-color: rgba(59, 130, 246, 0.3);
|
|
}
|
|
|
|
.sidebar-step.completed {
|
|
background: linear-gradient(135deg, rgba(34, 197, 94, 0.06) 0%, rgba(34, 197, 94, 0.01) 100%);
|
|
border-color: rgba(34, 197, 94, 0.25);
|
|
}
|
|
|
|
.sidebar-step-num {
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
color: #64748b;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.sidebar-step.active .sidebar-step-num {
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
|
color: white;
|
|
}
|
|
|
|
.sidebar-step.completed .sidebar-step-num {
|
|
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
|
color: white;
|
|
}
|
|
|
|
.sidebar-step-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.sidebar-step.active .sidebar-step-title {
|
|
color: #1e40af;
|
|
}
|
|
|
|
@keyframes overlayFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.overlay-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
background: linear-gradient(135deg, rgba(0, 138, 255, 0.08) 0%, rgba(0, 138, 255, 0.02) 100%);
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
cursor: default;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.header-insert {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.header-sub-title {
|
|
padding: 6px 14px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
color: #64748b;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.header-sub-title:hover {
|
|
background: rgba(0, 138, 255, 0.08);
|
|
color: #008aff;
|
|
}
|
|
|
|
.header-sub-title.active {
|
|
background: rgba(0, 138, 255, 0.12);
|
|
color: #008aff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.overlay-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.overlay-body {
|
|
flex: 1;
|
|
overflow: auto;
|
|
min-height: 200px;
|
|
}
|
|
|
|
.overlay-content {
|
|
padding: 24px;
|
|
min-height: 180px;
|
|
}
|
|
|
|
.overlay-content.task-page {
|
|
padding: 0;
|
|
height: 100%;
|
|
}
|
|
|
|
.overlay-content.understanding-page,
|
|
.overlay-content.step-page {
|
|
padding: 0;
|
|
height: 100%;
|
|
}
|
|
|
|
.task-page-body,
|
|
.understanding-page-body,
|
|
.step-canvas-body {
|
|
height: 100%;
|
|
}
|
|
|
|
.blank-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 160px;
|
|
color: #94a3b8;
|
|
text-align: center;
|
|
}
|
|
|
|
.blank-placeholder i {
|
|
font-size: 48px;
|
|
margin-bottom: 16px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.blank-placeholder p {
|
|
margin: 4px 0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.blank-placeholder .hint {
|
|
font-size: 12px;
|
|
color: #cbd5e1;
|
|
}
|
|
|
|
.sub-title-context-menu {
|
|
position: fixed;
|
|
z-index: 10000;
|
|
min-width: 120px;
|
|
padding: 4px 0;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.sub-title-context-menu .context-menu-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 16px;
|
|
font-size: 14px;
|
|
color: #1e293b;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.sub-title-context-menu .context-menu-item:hover {
|
|
background: rgba(0, 138, 255, 0.08);
|
|
color: #008aff;
|
|
}
|
|
|
|
.sub-title-context-menu .context-menu-item-danger:hover {
|
|
background: rgba(239, 68, 68, 0.08);
|
|
color: #ef4444;
|
|
}
|
|
</style>
|
|
|