|
|
@@ -0,0 +1,1759 @@
|
|
|
+<template>
|
|
|
+ <div class="all target-detail-box" v-loading="loading">
|
|
|
+
|
|
|
+ <div class="top" :class="currentTab == '概览' ? 'flex-1' : 'h-100'">
|
|
|
+
|
|
|
+ <div style="padding: 10px 20px 0 20px; box-sizing: border-box; justify-content: space-between;"
|
|
|
+ class="flex-box-ce">
|
|
|
+ <header class="PageHead flex-box-ce">
|
|
|
+ <span class="return" @click="goPage()">返回</span>
|
|
|
+ <span style="font-size: 16px; font-weight: 600;">目标详情</span>
|
|
|
+ <span class="fontColorC" style="margin-left: 10px;">对齐目标: {{ targetDetail.parentName || '--'
|
|
|
+ }}</span>
|
|
|
+ </header>
|
|
|
+ <div class="flex-box-ce">
|
|
|
+ <!-- <el-button type="primary" size="small" plain @click="getDataCenter">数据大屏</el-button> -->
|
|
|
+ <el-button type="primary" size="small" plain @click="getDetails">刷新数据</el-button>
|
|
|
+ <el-tooltip effect="dark" content="教程指引" placement="top">
|
|
|
+ <div class="icon flex-center-center" @click="initStepData()">
|
|
|
+ <i class="el-icon-document" id="startTour"></i>
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-alert v-if="targetDetail.status == 0" class="bounce animated" type="warning" title="" :closable="false"
|
|
|
+ show-icon style="width: 99%; margin: 0 auto;">
|
|
|
+ <template slot="title">
|
|
|
+ <div class="alert-title" style="width: 100%;">
|
|
|
+ <div class="flex-1">{{ alertTilte }}</div>
|
|
|
+ <el-link type="primary" size="mini" @click="openTableData()">
|
|
|
+ 进入目标制定阶段
|
|
|
+ </el-link>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-alert>
|
|
|
+
|
|
|
+
|
|
|
+ <div class="target-info">
|
|
|
+ <div class="flex-box-ce">
|
|
|
+ <div class="type-img-box">
|
|
|
+ <svg-icon v-if="targetDetail.type == 1" icon-class="#icon-gongsi" class="svgIcon"></svg-icon>
|
|
|
+ <svg-icon v-if="targetDetail.type == 2" icon-class="#icon-bumenguanli"
|
|
|
+ class="svgIcon"></svg-icon>
|
|
|
+ <svg-icon v-if="targetDetail.type == 3" icon-class="#icon-yonghu" class="svgIcon"></svg-icon>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+
|
|
|
+ <div class="name flex-box-ce">
|
|
|
+ {{ targetDetail.name }}
|
|
|
+
|
|
|
+ <!-- 目标状态 -->
|
|
|
+ <TargetStatus :targetDetail="targetDetail" @showTableData="openTableData()"
|
|
|
+ @confirm="getDetails" />
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <div class="flex-box-ce">
|
|
|
+ <div class="tag" v-if="targetDetail.type == 1">
|
|
|
+ <svg-icon icon-class="#icon-gongsi" class="svgIcon"
|
|
|
+ style="margin: 0 5px 0 0;"></svg-icon>公司目标
|
|
|
+ </div>
|
|
|
+ <div class="tag" v-if="targetDetail.type == 2">
|
|
|
+ <svg-icon icon-class="#icon-bumenguanli" class="svgIcon"
|
|
|
+ style="margin: 0 5px 0 0;"></svg-icon>部门目标
|
|
|
+ </div>
|
|
|
+ <div class="tag" v-if="targetDetail.type == 3">
|
|
|
+ <svg-icon icon-class="#icon-yonghu" class="svgIcon"
|
|
|
+ style="margin: 0 5px 0 0;"></svg-icon>个人目标
|
|
|
+ </div>
|
|
|
+ <div class="tag">
|
|
|
+ <svg-icon icon-class="#icon-yonghu" class="svgIcon"
|
|
|
+ style="margin: 0 5px 0 0;"></svg-icon>
|
|
|
+ 负责人: {{ ownerName }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <div class="tag" v-if="targetDetail.scope.cycleType == 1"> <i
|
|
|
+ class="el-icon-alarm-clock"></i> 年度 {{ targetDetail.scope.name }}</div>
|
|
|
+ <div class="tag" v-if="targetDetail.scope.cycleType == 2"> <i
|
|
|
+ class="el-icon-alarm-clock"></i> 半年度 {{ targetDetail.scope.name }}</div>
|
|
|
+ <div class="tag" v-if="targetDetail.scope.cycleType == 3"> <i
|
|
|
+ class="el-icon-alarm-clock"></i> 季度 {{ targetDetail.scope.name }}</div>
|
|
|
+ <div class="tag" v-if="targetDetail.scope.cycleType == 4"> <i
|
|
|
+ class="el-icon-alarm-clock"></i> 月度 {{ targetDetail.scope.name }}</div>
|
|
|
+ <el-link type="primary" @click="openTargetSearch()">
|
|
|
+ <i class="el-icon-link"></i>
|
|
|
+ {{ targetDetail.krName ? targetDetail.objectName + ' - ' + targetDetail.krName :
|
|
|
+ '关联KR'}}
|
|
|
+ </el-link>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex-box-ce" style="margin: 5px 0;">
|
|
|
+ <div class="tag">
|
|
|
+ <i class="el-icon-document"></i>
|
|
|
+ 目标说明: {{ targetDetail.content || '--' }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex-box-ce">
|
|
|
+ <div class="tag">
|
|
|
+ <i class="el-icon-document"></i>
|
|
|
+ 目标标准: {{ targetDetail.standard || '--' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 自动量化配置,预计达成值,完成值是否由子目标决定 -->
|
|
|
+ <div class="flex-box-ce switch-box">
|
|
|
+ <EditAutoSubSwitch v-if="targetDetail.goalId" :targetDetail="targetDetail"
|
|
|
+ @confirm="getDetails" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex-box-ce">
|
|
|
+ <div class="chart1">
|
|
|
+ <GaugeChart :percent="percent" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="tab-list">
|
|
|
+ <div class="tab-item" @click="changeTab(index)"
|
|
|
+ :class="[ item.class, current == index ? 'active' : '' ]" v-for="(item, index) in tabs"
|
|
|
+ :key="item.id">
|
|
|
+ {{ item.name }}
|
|
|
+ <div v-show="current == index" class="line"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 基础值列表 -->
|
|
|
+ <CardList v-if="currentTab == '概览'" :targetDetail="targetDetail" />
|
|
|
+
|
|
|
+ <div class="flex-1">
|
|
|
+ <!-- 拆解目标 -->
|
|
|
+ <DismantlingTarget ref="dismantlingTargetRef" v-if="currentTab == '拆解目标'"
|
|
|
+ :targetDetail="targetDetail" />
|
|
|
+
|
|
|
+ <!-- 任务列表 -->
|
|
|
+ <TaskList v-if="currentTab == '任务'" :targetDetail="targetDetail" />
|
|
|
+
|
|
|
+ <!-- 任务列表 -->
|
|
|
+ <MessageList v-if="currentTab == '动态'" :targetDetail="targetDetail" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="bottom" v-if="current == 0" :style="{ height: '52%' } ">
|
|
|
+ <template v-if="!autoBySub">
|
|
|
+ <div class="title-box">
|
|
|
+ <div class="title flex-box-ce">
|
|
|
+ 目标时间计划
|
|
|
+ <el-button v-if="isCanSvgTarget" type="primary" size="mini" plain round @click="svgTargetNums()"
|
|
|
+ style="margin-left: 10px;">一键平均分配</el-button>
|
|
|
+
|
|
|
+ <el-button type="primary" size="mini" plain round @click="exportEvent()"
|
|
|
+ style="margin-left: 10px;">导出</el-button>
|
|
|
+ </div>
|
|
|
+ <div v-if="targetDetail.planModels && targetDetail.planModels.data">
|
|
|
+ <el-link @click="deletePlan()" type="danger"
|
|
|
+ v-if="targetDetail.status == 0 || targetDetail.status == 1">删除计划</el-link>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <div class="table-box">
|
|
|
+ <template v-if="targetDetail.planModels && targetDetail.planModels.data">
|
|
|
+ <vxe-table ref="xTable" :data="targetDetail.planModels.data"
|
|
|
+ :column-config="{ resizable: true }" :row-config="{ isHover: true, isCurrent: true }"
|
|
|
+ max-height="auto" border auto-resize :edit-config="editConfig"
|
|
|
+ @edit-actived="handleEditActived" @edit-closed="handleEditClosed">
|
|
|
+ <vxe-column field="name" :title="'指标(' + targetDetail.unit + ')'" min-width="200"
|
|
|
+ fixed="left">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="flex-box-ce">
|
|
|
+ <div v-if="row.code === 'RISK_VALUE'" class="line"
|
|
|
+ style=" background: #FFEEEB; ">
|
|
|
+ </div>
|
|
|
+ <div v-if="row.code === 'TARGET_VALUE'" class="line"
|
|
|
+ style=" background: #E9F8F7; ">
|
|
|
+ </div>
|
|
|
+ <div v-if="row.code === 'CHALLENGE_VALUE'" class="line"
|
|
|
+ style=" background: #FCF9ED; ">
|
|
|
+ </div>
|
|
|
+ <div v-if="row.code === 'SURPASS_VALUE'" class="line"
|
|
|
+ style=" background: #FFF7EE; ">
|
|
|
+ </div>
|
|
|
+ <div v-if="row.code === 'PROJECTED_VALUE'" class="line"
|
|
|
+ style=" background: #F3F3FE; "></div>
|
|
|
+ <div v-if="row.code === 'RESULT_VALUE'" class="line"
|
|
|
+ style=" background: #c6f7de; ">
|
|
|
+ </div>
|
|
|
+ <div>{{ row.name }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </vxe-column>
|
|
|
+ <template v-if="targetDetail.planModels && targetDetail.planModels.scopes.length > 0">
|
|
|
+
|
|
|
+ <vxe-column v-for="(header, index) in targetDetail.planModels.scopes"
|
|
|
+ :key="header.scopeId" :title="header.scopeName" :field="header.scopeId"
|
|
|
+ min-width="100" align="left" :edit-render="{
|
|
|
+ name: 'input',
|
|
|
+ attrs: { type: 'number' },
|
|
|
+ style: { textAlign: 'left' }
|
|
|
+ }">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <template v-for="value in row.values">
|
|
|
+ <span v-if="value.scopeId == header.scopeId"
|
|
|
+ :class="[row.allowEdit ? 'cursor-pointer' : '', row.code === 'DIFF_VALUE' ? value.value > 0 ? 'color-green' : 'color-red' : '']">{{
|
|
|
+ value.value
|
|
|
+ }}</span>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </vxe-column>
|
|
|
+
|
|
|
+ </template>
|
|
|
+ </vxe-table>
|
|
|
+ </template>
|
|
|
+ <div v-else class="flex-box-ce" style="justify-content: center; width: 100%; height: 100%;">
|
|
|
+ <!-- 只有周期为月度才能按日拆分计划 -->
|
|
|
+ <el-button v-if="targetDetail.scope.cycleType == 4" type="primary" round
|
|
|
+ @click="planByDate('day')">按日规划</el-button>
|
|
|
+ <!-- 只有周期为年度,半年度,季度才能按月拆分计划 -->
|
|
|
+ <el-button
|
|
|
+ v-if="targetDetail.scope.cycleType == 1 || targetDetail.scope.cycleType == 2 || targetDetail.scope.cycleType == 3"
|
|
|
+ type="primary" round @click="planByDate('month')">按月规划</el-button>
|
|
|
+ <!-- 只有周期为年度,半年度才能按季拆分计划 -->
|
|
|
+ <el-button v-if="targetDetail.scope.cycleType == 1 || targetDetail.scope.cycleType == 2"
|
|
|
+ type="primary" round @click="planByDate('quarter')">按季规划</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <div class=" title-box">
|
|
|
+ <div class="title flex-box-ce">
|
|
|
+ 目标时间计划 {{ rawData }}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <el-tooltip class="item" effect="dark" content="全屏" placement="top-start">
|
|
|
+ <el-button @click="cycleTableDialogVisible = true" type="primary" icon="el-icon-full-screen"
|
|
|
+ circle size="small"></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <MultiCycleTable v-if="rawData" :rawData="rawData" :isCardListShow="false"
|
|
|
+ :name="targetDetail.name || ''" :owner-name="ownerName" />
|
|
|
+ </template>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <TargetSearch :visible.sync="showTargetSearch" :selectedCheckList="checkList" @confirm="confirmSelectedOkr">
|
|
|
+ </TargetSearch>
|
|
|
+
|
|
|
+ <EditBaseTableData v-if="dialogVisible" v-model="dialogVisible" :targetDetail="targetDetail"
|
|
|
+ @confirm="getDetails" />
|
|
|
+
|
|
|
+ <el-dialog title="平均分配数据" center :visible.sync="openSvgTargetDialog" width="1200px"
|
|
|
+ :close-on-press-escape="false" append-to-body :close-on-click-modal="false">
|
|
|
+ <div>
|
|
|
+ <el-alert style="margin-bottom: 10px;" type="warning" title="将各指标目标数按规划周期整数均摊,余数并入末周期。"
|
|
|
+ :closable="false" show-icon></el-alert>
|
|
|
+ <template>
|
|
|
+ <vxe-table :data="svgTargetData.data" :column-config="{ resizable: true }"
|
|
|
+ :row-config="{ isHover: true, isCurrent: true }" max-height="400px" border auto-resize
|
|
|
+ :edit-config="{enable: false}">
|
|
|
+ <vxe-column field="name" :title="'指标(' + targetDetail.unit + ')'" min-width="100" fixed="left">
|
|
|
+ </vxe-column>
|
|
|
+ <template v-for="(header, index) in svgTargetData.scopes">
|
|
|
+
|
|
|
+ <vxe-column :key="header.scopeId" :title="header.scopeName" :field="header.scopeId"
|
|
|
+ min-width="100" align="left">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <template v-for="value in row.values">
|
|
|
+
|
|
|
+ <span v-if="value.scopeId == header.scopeId"
|
|
|
+ :class="[row.allowEdit ? 'cursor-pointer' : '']">{{ value.value }}</span>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </vxe-column>
|
|
|
+ </template>
|
|
|
+ </vxe-table>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button type="primary" size="small" @click="saveSvgTargetNums()">保 存</el-button>
|
|
|
+ <el-button size="small" @click="resetData()">关 闭</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog class="multi-cycle-table" :title="targetDetail.name" :visible.sync="cycleTableDialogVisible"
|
|
|
+ @close="handleCycleTableClose()" :close-on-click-modal="false" :close-on-press-escape="true" center
|
|
|
+ fullscreen :show-close="false" @open="handleCycleTableOpen()">
|
|
|
+ <div style=" width: 100%; height: 100%; position: relative; ">
|
|
|
+ <el-button round style="position: absolute; top: -45px; left: 10px; z-index: 99;"
|
|
|
+ @click="cycleTableDialogVisible = false">返回</el-button>
|
|
|
+ <!-- 引入多周期表格组件 -->
|
|
|
+ <MultiCycleTable v-if="rawData" :rawData="rawData" :name="targetDetail.name" :owner-name="ownerName" />
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog class="data-center-box" title="目标详情" :visible.sync="dataCenterDialogVisible" @close="handleClose()"
|
|
|
+ :close-on-click-modal="false" :close-on-press-escape="true" center fullscreen :show-close="false"
|
|
|
+ @open="onDialogOpen()" style="width: 100%; height: 100%;">
|
|
|
+ <div style="width: 100%; height: 100%; position: relative;">
|
|
|
+ <el-button round style="position: absolute; top: -65px; left: 0px; z-index: 99;"
|
|
|
+ @click="handleClose">返回</el-button>
|
|
|
+ <DataCenter v-if="dataCenterDialogVisible" :outer-refresh="outerRefresh" :goal-id="goalId"
|
|
|
+ :title="title" :parentGoalsData="[]" :scopeId="scopeId" :cycleType="cycleType" />
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<script>
|
|
|
+import { mapGetters } from 'vuex';
|
|
|
+import GaugeChart from './components/TargetDetail/GaugeChart.vue'
|
|
|
+import PageHead from '@/components/PageHead'; //头部---返回
|
|
|
+import CardList from './components/CardList'; // 卡片列表
|
|
|
+import DismantlingTarget from './components/TargetDetail/DismantlingTarget'; // 拆解目标
|
|
|
+import MultiCycleTable from './components/MultiCycleTable.vue'; // 多周期表格
|
|
|
+import TargetStatus from "./components/TargetDetail/TargetStatus.vue"
|
|
|
+import TaskList from './components/TargetDetail/TaskList.vue';
|
|
|
+import MessageList from './components/TargetDetail/MessageList.vue';
|
|
|
+import EditAutoSubSwitch from "./components/TargetDetail/EditAutoSubSwitch.vue"
|
|
|
+import TargetSearch from "./components/TargetDetail/TargetSearchSingle.vue"
|
|
|
+import DataCenter from './components/DataCenter.vue'
|
|
|
+import EditBaseTableData from "./components/TargetDetail/EditBaseTableData.vue"
|
|
|
+import { splitTarget } from './utils/splitTarget.js'
|
|
|
+import { exportArrayToExcel } from './utils/exportTable.js'
|
|
|
+import cloneDeep from 'lodash.clonedeep';
|
|
|
+import introJs from 'intro.js'
|
|
|
+import 'intro.js/introjs.css'
|
|
|
+import targetDetailSteps from '@/okr/views/targetBusiness/utils/step/targetDetailSteps.js';
|
|
|
+import { targetDetail, planModels, rawData } from '@/okr/views/targetBusiness/utils/step/mock.js';
|
|
|
+
|
|
|
+export default {
|
|
|
+ components: {
|
|
|
+ GaugeChart,
|
|
|
+ CardList,
|
|
|
+ DismantlingTarget,
|
|
|
+ PageHead,
|
|
|
+ TargetStatus,
|
|
|
+ EditAutoSubSwitch,
|
|
|
+ TaskList,
|
|
|
+ MessageList,
|
|
|
+ TargetSearch,
|
|
|
+ DataCenter,
|
|
|
+ EditBaseTableData,
|
|
|
+ MultiCycleTable
|
|
|
+ },
|
|
|
+
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ goalId: '',
|
|
|
+ routePush: null,
|
|
|
+ loading: false,
|
|
|
+ alertTilte: "当前目标正处于待确认状态,请确认好红线值,目标值,挑战值,超越值,预估达成值,完成值,开启实施目标的第一步吧!",
|
|
|
+ showTargetSearch: false,
|
|
|
+ checkList: '',
|
|
|
+ tabs: [
|
|
|
+ { id: 1, name: "概览", class: 'item-overview' },
|
|
|
+ { id: 2, name: "拆解目标", class: 'item-dismantling' },
|
|
|
+ { id: 3, name: "任务", class: 'item-task' },
|
|
|
+ { id: 4, name: "动态", class: 'item-dynamic' }
|
|
|
+ ],
|
|
|
+ employeeMap: this.$getEmployeeMap(),
|
|
|
+ dialogVisible: false,
|
|
|
+ editBaseData: [],
|
|
|
+ title: "",
|
|
|
+ targetDetail: {
|
|
|
+ content: "",
|
|
|
+ deptId: 0,
|
|
|
+ deptName: "",
|
|
|
+ endDate: "",
|
|
|
+ goalId: 1,
|
|
|
+ krId: 0,
|
|
|
+ krName: "",
|
|
|
+ name: "",
|
|
|
+ oid: 0,
|
|
|
+ oname: "",
|
|
|
+ ownerId: 0,
|
|
|
+ ownerName: "",
|
|
|
+ pid: 0,
|
|
|
+ planCount: 0,
|
|
|
+ planFinishCount: 0,
|
|
|
+ planModels: [],
|
|
|
+ pname: "",
|
|
|
+ publishId: "",
|
|
|
+ quantifyModels: [],
|
|
|
+ scope: {},
|
|
|
+ scopeId: 1,
|
|
|
+ standard: "",
|
|
|
+ startDate: "",
|
|
|
+ status: 0,
|
|
|
+ subCompleteCount: 0,
|
|
|
+ subConfirmCount: 0,
|
|
|
+ subCount: 4,
|
|
|
+ subReadyCount: 4,
|
|
|
+ subRunningCount: 0,
|
|
|
+ type: 1,
|
|
|
+ unit: ""
|
|
|
+ },
|
|
|
+ current: 0,
|
|
|
+ currentTab: '概览',
|
|
|
+ autoBySub: false, // 预计达成值, 完成值是否与子目标决定
|
|
|
+ editInitialMap: {},
|
|
|
+ openSvgTarget: true,
|
|
|
+ svgTargetData: [],
|
|
|
+ openSvgTargetDialog: false,
|
|
|
+ cycleTableDialogVisible: false,
|
|
|
+ title: "",
|
|
|
+ dataCenterDialogVisible: false,
|
|
|
+ outerRefresh: 0,
|
|
|
+ cycleType: '',
|
|
|
+ initialPlanModels: null,
|
|
|
+ rawData: {
|
|
|
+ name: "",
|
|
|
+ quarterModel: null,
|
|
|
+ monthModel: null,
|
|
|
+ dayModel: null,
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ autoBySub(oVal, nVal) {
|
|
|
+ if (!nVal) this.getStatisticsData();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ ...mapGetters(['user_info']),
|
|
|
+
|
|
|
+ ownerName() {
|
|
|
+ return this.employeeMap[this.targetDetail.ownerId] && this.employeeMap[this.targetDetail.ownerId].name || '--'
|
|
|
+ },
|
|
|
+ // 创始人,总经理
|
|
|
+ isCreator() {
|
|
|
+ return this.$supremeAuthority('creator')
|
|
|
+ },
|
|
|
+ //是否目标管理员
|
|
|
+ isOkrManager() {
|
|
|
+ return this.user_info.is_okr_manager
|
|
|
+ },
|
|
|
+ // 创始人,OKR管理员,负责人可以拆解目标
|
|
|
+ isAllowDismantlingTarget() {
|
|
|
+ return this.isCreator || this.isOkrManager || this.user_info.id == this.targetDetail.ownerId
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ editConfig() {
|
|
|
+ return ({
|
|
|
+ trigger: 'dblclick',
|
|
|
+ mode: 'cell',
|
|
|
+ beforeEditMethod: this.checkCell, // ← 动态开关
|
|
|
+ autoFocus: true
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 计算完成率
|
|
|
+ percent() {
|
|
|
+ let quantifyModels = this.targetDetail && this.targetDetail.quantifyModels || []
|
|
|
+ if (quantifyModels.length == 0) return 0
|
|
|
+ let target = quantifyModels.find(item => item.code == 'TARGET_VALUE').value // 目标值
|
|
|
+ let result = quantifyModels.find(item => item.code == 'RESULT_VALUE').autoBySub ? quantifyModels.find(item => item.code == 'RESULT_VALUE').subValue : quantifyModels.find(item => item.code == 'RESULT_VALUE').value // 完成值
|
|
|
+ if (target == 0) return 0
|
|
|
+ if (result == 0) return 0
|
|
|
+ else return result ? Number(result / target) * 100 : 0
|
|
|
+ },
|
|
|
+
|
|
|
+ // 是否可以一键平分目标isCanSvgTarget
|
|
|
+ isCanSvgTarget() {
|
|
|
+ return (this.targetDetail.status == 0 || this.targetDetail.status == 1) && this.targetDetail.planModels
|
|
|
+ },
|
|
|
+
|
|
|
+ // 是否有季度数据
|
|
|
+ isHaveQuarterModelData() {
|
|
|
+ return this.rawData && this.rawData.quarterModel && this.rawData.quarterModel.scopes && this.rawData.quarterModel.scopes.length > 0
|
|
|
+ },
|
|
|
+ // 是否有月度数据
|
|
|
+ isHaveMonthModelData() {
|
|
|
+ return this.rawData && this.rawData.monthModel && this.rawData.monthModel.scopes && this.rawData.monthModel.scopes.length > 0
|
|
|
+ },
|
|
|
+ // 是否有日度数据
|
|
|
+ isHaveDayModelData() {
|
|
|
+ return this.rawData && this.rawData.dayModel && this.rawData.dayModel.scopes && this.rawData.dayModel.scopes.length > 0
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ mounted() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.goalId = this.$route.params.goalId;
|
|
|
+ let scopeId = this.$route.query.scopeId, cycleType = this.$route.query.cycleType
|
|
|
+ this.cycleType = this.$route.query.cycleType
|
|
|
+ this.routePush = { path: this.$route.query.from, query: { scopeId, cycleType } }
|
|
|
+ this.getDetails();
|
|
|
+
|
|
|
+ if (localStorage.getItem("isTargetLearned")) {
|
|
|
+ if (!localStorage.getItem('tourTargetDetail')) {
|
|
|
+ this.initStepData();
|
|
|
+ localStorage.setItem('tourTargetDetail', 'true');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ methods: {
|
|
|
+
|
|
|
+ initStepData() {
|
|
|
+ this.current = 0
|
|
|
+ this.currentTab = '概览'
|
|
|
+ this.autoBySub = true
|
|
|
+ this.tabs = [
|
|
|
+ { id: 1, name: "概览", class: 'item-overview' },
|
|
|
+ { id: 2, name: "拆解目标", class: 'item-dismantling' },
|
|
|
+ { id: 3, name: "任务", class: 'item-task' },
|
|
|
+ { id: 4, name: "动态", class: 'item-dynamic' }
|
|
|
+ ];
|
|
|
+ this.targetDetail = targetDetail;
|
|
|
+ this.targetDetail.planModels = { data: null }
|
|
|
+ this.rawData = rawData
|
|
|
+ this.startGuide();
|
|
|
+ },
|
|
|
+
|
|
|
+ startGuide() {
|
|
|
+ let that = this
|
|
|
+ introJs().setOptions({
|
|
|
+ nextLabel: '下一个', // 下一个按钮文字
|
|
|
+ prevLabel: '上一个', // 上一个按钮文字
|
|
|
+ skipLabel: '跳过', // 跳过按钮文字
|
|
|
+ doneLabel: '立即体验',// 完成按钮文字
|
|
|
+ tooltipClass: 'intro-tooltip', /* 引导说明文本框的样式 */
|
|
|
+ highlightClass: 'intro-highlight', /* 说明高亮区域的样式 */
|
|
|
+ exitOnEsc: true, /* 是否使用键盘Esc退出 */
|
|
|
+ exitOnOverlayClick: false, /* 是否允许点击空白处退出 */
|
|
|
+ keyboardNavigation: true, /* 是否允许键盘来操作 */
|
|
|
+ showBullets: false, /* 是否使用点显示进度 */
|
|
|
+ showProgress: false, /* 是否显示进度条 */
|
|
|
+ scrollToElement: true, /* 是否滑动到高亮的区域 */
|
|
|
+ overlayOpacity: 0.5, // 遮罩层的透明度 0-1之间
|
|
|
+ // positionPrecedence: ['bottom', 'top', 'right', 'left'], /* 当位置选择自动的时候,位置排列的优先级 */
|
|
|
+ disableInteraction: false, /* 是否禁止与元素的相互关联 */
|
|
|
+ hidePrev: true, /* 是否在第一步隐藏上一步 */
|
|
|
+ hidePrev: false, // 在第一步中是否隐藏上一个按钮
|
|
|
+ hideNext: false, // 在最后一步中是否隐藏下一个按钮
|
|
|
+ exitOnOverlayClick: false, // 点击叠加层时是否退出介绍
|
|
|
+ showStepNumbers: false, // 是否显示红色圆圈的步骤编号
|
|
|
+ disableInteraction: true, // 是否禁用与突出显示的框内的元素的交互,就是禁止点击
|
|
|
+ showBullets: true, // 是否显示面板指示点
|
|
|
+ // 配置内容 steps数组,内部一个对象代表一个步骤
|
|
|
+ steps: targetDetailSteps,
|
|
|
+ scrollToElement: true, // 自动滚动到目标元素
|
|
|
+ }).onexit((e) => {
|
|
|
+ that.endGuide();
|
|
|
+ }).oncomplete((e) => {
|
|
|
+ that.endGuide();
|
|
|
+ }).onbeforechange((e) => {
|
|
|
+
|
|
|
+ if (e && e.className) {
|
|
|
+ if (e.className.includes('switch-box')) {
|
|
|
+ that.autoBySub = false
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.className.includes('introjsFloatingElement')) {
|
|
|
+ that.targetDetail.planModels = planModels
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }).start()
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ endGuide() {
|
|
|
+ introJs().setOptions({
|
|
|
+ steps: [
|
|
|
+ {
|
|
|
+ element: '#startTour',
|
|
|
+ intro: '教程已结束。您可以在点击这里重新开始教程。'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ overlayOpacity: 0.5, // 遮罩层的透明度 0-1之间
|
|
|
+ showBullets: false,
|
|
|
+ showProgress: false,
|
|
|
+ showStepNumbers: false,
|
|
|
+ tooltipClass: 'intro-tooltip', /* 引导说明文本框的样式 */
|
|
|
+ highlightClass: 'intro-highlight', /* 说明高亮区域的样式 */
|
|
|
+ exitOnEsc: true, /* 是否使用键盘Esc退出 */
|
|
|
+ exitOnOverlayClick: false, /* 是否允许点击空白处退出 */
|
|
|
+ scrollToElement: true, // 自动滚动到目标元素
|
|
|
+ skipLabel: '知道了',
|
|
|
+ doneLabel: '知道了'
|
|
|
+
|
|
|
+ }).onexit((e) => {
|
|
|
+ this.getDetails();
|
|
|
+ }).oncomplete((e) => {
|
|
|
+ this.getDetails();
|
|
|
+ }).onbeforechange((e) => {
|
|
|
+ }).start()
|
|
|
+ },
|
|
|
+
|
|
|
+ goPage() {
|
|
|
+ let { from, scopeId, current, cycleType } = this.$route.query
|
|
|
+ this.$router.push({ name: from, query: { scopeId: scopeId.toString(), current: current || '', cycleType } })
|
|
|
+ },
|
|
|
+
|
|
|
+ openTableData() {
|
|
|
+ this.dialogVisible = true
|
|
|
+ },
|
|
|
+
|
|
|
+ closeDialog(data) {
|
|
|
+ this.getDetails()
|
|
|
+ },
|
|
|
+
|
|
|
+ exportEvent() {
|
|
|
+ exportArrayToExcel(
|
|
|
+ this.$refs.xTable.getColumns(),
|
|
|
+ this.$refs.xTable.getData(),
|
|
|
+ this.targetDetail.name + '-' + this.ownerName + '.xlsx'
|
|
|
+ )
|
|
|
+ },
|
|
|
+
|
|
|
+ // 行级 + 列级 自由控制
|
|
|
+ checkCell({ row, column }) {
|
|
|
+ const scopeId = column.property
|
|
|
+ // 1. 把 values 里的值同步到行上的动态字段
|
|
|
+ const item = row.values.find(v => v.scopeId == scopeId)
|
|
|
+ if (item) row[scopeId] = item.value || 0
|
|
|
+ // 例:整行只读
|
|
|
+ if (!row.allowEdit) return false
|
|
|
+ // 其余放行
|
|
|
+ return true
|
|
|
+ },
|
|
|
+
|
|
|
+ changeTab(index) {
|
|
|
+ this.current = index
|
|
|
+ this.currentTab = this.tabs[index].name
|
|
|
+ },
|
|
|
+
|
|
|
+ openTargetSearch() {
|
|
|
+ this.checkList = this.targetDetail.krId
|
|
|
+ this.showTargetSearch = true
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ updateTargetDetail(data) {
|
|
|
+ this.targetDetail = data
|
|
|
+ this.initEditStatus()
|
|
|
+ },
|
|
|
+
|
|
|
+ confirmSelectedOkr(krIds, oIds) {
|
|
|
+ let url = `/okr/og/goals/bind/${this.user_info.site_id}`
|
|
|
+ let data = {
|
|
|
+ goalId: this.targetDetail.goalId,
|
|
|
+ objectId: oIds.toString(),
|
|
|
+ krId: krIds.toString(),
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$http.post(url, data).then(res => {
|
|
|
+ if (res.code == 1) {
|
|
|
+ this.$message.success('绑定成功')
|
|
|
+ this.getDetails();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return this.$message.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ async getDetails() {
|
|
|
+ this.loading = true
|
|
|
+ let url = `/okr/og/goals/info/${this.user_info.site_id}/${this.goalId}`;
|
|
|
+ let res = await this.$axiosUser('get', url)
|
|
|
+ this.loading = false
|
|
|
+ this.targetDetail = res.data.data
|
|
|
+ let quantifyModels = this.targetDetail && this.targetDetail.quantifyModels || []
|
|
|
+ let { autoBySub } = quantifyModels.find(item => item.code === 'PROJECTED_VALUE')
|
|
|
+ this.autoBySub = autoBySub
|
|
|
+ if (this.targetDetail && this.targetDetail.quantifyModels.length > 0 && this.targetDetail.planModels) {
|
|
|
+ let obj = {
|
|
|
+ code: "DIFF_VALUE",
|
|
|
+ name: "差值(目标值 - 完成值)",
|
|
|
+ values: []
|
|
|
+ }
|
|
|
+ let targetValues = this.targetDetail.planModels.data.find(item => item.code === "TARGET_VALUE").values.map(value => value)
|
|
|
+ let resultValues = this.targetDetail.planModels.data.find(item => item.code === "RESULT_VALUE").values.map(value => value)
|
|
|
+ targetValues.forEach((value, index) => {
|
|
|
+ obj.values.push({ value: (resultValues[index].value - targetValues[index].value).toFixed(2) })
|
|
|
+ // let resultValues = resultValues[index]
|
|
|
+ // let targetValues = targetValues[index]
|
|
|
+ // let isBothInteger = Number.isInteger(resultValues) && Number.isInteger(targetValues)
|
|
|
+ // obj.values.push({ value: isBothInteger ? resultValues - targetValues : (resultValues - targetValues).toFixed(2) })
|
|
|
+ // obj.values.push({ value: resultValues - targetValues })
|
|
|
+ })
|
|
|
+ this.targetDetail.planModels.data.push(obj)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.initialPlanModels = cloneDeep(this.targetDetail.planModels)
|
|
|
+
|
|
|
+ if (this.targetDetail.scope.cycleType == 4) {
|
|
|
+ if (this.targetDetail && this.targetDetail.planModels && this.targetDetail.planModels.data && this.targetDetail.planModels.data.length > 0 ) {
|
|
|
+ let finalData = [];
|
|
|
+ this.targetDetail.planModels.data.forEach(item => {
|
|
|
+ if (item.code === 'TARGET_VALUE' || item.code === 'RESULT_VALUE' || item.code === 'DIFF_VALUE') {
|
|
|
+ finalData.push(item)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.targetDetail.planModels.data = finalData
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(this.targetDetail.planModels.data);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 导出表格数据预处理
|
|
|
+ const scopes = this.targetDetail && this.targetDetail.planModels && this.targetDetail.planModels.scopes || [];
|
|
|
+ if (scopes.length > 0)
|
|
|
+ this.targetDetail.planModels.data = this.flattenRowValues(this.targetDetail.planModels.data, scopes);
|
|
|
+
|
|
|
+ if (this.isAllowDismantlingTarget && this.autoBySub) {
|
|
|
+ this.tabs = [
|
|
|
+ { id: 1, name: "概览", class: 'item-overview' },
|
|
|
+ { id: 2, name: "拆解目标", class: 'item-dismantling' },
|
|
|
+ { id: 3, name: "任务", class: 'item-task' },
|
|
|
+ { id: 4, name: "动态", class: 'item-dynamic' }
|
|
|
+ ]
|
|
|
+ } else {
|
|
|
+ this.tabs = [
|
|
|
+ { id: 1, name: "概览", class: 'item-overview' },
|
|
|
+ { id: 3, name: "任务", class: 'item-task' },
|
|
|
+ { id: 4, name: "动态", class: 'item-dynamic' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ this.initEditStatus();
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ flattenRowValues(list, scopes) {
|
|
|
+ list.forEach(row => {
|
|
|
+ scopes.forEach(s => {
|
|
|
+ const hit = row.values.find(v => v.scopeId === s.scopeId);
|
|
|
+ row[s.scopeId] = hit ? hit.value : ''; // 关键:把值挂到行上
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return list;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ initEditStatus() {
|
|
|
+ // 根据目标状态,动态控制表格列是否可以修改
|
|
|
+ if (this.targetDetail && this.targetDetail.planModels && this.targetDetail.planModels.data && this.targetDetail.planModels.data.length > 0) {
|
|
|
+ this.targetDetail.planModels.data.forEach(item => {
|
|
|
+
|
|
|
+ item.allowEdit = true
|
|
|
+ if( this.targetDetail.ownerId !== this.user_info.id ) {
|
|
|
+ item.allowEdit = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 确认中,制作中,除了完成值,其他都可以修改
|
|
|
+ if (this.targetDetail.status == 0 || this.targetDetail.status == 1) {
|
|
|
+ if (item.code === 'RESULT_VALUE') item.allowEdit = false
|
|
|
+ else item.allowEdit = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行中,可修改预估达成值,完成值
|
|
|
+ if (this.targetDetail.status == 2) {
|
|
|
+ if (item.code === 'RESULT_VALUE' || item.code === 'PROJECTED_VALUE') item.allowEdit = true
|
|
|
+ else item.allowEdit = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已结束,所有值都不能修改
|
|
|
+ if (this.targetDetail.status == 3) {
|
|
|
+ item.allowEdit = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已结束,所有值都不能修改
|
|
|
+ if (item.code == "DIFF_VALUE") {
|
|
|
+ item.allowEdit = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async getStatisticsData() {
|
|
|
+ let url = `/okr/og/goals/plan/statistics/${this.user_info.site_id}/${this.targetDetail.goalId}`
|
|
|
+ let res = await this.$axiosUser('get', url)
|
|
|
+ this.rawData = res.data.data
|
|
|
+ // let obj = {
|
|
|
+ // code: "DIFF_VALUE",
|
|
|
+ // name: "差值(目标值 - 完成值)",
|
|
|
+ // values: []
|
|
|
+ // }
|
|
|
+ // if (this.isHaveQuarterModelData) {
|
|
|
+ // let targetValues = this.rawData.quarterModel.data.find(item => item.code === "TARGET_VALUE").values.map(value => value.value)
|
|
|
+ // let resultValues = this.rawData.quarterModel.data.find(item => item.code === "RESULT_VALUE").values.map(value => value.value)
|
|
|
+ // targetValues.forEach((value, index) => {
|
|
|
+ // let resultValues = resultValues[index].value
|
|
|
+ // let targetValues = targetValues[index].value
|
|
|
+ // let isBothInteger = Number.isInteger(resultValues) && Number.isInteger(targetValues)
|
|
|
+ // obj.values.push({ value: isBothInteger ? resultValues - targetValues : (resultValues - targetValues).toFixed(2) })
|
|
|
+ // })
|
|
|
+ // this.rawData.quarterModel.data.push(obj)
|
|
|
+ // obj = {
|
|
|
+ // code: "DIFF_VALUE",
|
|
|
+ // name: "差值(目标值 - 完成值)",
|
|
|
+ // values: []
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // if (this.isHaveMonthModelData) {
|
|
|
+ // let targetValues = this.rawData.monthModel.data.find(item => item.code === "TARGET_VALUE").values.map(value => value.value)
|
|
|
+ // let resultValues = this.rawData.monthModel.data.find(item => item.code === "RESULT_VALUE").values.map(value => value.value)
|
|
|
+ // targetValues.forEach((value, index) => {
|
|
|
+ // let resultValues = resultValues[index].value
|
|
|
+ // let targetValues = targetValues[index].value
|
|
|
+ // let isBothInteger = Number.isInteger(resultValues) && Number.isInteger(targetValues)
|
|
|
+ // obj.values.push({ value: isBothInteger ? resultValues - targetValues : (resultValues - targetValues).toFixed(2) })
|
|
|
+ // })
|
|
|
+ // this.rawData.monthModel.data.push(obj)
|
|
|
+ // obj = {
|
|
|
+ // code: "DIFF_VALUE",
|
|
|
+ // name: "差值(目标值 - 完成值)",
|
|
|
+ // values: []
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // if (this.isHaveDayModelData) {
|
|
|
+ // let targetValues = this.rawData.dayModel.data.find(item => item.code === "TARGET_VALUE").values.map(value => value.value)
|
|
|
+ // let resultValues = this.rawData.dayModel.data.find(item => item.code === "RESULT_VALUE").values.map(value => value.value)
|
|
|
+ // targetValues.forEach((value, index) => {
|
|
|
+ // let resultValues = resultValues[index].value
|
|
|
+ // let targetValues = targetValues[index].value
|
|
|
+ // let isBothInteger = Number.isInteger(resultValues) && Number.isInteger(targetValues)
|
|
|
+ // obj.values.push({ value: isBothInteger ? resultValues - targetValues : (resultValues - targetValues).toFixed(2) })
|
|
|
+ // })
|
|
|
+ // this.rawData.dayModel.data.push(obj)
|
|
|
+ // obj = {
|
|
|
+ // code: "DIFF_VALUE",
|
|
|
+ // name: "差值(目标值 - 完成值)",
|
|
|
+ // values: []
|
|
|
+ // }
|
|
|
+ // if (this.targetDetail.scope.cycleType == 4) {
|
|
|
+ // let finalData = [];
|
|
|
+ // this.rawData.dayModel.data.forEach(item => {
|
|
|
+ // if (item.code === 'TARGET_VALUE' || item.code === 'RESULT_VALUE' || item.code === 'DIFF_VALUE') {
|
|
|
+ // finalData.push(item)
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ // this.rawData.dayModel.data = finalData
|
|
|
+ // }
|
|
|
+
|
|
|
+ // }
|
|
|
+
|
|
|
+ let obj = {
|
|
|
+ code: "DIFF_VALUE",
|
|
|
+ name: "差值(目标值 - 完成值)",
|
|
|
+ values: []
|
|
|
+ }
|
|
|
+ if (this.isHaveQuarterModelData) {
|
|
|
+ let targetValues = this.rawData.quarterModel.data.find(item => item.code === "TARGET_VALUE").values.map(value => value.value)
|
|
|
+ let resultValues = this.rawData.quarterModel.data.find(item => item.code === "RESULT_VALUE").values.map(value => value.value)
|
|
|
+ targetValues.forEach((value, index) => {
|
|
|
+ obj.values.push({ value: (resultValues[index].value - targetValues[index].value) })
|
|
|
+ })
|
|
|
+ this.rawData.quarterModel.data.push(obj)
|
|
|
+ obj = {
|
|
|
+ code: "DIFF_VALUE",
|
|
|
+ name: "差值(目标值 - 完成值)",
|
|
|
+ values: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.isHaveMonthModelData) {
|
|
|
+ let targetValues = this.rawData.monthModel.data.find(item => item.code === "TARGET_VALUE").values.map(value => value.value)
|
|
|
+ let resultValues = this.rawData.monthModel.data.find(item => item.code === "RESULT_VALUE").values.map(value => value.value)
|
|
|
+ targetValues.forEach((value, index) => {
|
|
|
+ obj.values.push({ value: (resultValues[index].value - targetValues[index].value) })
|
|
|
+ })
|
|
|
+ this.rawData.monthModel.data.push(obj)
|
|
|
+ obj = {
|
|
|
+ code: "DIFF_VALUE",
|
|
|
+ name: "差值(目标值 - 完成值)",
|
|
|
+ values: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.isHaveDayModelData) {
|
|
|
+ let targetValues = this.rawData.dayModel.data.find(item => item.code === "TARGET_VALUE").values.map(value => value.value)
|
|
|
+ let resultValues = this.rawData.dayModel.data.find(item => item.code === "RESULT_VALUE").values.map(value => value.value)
|
|
|
+ targetValues.forEach((value, index) => {
|
|
|
+ obj.values.push({ value: resultValues[index] - targetValues[index] })
|
|
|
+ })
|
|
|
+ this.rawData.dayModel.data.push(obj)
|
|
|
+ obj = {
|
|
|
+ code: "DIFF_VALUE",
|
|
|
+ name: "差值(目标值 - 完成值)",
|
|
|
+ values: []
|
|
|
+ }
|
|
|
+ let finalData = [];
|
|
|
+ this.rawData.dayModel.data.forEach(item => {
|
|
|
+ if (item.code === 'TARGET_VALUE' || item.code === 'RESULT_VALUE' || item.code === 'DIFF_VALUE') {
|
|
|
+ finalData.push(item)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.rawData.dayModel.data = finalData
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // 按日,月,季规划计划
|
|
|
+ planByDate(date) {
|
|
|
+ let url = `/okr/og/goals/plan/${date}/${this.user_info.site_id}/${this.goalId}`;
|
|
|
+ this.$axiosUser('post', url).then(res => {
|
|
|
+ if (res.data.code == 1) {
|
|
|
+ this.getDetails();
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // 删除计划
|
|
|
+ deletePlan() {
|
|
|
+ this.$confirm('删除后,所有时间计划数据将被清空,是否继续?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+
|
|
|
+ let url = `/okr/og/goals/plan/remove/${this.user_info.site_id}/${this.goalId}`
|
|
|
+ this.$axiosUser('post', url).then(res => {
|
|
|
+ if (res.data.code = 1) {
|
|
|
+ thi.getDetails();
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message.info('已取消删除')
|
|
|
+ });
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ handleEditActived({ row, column }) {
|
|
|
+ // 记录初始值
|
|
|
+ // this.initialValue = row.name;
|
|
|
+ if (!row.allowEdit) return;
|
|
|
+
|
|
|
+ const scopeId = column.field;
|
|
|
+ const valueObj = row.values.find(v => v.scopeId === scopeId);
|
|
|
+ if (valueObj) {
|
|
|
+ const key = `${scopeId}`;
|
|
|
+ this.editInitialMap[key] = valueObj.value
|
|
|
+ // this.$set(this.editInitialMap, key, valueObj.value);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 单元格编辑完成的回调
|
|
|
+ async handleEditClosed(e) {
|
|
|
+ if (!e) return
|
|
|
+ let prop = e.column.field
|
|
|
+ let code = e.row.code
|
|
|
+ let value = e.row[prop] == '' || e.row[prop] == null ? 0 : e.row[prop]
|
|
|
+ let { valueId } = e.row.values.find(value => value.scopeId == prop)
|
|
|
+ e.row.values.find(value => value.scopeId == prop).value = value
|
|
|
+
|
|
|
+ const valueObj = e.row.values.find(v => v.scopeId == prop);
|
|
|
+ const key = `${prop}`;
|
|
|
+ const initial = this.editInitialMap[key];
|
|
|
+ const current = valueObj.value;
|
|
|
+
|
|
|
+
|
|
|
+ if (current == null || current == '') {
|
|
|
+ valueObj.value = initial; // 还原
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let res;
|
|
|
+ if (e.row.name === '完成值') {
|
|
|
+ res = await this.handleEditProjectedValue(value, valueId, this.goalId, 'result')
|
|
|
+
|
|
|
+ } else if (e.row.name.includes('预估')) {
|
|
|
+ res = await this.handleEditProjectedValue(value, valueId, this.goalId, 'projected')
|
|
|
+ } else {
|
|
|
+ res = await this.handleEditOtherValue(code, value, valueId, this.goalId)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.code == 1) {
|
|
|
+ this.getDetails()
|
|
|
+ // 清理缓存
|
|
|
+ this.$delete(this.editInitialMap, key);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return this.$message.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // 修改目标预计达成值,完成值
|
|
|
+ async handleEditProjectedValue(value, valueId, goalId, editValue) {
|
|
|
+ let url = `/okr/og/goals/plan/${editValue}/${this.user_info.site_id}`
|
|
|
+ let data = {
|
|
|
+ goalId,
|
|
|
+ valueId,
|
|
|
+ value
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 直接把响应体当成返回值
|
|
|
+ const res = await this.$http.post(url, data)
|
|
|
+ return res // ← 这就是“请求的结果作为返回值”
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error(e.message || '保存失败')
|
|
|
+ throw e // 继续向上抛,方便调用方捕获
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改红线值,挑战值,目标值,超越值
|
|
|
+ async handleEditOtherValue(code, value, valueId, goalId) {
|
|
|
+ let url = `/okr/og/goals/plan/base/${this.user_info.site_id}`
|
|
|
+ let data = {
|
|
|
+ goalId,
|
|
|
+ value,
|
|
|
+ valueId,
|
|
|
+ code
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 直接把响应体当成返回值
|
|
|
+ const res = await this.$http.post(url, data)
|
|
|
+ return res // ← 这就是“请求的结果作为返回值”
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error(e.message || '保存失败')
|
|
|
+ throw e // 继续向上抛,方便调用方捕获
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ // 平分数据
|
|
|
+ svgTargetNums() {
|
|
|
+ // 平分区间
|
|
|
+ let range = this.targetDetail.planModels.scopes.length || 0
|
|
|
+ // 红线值 - 目标数
|
|
|
+ let riskValueTarget = this.targetDetail.quantifyModels.find(item => item.code === 'RISK_VALUE').value
|
|
|
+ // 目标值 - 目标数
|
|
|
+ let targetValueTarget = this.targetDetail.quantifyModels.find(item => item.code === 'TARGET_VALUE').value
|
|
|
+ // 挑战值 - 目标数
|
|
|
+ let challengeValueTarget = this.targetDetail.quantifyModels.find(item => item.code === 'CHALLENGE_VALUE').value
|
|
|
+ // 超越值 - 目标数
|
|
|
+ let surpassValueTarget = this.targetDetail.quantifyModels.find(item => item.code === 'SURPASS_VALUE').value
|
|
|
+ // 预估达成值 - 目标数
|
|
|
+ let projectValueTarget = this.targetDetail.quantifyModels.find(item => item.code === 'PROJECTED_VALUE').value
|
|
|
+ // 如果目标数为0,返回一个全是0的数组
|
|
|
+ let zeroArray = Array.from({ length: range }, (v, i) => 0)
|
|
|
+ // 平分红线值
|
|
|
+ let riskValues = riskValueTarget !== 0 ? splitTarget(riskValueTarget, range) : zeroArray;
|
|
|
+ // 平分目标值
|
|
|
+ let targetValues = targetValueTarget !== 0 ? splitTarget(targetValueTarget, range) : zeroArray;
|
|
|
+ // 平分挑战值
|
|
|
+ let challengeValues = challengeValueTarget !== 0 ? splitTarget(challengeValueTarget, range) : zeroArray;
|
|
|
+ // 平分超越值
|
|
|
+ let surpassValues = surpassValueTarget !== 0 ? splitTarget(surpassValueTarget, range) : zeroArray;
|
|
|
+ // 平分预估达成值
|
|
|
+ let projectValues = projectValueTarget !== 0 ? splitTarget(projectValueTarget, range) : zeroArray;
|
|
|
+ let svgTargetData = []
|
|
|
+
|
|
|
+ this.initialPlanModels.data.forEach((item) => {
|
|
|
+ if (item.code === "RISK_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: riskValues[index] }))
|
|
|
+ }
|
|
|
+ if (item.code === "TARGET_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: targetValues[index] }))
|
|
|
+ }
|
|
|
+ if (item.code === "CHALLENGE_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: challengeValues[index] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if (item.code === "SURPASS_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: surpassValues[index] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if (item.code === "PROJECTED_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: projectValues[index] }))
|
|
|
+ }
|
|
|
+ if (item.code !== "DIFF_VALUE") svgTargetData.push(item)
|
|
|
+ })
|
|
|
+
|
|
|
+ this.targetDetail.planModels.data.forEach((item) => {
|
|
|
+ if (item.code === "RISK_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({...value, value: riskValues[index]}))
|
|
|
+ }
|
|
|
+ if (item.code === "TARGET_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: targetValues[index] }))
|
|
|
+ }
|
|
|
+ if (item.code === "CHALLENGE_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: challengeValues[index] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if (item.code === "SURPASS_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: surpassValues[index] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if (item.code === "PROJECTED_VALUE") {
|
|
|
+ item.values = item.values.map((value, index) => ({ ...value, value: projectValues[index] }))
|
|
|
+ }
|
|
|
+ if (item.code !== "DIFF_VALUE") svgTargetData.push(item)
|
|
|
+ })
|
|
|
+ this.openSvgTarget = false;
|
|
|
+ this.svgTargetData = cloneDeep(this.targetDetail.planModels)
|
|
|
+ this.svgTargetData.data = this.svgTargetData.data.filter(item => item.code !== 'DIFF_VALUE')
|
|
|
+
|
|
|
+ // const scopeLen = this.targetDetail.planModels.scopes.length || 0
|
|
|
+ // // 一次性提取所有目标值
|
|
|
+ // const targetMap = new Map(
|
|
|
+ // this.targetDetail.quantifyModels
|
|
|
+ // .filter(m => ['红线值', '目标值', '挑战值', '超越值', '预估达成值'].includes(m.name))
|
|
|
+ // .map(m => [m.name, m.value])
|
|
|
+ // )
|
|
|
+
|
|
|
+ // // 预生成所有字段的均分数组(0 则全 0)
|
|
|
+ // const splitMap = new Map(
|
|
|
+ // ['红线值', '目标值', '挑战值', '超越值', '预估达成值'].map(key => [
|
|
|
+ // key,
|
|
|
+ // targetMap.get(key) !== 0 ? splitTarget(targetMap.get(key) || 0, scopeLen) : Array.from({ length: scopeLen }, () => 0)
|
|
|
+ // ])
|
|
|
+ // )
|
|
|
+
|
|
|
+
|
|
|
+ // // 一次循环赋值
|
|
|
+ // this.svgTargetData.data.forEach(item => {
|
|
|
+ // const arr = splitMap.get(item.name)
|
|
|
+ // if (arr) {
|
|
|
+ // item.values = item.values.map((v, i) => ({ ...v, value: arr[i] }))
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+
|
|
|
+ this.openSvgTargetDialog = true
|
|
|
+ },
|
|
|
+
|
|
|
+ saveSvgTargetNums() {
|
|
|
+ let url = `/okr/og/goals/plan/base/multi/${this.user_info.site_id}/${this.goalId}`
|
|
|
+ this.initialPlanModels.data = this.initialPlanModels.data.filter(item => item.code !== 'DIFF_VALUE' || item.code === 'RESULT_VALUE' || item.code === 'TARGET_VALUE')
|
|
|
+ let data = { ...this.initialPlanModels }
|
|
|
+ console.log(data)
|
|
|
+ this.$http.post(url, data).then(res => {
|
|
|
+ if (res.code == 1) {
|
|
|
+ this.openSvgTargetDialog = false
|
|
|
+ this.openSvgTarget = true;
|
|
|
+ this.$message.success("操作成功")
|
|
|
+ this.getDetails();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return this.$message.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ resetData() {
|
|
|
+ this.openSvgTarget = true;
|
|
|
+ this.openSvgTargetDialog = false
|
|
|
+ this.getDetails();
|
|
|
+ },
|
|
|
+
|
|
|
+ getDataCenter() {
|
|
|
+ this.goalId = this.targetDetail.goalId
|
|
|
+ this.title = this.targetDetail.name + '-' + this.ownerName
|
|
|
+ this.goalList = [{
|
|
|
+ goalId: this.targetDetail.goalId,
|
|
|
+ name: this.targetDetail.name,
|
|
|
+ ownerName: this.ownerName
|
|
|
+ }];
|
|
|
+ this.dataCenterDialogVisible = true
|
|
|
+ },
|
|
|
+
|
|
|
+ /* 拦截浏览器返回键 */
|
|
|
+ blockPopstate(e) {
|
|
|
+ // 阻止默认返回行为
|
|
|
+ e.preventDefault();
|
|
|
+ // 可以选择关闭 dialog
|
|
|
+ this.cycleTableDialogVisible = false;
|
|
|
+ this.dataCenterDialogVisible = false;
|
|
|
+ // 重新插入一条记录,防止再次返回
|
|
|
+ history.pushState(null, null, location.href);
|
|
|
+ },
|
|
|
+
|
|
|
+ handleClose() {
|
|
|
+ this.dataCenterDialogVisible = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /* 开启 dialog 时:禁用返回键 */
|
|
|
+ onDialogOpen() {
|
|
|
+ // 立即往 history 里插一条空记录,拦截返回
|
|
|
+ history.pushState(null, null, location.href);
|
|
|
+ // 监听 popstate
|
|
|
+ window.addEventListener('popstate', this.blockPopstate, false);
|
|
|
+ // 延迟200ms,等待全屏动画彻底结束
|
|
|
+ setTimeout(() => {
|
|
|
+ this.outerRefresh += 1
|
|
|
+ }, 200)
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ handleCycleTableClose() {
|
|
|
+ this.cycleTableDialogVisible = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /* 开启 dialog 时:禁用返回键 */
|
|
|
+ handleCycleTableOpen() {
|
|
|
+ // 立即往 history 里插一条空记录,拦截返回
|
|
|
+ history.pushState(null, null, location.href);
|
|
|
+ // 监听 popstate
|
|
|
+ window.addEventListener('popstate', this.blockPopstate, false);
|
|
|
+ },
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+
|
|
|
+/* 让标题与按钮在同一行,按钮靠右 */
|
|
|
+.alert-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.all {
|
|
|
+
|
|
|
+
|
|
|
+ .el-switch__core {
|
|
|
+ width: 30px !important;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-switch__core::after {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ margin-top: -1px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-switch.is-checked .el-switch__core::after {
|
|
|
+ margin-left: -15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .multi-cycle-table {
|
|
|
+
|
|
|
+ .el-dialog__body {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-center-box {
|
|
|
+ .el-dialog__header {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__body {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+
|
|
|
+.color-green { color: #2ecc71; }
|
|
|
+.color-red { color: #e74c3c; }
|
|
|
+
|
|
|
+.line {
|
|
|
+ width: 4px;
|
|
|
+ height: 20px;
|
|
|
+ background: #1299f9;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.base-table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ margin: 10px auto;
|
|
|
+ border-right: 1px solid #ddd;
|
|
|
+ border-top: 1px solid #ddd;
|
|
|
+}
|
|
|
+
|
|
|
+.base-table th,
|
|
|
+.base-table td {
|
|
|
+ padding: 3px 6px;
|
|
|
+ text-align: center;
|
|
|
+ border-bottom: 1px solid #ddd;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.base-table th {
|
|
|
+ background-color: #f2f2f2;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.base-table td {
|
|
|
+ border-left: 1px solid #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+.base-table tr:hover {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.all {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .PageHead {
|
|
|
+ height: 42px;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ span {
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ span:nth-child(1) {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 42px;
|
|
|
+ color: #8f8f8f;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ span:nth-child(1)::after {
|
|
|
+ position: absolute;
|
|
|
+ content: " ";
|
|
|
+ height: 18px;
|
|
|
+ width: 1px;
|
|
|
+ background-color: #8f8f8f;
|
|
|
+ right: 0px;
|
|
|
+ top: 50%;
|
|
|
+ margin-top: -9px;
|
|
|
+ }
|
|
|
+
|
|
|
+ span:nth-child(1):hover {
|
|
|
+ color: #1299f9;
|
|
|
+ }
|
|
|
+
|
|
|
+ span:nth-child(2) {
|
|
|
+ padding-left: 12px;
|
|
|
+ color: #585858;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slot {
|
|
|
+ margin-left: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .top {
|
|
|
+ width: 100%;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+
|
|
|
+ .icon {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 20px;
|
|
|
+ color: #999;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.3s;
|
|
|
+ margin-left: 10px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ cursor: pointer;
|
|
|
+ background: #f5f5f5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom {
|
|
|
+ width: 100%;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+
|
|
|
+ .cursor-pointer {
|
|
|
+ display: flex !important;
|
|
|
+ text-align: left !important;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .target-info {
|
|
|
+ width: 100%;
|
|
|
+ height: 140px;
|
|
|
+ padding: 0 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .type-img-box {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: 50%;
|
|
|
+ color: white;
|
|
|
+ font-size: 30px;
|
|
|
+ background-color: #409EFF;
|
|
|
+ margin-right: 10px;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ .name {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin: 5px 0;
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ .tag {
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin-right: 10px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart1 {
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ .switch-box {
|
|
|
+ display: flex;
|
|
|
+ margin-top: 5px;
|
|
|
+ // right: 100px;
|
|
|
+ // top: 80px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .tab-list {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin: 0 0 10px 0;
|
|
|
+ border-bottom: 1px solid #eee;
|
|
|
+ .tab-item {
|
|
|
+ padding: 0 10px;
|
|
|
+ height: 40px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ &:hover {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ &.active {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #409EFF;
|
|
|
+ font-weight: bolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ .line {
|
|
|
+ width: 30px;
|
|
|
+ height: 3px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #409EFF;
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-list {
|
|
|
+ gap: 12px;
|
|
|
+ padding: 0 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ .card-item {
|
|
|
+ width: 20%;
|
|
|
+ height: 100px;
|
|
|
+ border-radius: 12px;
|
|
|
+ background-color: #409EFF;
|
|
|
+ padding: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ .card-title {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-num {
|
|
|
+ margin-top: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ & .card-item:nth-child(1) {
|
|
|
+ background: #FFEEEB;
|
|
|
+ }
|
|
|
+
|
|
|
+ & .card-item:nth-child(2) {
|
|
|
+ background: #E9F8F7;
|
|
|
+ }
|
|
|
+
|
|
|
+ & .card-item:nth-child(3) {
|
|
|
+ background: #FCF9ED;
|
|
|
+ }
|
|
|
+
|
|
|
+ & .card-item:nth-child(4) {
|
|
|
+ background: #FFF7EE;
|
|
|
+ }
|
|
|
+
|
|
|
+ & .card-item:nth-child(5) {
|
|
|
+ background: #F3F3FE;
|
|
|
+ }
|
|
|
+
|
|
|
+ & .card-item:nth-child(6) {
|
|
|
+ background: #c6f7de;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .top {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .h-100 {
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .bottom {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ margin-top: 20px;
|
|
|
+ .title-box {
|
|
|
+ width: 100;
|
|
|
+ height: 50px;
|
|
|
+ padding: 0 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ .title {
|
|
|
+ margin-right: 10px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-box {
|
|
|
+ flex: 1;
|
|
|
+ width: 100%;
|
|
|
+ padding: 0 20px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ /* 设置滚动条的宽度和背景色 */
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 6px !important;
|
|
|
+ height: 6px !important;
|
|
|
+ background-color: #f9f9f9 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设置滚动条滑块的样式 */
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ border-radius: 6px !important;
|
|
|
+ background-color: #c1c1c1 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设置滚动条滑块hover样式 */
|
|
|
+ &::-webkit-scrollbar-thumb:hover {
|
|
|
+ background-color: #a8a8a8 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设置滚动条轨道的样式 */
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1) !important;
|
|
|
+ border-radius: 6px !important;
|
|
|
+ background: #ededed !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.introjs-helperLayer {
|
|
|
+ box-shadow: rgba(33, 33, 33, 0.8) 0px 0px 1px 0px, rgba(33, 33, 33, 0.5) 0px 0px 0px 5000px !important;
|
|
|
+ border: 3px dashed #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 调整 intro.js 弹出框的大小 */
|
|
|
+.introjs-tooltip {
|
|
|
+ width: auto;
|
|
|
+ /* 自动调整宽度 */
|
|
|
+ max-width: 1600px;
|
|
|
+ /* 最大宽度 */
|
|
|
+ height: auto;
|
|
|
+ /* 自动调整高度 */
|
|
|
+ overflow: hidden;
|
|
|
+ /* 防止内容溢出 */
|
|
|
+}
|
|
|
+
|
|
|
+.new-tips {
|
|
|
+ color: #409eff;
|
|
|
+ line-height: 80px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-tooltip-title {
|
|
|
+ font-size: 16px;
|
|
|
+ width: 80%;
|
|
|
+ padding-top: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.warper {
|
|
|
+ width: 200px;
|
|
|
+ height: 100px;
|
|
|
+ line-height: 100px;
|
|
|
+ text-align: center;
|
|
|
+ border: 1px solid saddlebrown;
|
|
|
+}
|
|
|
+
|
|
|
+/* 重置引导组件样式(类似element-ui个人使用) */
|
|
|
+.intro-tooltip {
|
|
|
+ color: #ffff;
|
|
|
+ background: #2c3e50;
|
|
|
+}
|
|
|
+
|
|
|
+/* 引导提示框的位置 */
|
|
|
+.introjs-bottom-left-aligned {
|
|
|
+ left: 45% !important;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-right,
|
|
|
+.introjs-left {
|
|
|
+ top: 30%;
|
|
|
+}
|
|
|
+
|
|
|
+.intro-highlight {
|
|
|
+ background: rgba(255, 255, 255, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-arrow.left {
|
|
|
+ border-right-color: #2c3e50;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-arrow.top {
|
|
|
+ border-bottom-color: #2c3e50;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-arrow.right {
|
|
|
+ border-left-color: #2c3e50;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-arrow.bottom {
|
|
|
+ border-top-color: #2c3e50;
|
|
|
+}
|
|
|
+
|
|
|
+/* 提示框头部区域 */
|
|
|
+.introjs-tooltip-header {
|
|
|
+ padding-right: 0 !important;
|
|
|
+ padding-top: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-skipbutton {
|
|
|
+ color: #409eff !important;
|
|
|
+ font-size: 14px !important;
|
|
|
+ font-weight: normal !important;
|
|
|
+ // padding: 8px 10px !important ;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-tooltipbuttons {
|
|
|
+ border: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-tooltiptext {
|
|
|
+ font-size: 14px !important;
|
|
|
+ padding: 15px !important;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-tooltiptext .title{
|
|
|
+ min-width: 600px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 提示框按钮 */
|
|
|
+.introjs-tooltipbuttons {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-button {
|
|
|
+ width: 50px !important;
|
|
|
+ text-align: center;
|
|
|
+ padding: 4px !important;
|
|
|
+ font-size: 12px !important;
|
|
|
+ font-weight: 500 !important;
|
|
|
+ border-radius: 3px !important;
|
|
|
+ border: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-button:last-child {
|
|
|
+ margin-left: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-prevbutton {
|
|
|
+ color: #606266 !important;
|
|
|
+ background: #fff !important;
|
|
|
+ border: 1px solid #dcdfe6 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-nextbutton {
|
|
|
+ color: #fff !important;
|
|
|
+ background-color: #409eff !important;
|
|
|
+ border-color: #409eff !important;
|
|
|
+}
|
|
|
+
|
|
|
+.introjs-disabled {
|
|
|
+ color: #9e9e9e !important;
|
|
|
+ border-color: #bdbdbd !important;
|
|
|
+ background-color: #f4f4f4 !important;
|
|
|
+}
|
|
|
+</style>
|