@@ -4,7 +4,7 @@
< header class = "topbar" > < div class = "topbar-center" > < div class = "model-title" > XXX特大桥主体模型 . rvt < / div > < / div > < / header >
< section class = "model-stage" >
< ModelPlaceholder / >
< ModelPlaceholder ref = "modelRef" @componentBindEncoding ="onComponentBindEncoding" @codeBindComponent ="codeBindComponent" / >
< / section >
< section class = "module-topbar" >
@@ -33,15 +33,86 @@
< button class = "iconbtn" type = "button" @click ="sideCollapsed = !sideCollapsed" > {{ sideCollapsed ? " ▸ " : " ▾ " }} < / button >
< / header >
< div class = "sidepanel-body" v-show = "!sideCollapsed" >
< div class = "tree" >
< div class = "tree-item" : class = "{ 'is-active': row.leaf && row.id === selectedStructureId }" : style = "{ paddingLeft: `${10 + row.level * 14}px` }" v-for = "row in visibleTreeRows" :key="row.id" @click="onTreeRowClick(row)" >
< button class = "tree-caret" type = "button" : class = "{ 'is-leaf': row.leaf }" :disabled = "row.leaf" @click.stop ="toggleTreeExpand(row)" > {{ row.leaf ? " • " : row.open ? " ▾ " : " ▸ " }} < / button >
< span class = "tree-bullet" > < / span >
< span class = "tree-text" > { { row . name } } < / span >
< div class = "tree" >
< div class = "tree-item" : class = "{ 'is-active': row.leaf && row.id === selectedStructureId }" : style = "{ paddingLeft: `${10 + row.level * 14}px` }" v-for = "row in visibleTreeRows" :key="row.id" @click="onTreeRowClick(row)" @contextmenu.prevent="onTreeRightClick($event, row)" >
< button class = "tree-caret" type = "button" : class = "{ 'is-leaf': row.leaf }" :disabled = "row.leaf" @click.stop ="toggleTreeExpand(row)" > {{ row.leaf ? " • " : row.open ? " ▾ " : " ▸ " }} < / button >
< span class = "tree-bullet" > < / span >
< span class = "tree-text" > { { row . name } } < / span >
< / div >
< / div >
< div v-if = "showContextMenu" class="context-menu" :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }" @click.stop >
< div class = "context-menu-item" @click ="onAddComponent" > 绑定编码 < / div >
< div class = "context-menu-item" @click ="onViewComponent" > 查看编辑 < / div >
< / div >
< div v-if = "showAddModal" class="add-modal" :style="modalPosition" @pointerdown.stop="onModalPointerDown" >
< header class = "add-modal-header" >
< div class = "add-modal-title" > 绑定编码 < / div >
< button class = "add-modal-close" type = "button" @click ="showAddModalClose" > ✕ < / button >
< / header >
< div class = "add-modal-body" >
< div class = "add-modal-split" >
< div class = "add-modal-left" >
< div class = "add-modal-section-title" > 部位列表 < / div >
< div class = "add-modal-list" >
< div class = "add-modal-tag" v-for = "(id, index) in partList" :key="index" >
< span > { { getStructureName ( id . id ) } } < / span >
< button class = "add-modal-tag-delete" type = "button" @click ="partList.splice(index, 1)" > ✕ < / button >
< / div >
< / div >
< / div >
< div class = "add-modal-right" >
< div class = "add-modal-section-title" > 构件列表 < / div >
< div class = "add-modal-list" >
< div class = "add-modal-tag" v-for = "(item, index) in componentList" :key="index" >
< ! - - < input class = "add-form-input" v-model = "item.code" placeholder="请输入构件编码" / > -- >
< span > { { item . code } } < / span >
< button class = "add-modal-tag-delete" type = "button" @click ="componentList.splice(index, 1)" > ✕ < / button >
< / div >
< / div >
< / div >
< / div >
< div class = "add-modal-footer" >
< button class = "add-modal-btn add-modal-btn-secondary" type = "button" @click ="showAddModalHandle" > 取消 < / button >
< button class = "add-modal-btn add-modal-btn-primary" type = "button" @click ="onSaveComponent" > 保存 < / button >
< / div >
< / div >
< / div >
< div v-if = "showViewModal" class="add-modal" :style="viewModalPosition" @pointerdown.stop="onViewModalPointerDown" >
< header class = "add-modal-header" >
< div class = "add-modal-title" > 查看构件 < / div >
< button class = "add-modal-close" type = "button" @click ="showViewModal = false" > ✕ < / button >
< / header >
< div class = "add-modal-body" >
< div class = "add-modal-split" >
< div class = "add-modal-left" >
< div class = "add-modal-section-title" > 部位列表 < / div >
< div class = "add-modal-list" >
< div class = "add-modal-tag" v-for = "(id, index) in viewPartList" :key="index" >
< span > { { getStructureName ( id ) } } < / span >
< / div >
< / div >
< / div >
< div class = "add-modal-right" >
< div class = "add-modal-section-title" > 构件列表 < / div >
< div class = "add-modal-list" >
< div class = "add-modal-tag" v-for = "(item, index) in viewComponentList" :key="index" >
< span > { { item . codeId } } < / span >
< button class = "add-modal-tag-delete" type = "button" @click ="viewComponentList.splice(index, 1)" > ✕ < / button >
< / div >
< / div >
< / div >
< / div >
< div class = "add-modal-footer" >
< button class = "add-modal-btn add-modal-btn-secondary" type = "button" @click ="showViewModal = false" > 取消 < / button >
< button class = "add-modal-btn add-modal-btn-primary" type = "button" @click ="onDeleteComponent" > 保存 < / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< / aside >
< / aside >
< section class = "bottompanel" : class = "{ 'is-collapsed': bottomCollapsed }" >
< header class = "bottompanel-header" > < div class = "tabs" > < button class = "tab is-on" type = "button" > 项目日进度明细 < / button > < / div > < / header >
@@ -51,12 +122,12 @@
< thead >
< tr >
<!-- < th > 序号 < / th > < th > 全名称 < / th > < th > 清单编号 < / th > < th > 清单名称 < / th > < th > 单位 < / th > < th > 合同数量 < / th > < th > 合同金额 < / th > < th > 填报日期 < / th > < th > 完成数量 < / th > < th > 完成金额 < / th > < th > 累计完成数量 < / th > -- >
< th > 序号 < / th > < th > 全名称 < / th > < th > 清单编号 < / th > < th > 清单名称 < / th > < th > 单位 < / th > < th > 合同数量 < / th > < th > 合同金额 < / th > < th > 合同金额不含税 < / th >
< th > 序号 < / th > < th > 全名称 < / th > < th > 清单编号 < / th > < th > 清单名称 < / th > < th > 单位 < / th > < th > 合同数量 < / th > < th > 合同金额 < / th > < th > 合同金额不含税 < / th > < th > 累计完成数量 < / th > < th > 进度 < / th >
< / tr >
< / thead >
< tbody >
< tr v-for = "r in progressRows" :key="r.no" >
< td > { { r . no } } < / td > < td > { { r . fullName } } < / td > < td > { { r . workCode } } < / td > < td > { { r . name } } < / td > < td > { { r . workUnit } } < / td > < td > { { formatNumber ( r . meteringNum , 2 ) } } < / td > < td > { { formatMoney ( r . meteringAmt ) } } < / td > < td > { { formatMoney ( r . meteringNotaxAmt ) } } < / td >
< td > { { r . no } } < / td > < td > { { r . fullName } } < / td > < td > { { r . workCode } } < / td > < td > { { r . name } } < / td > < td > { { r . workUnit } } < / td > < td > { { formatNumber ( r . meteringNum , 2 ) } } < / td > < td > { { formatMoney ( r . meteringAmt ) } } < / td > < td > { { formatMoney ( r . meteringNotaxAmt ) } } < / td > < td > { { r . totalNum } } < / td > < td > { { proportion ( r . meteringNum , r . totalNum ) } } < / td >
< / tr >
< / tbody >
< / table >
@@ -64,14 +135,17 @@
< / section >
< / div >
< / div >
< div v-if = "toastText" class="toast" :class="'toast-' + toastType" > {{ toastText }} < / div >
< / template >
< script setup >
import { computed , onMounted , reactive , ref } from "vue" ;
import { computed , onMounted , reactive , ref } from "vue" ;
import ModelPlaceholder from "../../components/model-placeholder/index.vue" ;
import { progressApi } from "../../service/api/progress.js" ;
import { progressApi } from "../../service/api/progress.js" ;
const structures = ref ( [ ] ) ;
const modelRef = ref ( null ) ;
const elementTree = ref ( [ ] ) ;
const progressData = ref ( [ ] ) ;
const sideCollapsed = ref ( false ) ;
@@ -80,6 +154,86 @@ const selectedStructureId = ref("");
const expanded = ref ( new Set ( ) ) ;
const loading = ref ( false ) ;
const showContextMenu = ref ( false ) ;
const contextMenuX = ref ( 0 ) ;
const contextMenuY = ref ( 0 ) ;
const contextMenuRow = ref ( null ) ;
const showAddModal = ref ( false ) ;
const componentList = ref ( [ ] ) ;
const partList = ref ( [ ] ) ;
const showViewModal = ref ( false ) ;
const viewPartList = ref ( [ ] ) ;
const viewComponentList = ref ( [ ] ) ;
const toastText = ref ( "" ) ;
const toastType = ref ( "success" ) ;
const normalLight = ref ( [ ] ) ;
const modalPosition = ref ( { left : "50%" , top : "50%" , transform : "translate(-50%, -50%)" } ) ;
let modalDragging = false ;
let modalStartX = 0 , modalStartY = 0 ;
let modalStartLeft = 0 , modalStartTop = 0 ;
function onModalPointerDown ( e ) {
if ( e . target . closest ( ".add-modal-close" ) || e . button !== 0 ) return ;
modalDragging = true ;
modalStartX = e . clientX ;
modalStartY = e . clientY ;
const pos = modalPosition . value ;
if ( pos . transform ) {
modalStartLeft = 50 ;
modalStartTop = 50 ;
}
window . addEventListener ( "pointermove" , onModalPointerMove ) ;
window . addEventListener ( "pointerup" , onModalPointerUp ) ;
}
function onModalPointerMove ( e ) {
if ( ! modalDragging ) return ;
const dx = e . clientX - modalStartX ;
const dy = e . clientY - modalStartY ;
modalPosition . value = {
left : ` calc(50% + ${ dx } px) ` ,
top : ` calc(50% + ${ dy } px) ` ,
transform : "translate(-50%, -50%)"
} ;
}
function onModalPointerUp ( ) {
modalDragging = false ;
window . removeEventListener ( "pointermove" , onModalPointerMove ) ;
window . removeEventListener ( "pointerup" , onModalPointerUp ) ;
}
const viewModalPosition = ref ( { left : "50%" , top : "50%" , transform : "translate(-50%, -50%)" } ) ;
let viewModalDragging = false ;
let viewModalStartX = 0 , viewModalStartY = 0 ;
function onViewModalPointerDown ( e ) {
if ( e . target . closest ( ".add-modal-close" ) || e . button !== 0 ) return ;
viewModalDragging = true ;
viewModalStartX = e . clientX ;
viewModalStartY = e . clientY ;
window . addEventListener ( "pointermove" , onViewModalPointerMove ) ;
window . addEventListener ( "pointerup" , onViewModalPointerUp ) ;
}
function onViewModalPointerMove ( e ) {
if ( ! viewModalDragging ) return ;
const dx = e . clientX - viewModalStartX ;
const dy = e . clientY - viewModalStartY ;
viewModalPosition . value = {
left : ` calc(50% + ${ dx } px) ` ,
top : ` calc(50% + ${ dy } px) ` ,
transform : "translate(-50%, -50%)"
} ;
}
function onViewModalPointerUp ( ) {
viewModalDragging = false ;
window . removeEventListener ( "pointermove" , onViewModalPointerMove ) ;
window . removeEventListener ( "pointerup" , onViewModalPointerUp ) ;
}
const timeStatMode = ref ( "cutoff" ) ;
const baselineDate = ref ( todayISODate ( ) ) ;
const baselineOverallPercent = ref ( 0 ) ;
@@ -93,7 +247,7 @@ const visibleTreeRows = computed(() => {
const walk = ( node , level ) => {
const leaf = node . isLeaf === true ;
const open = expanded . value . has ( node . id ) ;
rows . push ( { id : node . id , name : node . name , level , leaf , open , createDate : node . createDate } ) ;
rows . push ( { id : node . id , name : node . name , level , leaf , open , createDate : node . createDate , projectId : node . projectId } ) ;
if ( ! leaf && open && node . children ) {
node . children . forEach ( ( c ) => walk ( c , level + 1 ) ) ;
}
@@ -125,7 +279,7 @@ const overallPercent = computed(() => {
} ) ;
const progressRows = computed ( ( ) => {
if ( ! selectedStructureId . value ) return [ ] ;
if ( ! selectedStructureId . value && progressData . value . length === 0 ) return [ ] ;
return progressData . value . map ( ( item , index ) => ( {
no : index + 1 ,
fullName : item . fullName || "" ,
@@ -135,6 +289,7 @@ const progressRows = computed(() => {
meteringAmt : item . meteringAmt || 0 ,
meteringNotaxAmt : item . meteringNotaxAmt || 0 ,
meteringNum : item . meteringNum || 0 ,
totalNum : item . totalNum || '' ,
doneQty : item . doneQty || 0 ,
doneAmt : item . doneAmt || 0 ,
cumDoneQty : item . cumDoneQty || 0 ,
@@ -145,7 +300,6 @@ async function loadWbsTree(parentId) {
loading . value = true ;
try {
const data = await progressApi . getTree ( parentId ) ;
console . log ( 7777 , data )
if ( ! parentId ) {
elementTree . value = data . map ( ( item ) => ( {
... item ,
@@ -204,12 +358,27 @@ function findNode(nodes, id) {
onMounted ( ( ) => {
loadWbsTree ( ) ;
window . addEventListener ( "click" , closeContextMenu ) ;
console . log ( 6666 , progressApi . calculateProgress ( ) ) ;
} ) ;
function todayISODate ( ) {
return new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
}
function getStructureName ( id ) {
const findName = ( nodes ) => {
for ( const n of nodes ) {
if ( n . id === id ) return n . name ;
if ( n . children ) {
const found = findName ( n . children ) ;
if ( found ) return found ;
}
}
} ;
return findName ( elementTree . value ) || id ;
}
function daysBetweenISO ( a , b ) {
const da = new Date ( ` ${ a } T00:00:00 ` ) ;
const db = new Date ( ` ${ b } T00:00:00 ` ) ;
@@ -240,11 +409,29 @@ function formatNumber(value, digits = 0) {
function applyCutoffDate ( ) {
cutoffDate . value = cutoffDateDraft . value || cutoffDate . value || todayISODate ( ) ;
}
// 计算比例: count / allCount, 并显示百分比
function proportion ( count , allCount ) {
if ( count === "" || allCount === "" )
return ""
// 防止除以 0 报错
if ( ! allCount || allCount === 0 ) return ` 0 / 0 (0%) `
// 计算百分比( 保留2位小数)
let percent = ( ( count / allCount ) * 100 ) . toFixed ( 2 )
// 返回格式: 5 / 10 (50.00%)
return ` ${ count } / ${ allCount } ( ${ percent } %) `
}
function applyAllExclusive ( key , checked ) {
if ( key === "all" ) {
percentFilters . all = checked ;
if ( checked ) {
percentFilters . p0 = true ;
percentFilters . p0 _50 = true ;
percentFilters . p50 _100 = true ;
percentFilters . p100 = true ;
} else {
percentFilters . p0 = false ;
percentFilters . p0 _50 = false ;
percentFilters . p50 _100 = false ;
@@ -254,7 +441,7 @@ function applyAllExclusive(key, checked) {
}
percentFilters [ key ] = checked ;
if ( checked ) percentFilters . all = false ;
if ( ! percentFilters . p0 && ! percentFilters . p0 _50 && ! percentFilters . p50 _100 && ! percentFilters . p100 ) percentFilters . all = tru e;
if ( ! percentFilters . p0 && ! percentFilters . p0 _50 && ! percentFilters . p50 _100 && ! percentFilters . p100 ) percentFilters . all = fals e;
}
function togglePercentFilter ( key , checked ) { applyAllExclusive ( key , checked ) ; }
@@ -272,26 +459,184 @@ function toggleTreeExpand(row) {
}
}
function onTreeRowClick ( row ) {
console . log ( 8888 , row )
async function onTreeRowClick ( row ) {
modelRef . value ? . cancelLightModels ( ) ;
normalLight . value = [ ] ;
const data = await progressApi . getByPartId ( row . id ) ;
if ( data . length > 0 ) {
for ( const item of data ) {
const codeData = getParams ( item . codeData ) ;
const url = codeData . url ;
const id = Number ( codeData . id ) ;
// 1. 去找数组里有没有相同的 url
let existItem = normalLight . value . find ( ( i ) => i . url === url ) ;
if ( existItem ) {
// 2. 找到了 → 把id加进去
existItem . ids . push ( id ) ;
} else {
// 3. 没找到 → 新增
normalLight . value . push ( { url , ids : [ id ] } ) ;
}
}
modelRef . value ? . highlightModels ( normalLight . value ) ;
}
if ( row . leaf ) {
selectedStructureId . value = row . id ;
loadProgressData ( row . id , row . createDate ) ;
await loadProgressData( row . id , row . createDate ) ;
} else {
toggleTreeExpand ( row ) ;
progressData . value = [ ]
progressData . value = await progressApi . findActAmtByPositionId ( row . projectId , row . id , row . createDate ) ;
}
}
//获取"{url=https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e, id=353503}" return obj =>{url,id}
function getParams ( str ) {
let obj = { } ;
str . replace ( /{([\s\S]+)}/ , '$1' ) . split ( ', ' ) . forEach ( i => {
let [ k , v ] = i . split ( '=' ) ;
obj [ k ] = v ;
} ) ;
return obj ;
}
function onTreeRightClick ( e , row ) {
// if (!row.leaf) return;
contextMenuX . value = e . clientX ;
contextMenuY . value = e . clientY ;
contextMenuRow . value = row ;
showContextMenu . value = true ;
}
function onAddComponent ( ) {
partList . value . push ( { id : contextMenuRow . value . id , createDate : contextMenuRow . value . createDate } ) ;
componentList . value = [ ]
showAddModal . value = true ;
showContextMenu . value = false ;
}
async function onViewComponent ( ) {
const partId = contextMenuRow . value ? . id ;
if ( ! partId ) return ;
showContextMenu . value = false ;
try {
const data = await progressApi . getByPartId ( partId ) ;
// const grouped = processCodeData(data);
// console.log("处理后数据:", grouped);
viewPartList . value = [ partId ] ;
viewComponentList . value = data ;
showViewModal . value = true ;
} catch ( e ) {
console . error ( "获取构件列表失败:" , e ) ;
showToast ( "获取失败" , "error" ) ;
}
}
async function onDeleteComponent ( ) {
const partIds = viewPartList . value ;
const allIds = [ ] ;
for ( const item of viewComponentList . value ) {
if ( item . ids ) allIds . push ( ... item . ids ) ;
}
if ( ! partIds . length || ! allIds . length ) return ;
try {
const res = await progressApi . deleteBatch ( { partIds , codeIds : allIds } ) ;
if ( res ? . code === 200 ) {
showToast ( "删除成功!" , "success" ) ;
showViewModal . value = false ;
} else {
showToast ( res ? . message || "删除失败!" , "error" ) ;
}
} catch ( e ) {
console . error ( "删除失败:" , e ) ;
showToast ( "删除失败!" , "error" ) ;
}
}
function closeContextMenu ( ) {
showContextMenu . value = false ;
}
function onComponentBindEncoding ( data ) {
if ( showAddModal . value ) {
showAddModal . value = false ;
return ;
}
componentList . value = [ ] ;
partList . value = [ ] ;
showAddModal . value = true ;
for ( let component of data . components ) {
componentList . value . push ( { code : component . id , data : component } )
}
}
async function codeBindComponent ( data ) {
// if (!showAddModal.value) {
// return
// }
if ( ! data ? . components ? . length ) return ;
progressData . value = [ ]
const newData = [ ] ;
for ( const comp of data . components ) {
const res = await progressApi . getByCodeId ( comp . id ) ;
for ( const item of res ) {
if ( item . pjDayData ) {
if ( Array . isArray ( item . pjDayData ) ) {
newData . push ( ... item . pjDayData ) ;
} else {
newData . push ( item . pjDayData ) ;
}
}
}
componentList . value . push ( { code : comp . id || "" , data : comp } ) ;
}
progressData . value = [ ... progressData . value , ... newData ] ;
}
async function onSaveComponent ( ) {
const partIds = partList . value ;
const codeIds = componentList . value ;
// const codeIds = componentList.value.filter((c) => c.code).map((c) => c.code);
if ( ! partIds . length || ! codeIds . length ) return ;
try {
const res = await progressApi . saveBatch ( { partIds , codeIds } ) ;
if ( res . code === 200 ) {
showToast ( "保存成功!" , "success" ) ;
showAddModal . value = false ;
} else {
showToast ( res . message || "保存失败!" , "error" ) ;
}
} catch ( e ) {
console . error ( "保存失败:" , e ) ;
showToast ( "保存失败!" , "error" ) ;
}
}
function showToast ( text , type = "success" ) {
toastText . value = text ;
toastType . value = type ;
setTimeout ( ( ) => { toastText . value = "" ; } , 2000 ) ;
}
async function showAddModalHandle ( ) {
partList . value = [ ] ;
componentList . value = [ ] ;
showAddModal . value = false ;
}
async function showAddModalClose ( ) {
partList . value = [ ] ;
componentList . value = [ ] ;
showAddModal . value = false ;
}
async function loadProgressData ( positionId , time ) {
loading . value = true ;
try {
const data = await progressApi . getPjProgress ( {
projectId : "12e3c0eb186243869d94e214363ba083 " ,
projectId : "5e4bde33ec084f1a8673eb59b190dce7 " ,
positionIds : positionId ,
period : time ,
} ) ;
progressData . value = data ;
console . log ( "进度数据:" , data ) ;
} catch ( e ) {
console . error ( "加载进度明细失败:" , e ) ;
progressData . value = [ ] ;
@@ -346,7 +691,16 @@ async function loadProgressData(positionId,time) {
. tree - caret { border : 0 ; background : transparent ; color : rgba ( 203 , 230 , 236 , .88 ) ; cursor : pointer ; font - size : 14 px ; }
. tree - caret . is - leaf { cursor : default ; }
. tree - bullet { width : 10 px ; height : 10 px ; border - radius : 50 % ; background : rgba ( 83 , 214 , 206 , .85 ) ; }
. tree - text { font - size : 13 px ; font - weight : 800 ; color : rgba ( 222 , 238 , 244 , .9 ) ; }
. tree - text {
font - size : 13 px ;
font - weight : 800 ;
color : rgba ( 222 , 238 , 244 , .9 ) ;
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
max - width : 180 px ;
display : inline - block ;
}
. bottompanel { position : absolute ; left : 370 px ; right : 16 px ; bottom : 100 px ; height : 390 px ; border - radius : 18 px ; border : 1 px solid rgba ( 83 , 214 , 206 , .24 ) ; background : radial - gradient ( 420 px 180 px at 14 % 0 % , rgba ( 83 , 214 , 206 , .2 ) , transparent 66 % ) padding - box , radial - gradient ( 520 px 220 px at 82 % 0 % , rgba ( 40 , 156 , 228 , .14 ) , transparent 70 % ) padding - box , linear - gradient ( 180 deg , rgba ( 18 , 33 , 40 , .94 ) , rgba ( 14 , 24 , 31 , .9 ) ) padding - box ; box - shadow : 0 24 px 70 px rgba ( 0 , 0 , 0 , .36 ) , 0 0 0 1 px rgba ( 83 , 214 , 206 , .14 ) inset ; overflow : hidden ; z - index : 26 ; }
. bottompanel - header { padding : 12 px 52 px 12 px 12 px ; border - bottom : 1 px solid rgba ( 83 , 214 , 206 , .28 ) ; background : radial - gradient ( 380 px 120 px at 18 % 0 % , rgba ( 83 , 214 , 206 , .28 ) , transparent 70 % ) , linear - gradient ( 180 deg , rgba ( 25 , 137 , 124 , .86 ) , rgba ( 16 , 66 , 82 , .64 ) ) ; }
@@ -370,4 +724,251 @@ async function loadProgressData(positionId,time) {
. sidepanel { width : 280 px ; }
. bottompanel { left : 330 px ; }
}
. context - menu {
position : fixed ;
z - index : 9999 ;
border - radius : 10 px ;
border : 1 px solid rgba ( 83 , 214 , 206 , 0.25 ) ;
background : linear - gradient ( 180 deg , rgba ( 20 , 31 , 37 , 0.95 ) , rgba ( 15 , 23 , 29 , 0.92 ) ) ;
box - shadow : 0 12 px 32 px rgba ( 0 , 0 , 0 , 0.4 ) ;
overflow : hidden ;
}
. context - menu - item {
padding : 10 px 16 px ;
font - size : 13 px ;
font - weight : 700 ;
color : rgba ( 207 , 247 , 242 , 0.9 ) ;
cursor : pointer ;
}
. context - menu - item : hover {
background : rgba ( 83 , 214 , 206 , 0.18 ) ;
}
. add - modal {
position : fixed ;
width : min ( 520 px , 86 vw ) ;
border - radius : 16 px ;
border : 1 px solid rgba ( 83 , 214 , 206 , 0.25 ) ;
background : linear - gradient ( 180 deg , rgba ( 20 , 31 , 37 , 0.95 ) , rgba ( 15 , 23 , 29 , 0.92 ) ) ;
box - shadow : 0 32 px 80 px rgba ( 0 , 0 , 0 , 0.45 ) ;
z - index : 100 ;
}
. add - modal - header {
display : flex ;
align - items : center ;
justify - content : space - between ;
padding : 16 px 18 px ;
border - bottom : 1 px solid rgba ( 83 , 214 , 206 , 0.16 ) ;
background : radial - gradient ( 360 px 100 px at 10 % 0 % , rgba ( 83 , 214 , 206 , 0.18 ) , transparent 68 % ) ,
linear - gradient ( 180 deg , rgba ( 28 , 134 , 122 , 0.45 ) , rgba ( 15 , 60 , 74 , 0.32 ) ) ;
}
. add - modal - title {
font - size : 15 px ;
font - weight : 800 ;
color : rgba ( 207 , 247 , 242 , 0.96 ) ;
}
. add - modal - close {
width : 28 px ;
height : 28 px ;
border - radius : 8 px ;
border : 1 px solid rgba ( 255 , 255 , 255 , 0.12 ) ;
background : rgba ( 255 , 255 , 255 , 0.08 ) ;
color : rgba ( 203 , 230 , 236 , 0.75 ) ;
font - size : 14 px ;
cursor : pointer ;
display : grid ;
place - items : center ;
}
. add - modal - close : hover {
background : rgba ( 255 , 255 , 255 , 0.14 ) ;
color : rgba ( 236 , 246 , 250 , 0.95 ) ;
}
. add - modal - body {
padding : 18 px ;
min - height : 300 px ;
max - height : 400 px ;
overflow : auto ;
}
. add - modal - split {
display : grid ;
grid - template - columns : 1 fr 1 fr ;
gap : 20 px ;
height : 100 % ;
}
. add - modal - left ,
. add - modal - right {
display : flex ;
flex - direction : column ;
gap : 10 px ;
}
. add - modal - section - title {
font - size : 13 px ;
font - weight : 800 ;
color : rgba ( 207 , 247 , 242 , 0.9 ) ;
}
. add - modal - list {
display : flex ;
flex - direction : column ;
gap : 8 px ;
overflow - y : auto ;
flex : 1 ;
}
. add - modal - tag {
display : flex ;
align - items : center ;
justify - content : space - between ;
padding : 8 px 12 px ;
border - radius : 8 px ;
border : 1 px solid rgba ( 83 , 214 , 206 , 0.25 ) ;
background : rgba ( 0 , 0 , 0 , 0.2 ) ;
color : rgba ( 207 , 247 , 242 , 0.9 ) ;
font - size : 13 px ;
}
. add - modal - tag - delete {
width : 22 px ;
height : 22 px ;
border - radius : 6 px ;
border : 1 px solid rgba ( 217 , 54 , 62 , 0.4 ) ;
background : rgba ( 217 , 54 , 62 , 0.1 ) ;
color : rgba ( 217 , 54 , 62 , 0.8 ) ;
font - size : 12 px ;
cursor : pointer ;
}
. add - form - list {
display : flex ;
flex - direction : column ;
gap : 10 px ;
}
. add - form - item {
display : flex ;
align - items : center ;
gap : 10 px ;
}
. add - form - input {
flex : 1 ;
height : 36 px ;
border - radius : 8 px ;
border : 1 px solid rgba ( 83 , 214 , 206 , 0.25 ) ;
background : rgba ( 0 , 0 , 0 , 0.2 ) ;
color : rgba ( 255 , 255 , 255 , 0.9 ) ;
padding : 0 12 px ;
font - size : 13 px ;
}
. add - form - input : focus {
outline : none ;
border - color : rgba ( 83 , 214 , 206 , 0.5 ) ;
}
. add - form - delete {
width : 32 px ;
height : 36 px ;
border - radius : 8 px ;
border : 1 px solid rgba ( 217 , 54 , 62 , 0.4 ) ;
background : rgba ( 217 , 54 , 62 , 0.15 ) ;
color : rgba ( 217 , 54 , 62 , 0.9 ) ;
font - size : 14 px ;
cursor : pointer ;
}
. add - form - delete : hover {
background : rgba ( 217 , 54 , 62 , 0.25 ) ;
}
. add - form - add - btn {
margin - top : 12 px ;
height : 36 px ;
border - radius : 8 px ;
border : 1 px solid rgba ( 83 , 214 , 206 , 0.3 ) ;
background : rgba ( 83 , 214 , 206 , 0.15 ) ;
color : rgba ( 83 , 214 , 206 , 0.95 ) ;
font - size : 13 px ;
font - weight : 700 ;
padding : 0 16 px ;
cursor : pointer ;
}
. add - form - add - btn : hover {
background : rgba ( 83 , 214 , 206 , 0.25 ) ;
}
. add - modal - footer {
display : flex ;
justify - content : flex - end ;
gap : 10 px ;
padding - top : 16 px ;
border - top : 1 px solid rgba ( 83 , 214 , 206 , 0.16 ) ;
margin - top : 16 px ;
}
. add - modal - btn {
padding : 8 px 16 px ;
border - radius : 8 px ;
font - size : 13 px ;
font - weight : 700 ;
cursor : pointer ;
border : 1 px solid transparent ;
}
. add - modal - btn - secondary {
background : rgba ( 255 , 255 , 255 , 0.1 ) ;
color : rgba ( 255 , 255 , 255 , 0.85 ) ;
border - color : rgba ( 255 , 255 , 255 , 0.15 ) ;
}
. add - modal - btn - secondary : hover {
background : rgba ( 255 , 255 , 255 , 0.15 ) ;
}
. add - modal - btn - primary {
background : rgba ( 83 , 214 , 206 , 0.25 ) ;
color : rgba ( 83 , 214 , 206 , 0.95 ) ;
border - color : rgba ( 83 , 214 , 206 , 0.3 ) ;
}
. add - modal - btn - primary : hover {
background : rgba ( 83 , 214 , 206 , 0.35 ) ;
}
. toast {
position : fixed ;
left : 50 % ;
top : 16 px ;
transform : translateX ( - 50 % ) ;
z - index : 9999 ;
padding : 10 px 20 px ;
border - radius : 10 px ;
font - size : 14 px ;
font - weight : 700 ;
box - shadow : 0 10 px 30 px rgba ( 0 , 0 , 0 , 0.35 ) ;
}
. toast - success {
background : rgba ( 83 , 214 , 206 , 0.25 ) ;
border : 1 px solid rgba ( 83 , 214 , 206 , 0.4 ) ;
color : rgba ( 83 , 214 , 206 , 0.95 ) ;
}
. toast - error {
background : rgba ( 217 , 54 , 62 , 0.25 ) ;
border : 1 px solid rgba ( 217 , 54 , 62 , 0.4 ) ;
color : rgba ( 255 , 255 , 255 , 0.95 ) ;
}
< / style >