Compare commits

...

4 Commits

  1. 9
      controller/GraphStyleController.py
  2. 46
      service/GraphStyleService.py
  3. 87
      service/OperationService.py
  4. 7
      vue/src/api/style.js
  5. 225
      vue/src/system/GraphDemo.vue
  6. 179
      vue/src/system/GraphStyle.vue

9
controller/GraphStyleController.py

@ -50,6 +50,15 @@ async def get_grouped_style_list(request):
return create_response(200, {"code": 200, "data": data, "msg": "查询成功"})
except Exception as e:
return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"})
@app.get("/api/graph/style/active")
async def get_active_style(request):
"""获取【分组嵌套】格式的配置列表(用于右侧折叠面板)"""
try:
# 调用 Service 的嵌套聚合方法,现在内部已包含 is_active/is_default 逻辑
data = GraphStyleService.get_active_configs()
return create_response(200, {"code": 200, "data": data, "msg": "查询成功"})
except Exception as e:
return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"})
@app.post("/api/graph/style/group/apply")

46
service/GraphStyleService.py

@ -87,6 +87,52 @@ class GraphStyleService:
return result
@staticmethod
def get_active_configs() -> list:
"""
获取 is_active = 1 的方案组及其配置项
返回长度为 0 1 的列表因为通常只有一个激活组
"""
# 1. 查询 is_active = 1 的组(按 id 排序,取第一个以防有多个)
group_sql = """
SELECT id, group_name, is_active, is_default
FROM graph_style_groups
WHERE is_active = 1
ORDER BY id ASC
LIMIT 1
"""
groups = mysql_client.execute_query(group_sql) or []
if not groups:
return [] # 没有激活的组
group = groups[0]
group_id = group['id']
# 2. 查询该组下的所有配置
configs_sql = """
SELECT id, group_id, canvas_name, current_label, config_json, create_time
FROM graph_configs
WHERE group_id = %s
"""
configs = mysql_client.execute_query(configs_sql, (group_id,)) or []
# 3. 处理配置项
for conf in configs:
if conf.get('config_json'):
try:
conf['styles'] = json.loads(conf['config_json'])
except (TypeError, ValueError):
conf['styles'] = {}
del conf['config_json']
if conf.get('create_time') and not isinstance(conf['create_time'], str):
conf['create_time'] = conf['create_time'].strftime('%Y-%m-%d %H:%M:%S')
# 4. 组装结果
group['configs'] = configs
return [group] # 返回单元素列表,保持接口兼容性
@staticmethod
def apply_group_all(group_id: int) -> bool:
"""
核心新增应用全案

87
service/OperationService.py

@ -389,4 +389,89 @@ class OperationService:
result = self.db.execute_write_and_return(cypher, {"id": rel_id})
return {"success": True, "msg": "删除成功"} if result else {"success": False, "msg": "关系不存在"}
except Exception as e:
return {"success": False, "msg": f"删除失败: {str(e)}"}
return {"success": False, "msg": f"删除失败: {str(e)}"}
# --- 7. 导出功能 ---
def export_nodes_to_json(self, label=None, name=None):
"""
按照条件导出节点确保包含 identity, elementId, labels, properties 等所有原始字段
"""
try:
conditions = []
params = {}
# 构建过滤条件(复用查询逻辑,但去掉分页)
if name:
params["name"] = unquote(str(name)).strip()
conditions.append("n.name CONTAINS $name")
lb_clause = ""
if label and label not in ["全部", ""]:
# 为了保证原生对象的完整性,这里直接 MATCH 标签
lb_clause = f":`{label}`"
where_clause = "WHERE " + " AND ".join(conditions) if conditions else ""
# 注意:这里 RETURN n,返回的是整个节点对象
cypher = f"MATCH (n{lb_clause}) {where_clause} RETURN n"
raw_data = self.db.execute_read(cypher, params)
export_items = []
for row in raw_data:
node = row['n']
# 核心逻辑:提取 Neo4j 节点对象的所有原生属性
node_data = {
"identity": node.id, # 对应你截图中的 identity (旧版 ID)
"elementId": node.element_id, # 对应你截图中的 elementId (新版 ID)
"labels": list(node.labels),
"properties": dict(node.items())
}
export_items.append(node_data)
return {"success": True, "data": export_items}
except Exception as e:
traceback.print_exc()
return {"success": False, "msg": f"导出节点失败: {str(e)}"}
def export_relationships_to_json(self, source=None, target=None, rel_type=None):
"""
按照条件导出关系确保包含起始/结束节点信息及完整属性
"""
try:
conditions = []
params = {}
if source:
params["source"] = unquote(str(source)).strip()
conditions.append("a.name CONTAINS $source")
if target:
params["target"] = unquote(str(target)).strip()
conditions.append("b.name CONTAINS $target")
if rel_type and rel_type not in ["全部", ""]:
conditions.append(f"type(r) = $rel_type")
params["rel_type"] = rel_type
where_clause = "WHERE " + " AND ".join(conditions) if conditions else ""
# 返回关系对象 r 以及起止节点的 elementId 以便追溯
cypher = f"MATCH (a)-[r]->(b) {where_clause} RETURN r, elementId(a) as startNode, elementId(b) as endNode"
raw_data = self.db.execute_read(cypher, params)
export_items = []
for row in raw_data:
rel = row['r']
rel_data = {
"identity": rel.id,
"elementId": rel.element_id,
"type": rel.type,
"startNodeElementId": row['startNode'],
"endNodeElementId": row['endNode'],
"properties": dict(rel.items())
}
export_items.append(rel_data)
return {"success": True, "data": export_items}
except Exception as e:
traceback.print_exc()
return {"success": False, "msg": f"导出关系失败: {str(e)}"}

7
vue/src/api/style.js

@ -72,6 +72,13 @@ export function getGraphStyleGroups() {
});
}
export function getGraphStyleActive() {
return request({
url: '/api/graph/style/active',
method: 'get'
});
}
/**
* 获取所有图谱样式配置列表
* 保留此接口用于兼容旧版逻辑或后台管理

225
vue/src/system/GraphDemo.vue

@ -157,6 +157,7 @@ import Menu from "@/components/Menu.vue";
import {a} from "vue-router/dist/devtools-EWN81iOl.mjs";
import Fuse from 'fuse.js';
import {getGraphStyleActive} from "@/api/style";
export default {
name: 'Display',
components: {Menu},
@ -232,7 +233,16 @@ export default {
{ value: 'Disease', label: '疾病' },
{ value: 'Drug', label: '药品' },
{ value: 'Check', label: '检查' }
]
],
configs:[],
parsedStyles:{},
enToZhLabelMap: {
Disease: '疾病',
Drug: '药品',
Check: '检查',
Symptom: '症状',
Other: '其他'
}
}
},
computed: {
@ -307,41 +317,66 @@ export default {
this.treeData=this.diseaseICD10Tree
await this.$nextTick();
try {
await this.getDefault()
const response = await getTestGraphData(); // Promise
const updatedNodes = response.nodes.map(node => ({
...node,
type: this.nodeShape,
style:{
size: this.nodeSize,
fill: this.nodeFill,
stroke: this.nodeStroke,
lineWidth: this.nodeLineWidth,
label: this.nodeShowLabel,
labelFontSize: this.nodeFontSize,
labelFontFamily: this.nodeFontFamily,
labelFill: this.nodeFontColor,
}
}))
const updatedEdges = response.edges.map(edge => ({
...edge,
id: edge.data.relationship.id,
type: this.edgeType,
style: {
endArrow: this.edgeEndArrow,
stroke: this.edgeStroke,
lineWidth: this.edgeLineWidth,
label: this.edgeShowLabel,
labelFontSize: this.edgeFontSize,
labelFontFamily: this.edgeFontFamily,
labelFill: this.edgeFontColor,
},
}))
const updatedData = {
// === 1. nodeId label ===
const nodeIdToEnLabel = {};
response.nodes.forEach(node => {
nodeIdToEnLabel[node.id] = node.data.label; // e.g. "Disease"
});
// === 2. label ===
const updatedNodes = response.nodes.map(node => {
const enLabel = node.data.label;
const zhLabel = this.enToZhLabelMap[enLabel] || '其他'; // 退
const styleConf = this.parsedStyles[zhLabel] || {};
return {
...node,
type: styleConf.nodeShape || this.nodeShape,
style: {
size: styleConf.nodeSize || this.nodeSize,
fill: styleConf.nodeFill || this.nodeFill,
stroke: styleConf.nodeStroke || this.nodeStroke,
lineWidth: styleConf.nodeLineWidth || this.nodeLineWidth,
label: styleConf.nodeShowLabel !== undefined ? styleConf.nodeShowLabel : true,
labelFontSize: styleConf.nodeFontSize || this.nodeFontSize,
labelFontFamily: styleConf.nodeFontFamily || this.nodeFontFamily,
labelFill: styleConf.nodeFontColor || this.nodeFontColor
}
};
});
// === 3. source label ===
const updatedEdges = response.edges.map(edge => {
console.log(edge)
const sourceEnLabel = nodeIdToEnLabel[edge.source]; // e.g. "Disease"
const sourceZhLabel = this.enToZhLabelMap[sourceEnLabel] || '其他';
const styleConf = this.parsedStyles[sourceZhLabel] || {};
return {
...edge,
id: edge.data?.relationship?.id || edge.id,
type: styleConf.edgeType ||this.edgeType,
style: {
endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true,
stroke: styleConf.edgeStroke || this.edgeStroke,
lineWidth: styleConf.edgeLineWidth || this.edgeLineWidth,
label: styleConf.edgeShowLabel !== undefined ? styleConf.edgeShowLabel : false,
labelFontSize: styleConf.edgeFontSize || this.edgeFontSize,
labelFontFamily: styleConf.edgeFontFamily || this.edgeFontFamily,
labelFill: styleConf.edgeFontColor || this.edgeFontColor
}
};
});
// === 4. ===
let updatedData = {
nodes: updatedNodes,
edges: updatedEdges
}
};
console.log(updatedData)
this.defaultData = updatedData
setTimeout(() => {
setTimeout(() => {
this.initGraph();
this.buildCategoryIndex();
window.addEventListener('resize', this.handleResize);
@ -349,6 +384,50 @@ export default {
} catch (error) {
console.error('加载图谱数据失败:', error);
}
// try {
// await this.getDefault()
// const response = await getTestGraphData(); // Promise
// const updatedNodes = response.nodes.map(node => ({
// ...node,
// type: this.nodeShape,
// style:{
// size: this.nodeSize,
// fill: this.nodeFill,
// stroke: this.nodeStroke,
// lineWidth: this.nodeLineWidth,
// label: this.nodeShowLabel,
// labelFontSize: this.nodeFontSize,
// labelFontFamily: this.nodeFontFamily,
// labelFill: this.nodeFontColor,
// }
// }))
// const updatedEdges = response.edges.map(edge => ({
// ...edge,
// id: edge.data.relationship.id,
// type: this.edgeType,
// style: {
// endArrow: this.edgeEndArrow,
// stroke: this.edgeStroke,
// lineWidth: this.edgeLineWidth,
// label: this.edgeShowLabel,
// labelFontSize: this.edgeFontSize,
// labelFontFamily: this.edgeFontFamily,
// labelFill: this.edgeFontColor,
// },
// }))
// const updatedData = {
// nodes: updatedNodes,
// edges: updatedEdges
// }
// this.defaultData = updatedData
// setTimeout(() => {
// this.initGraph();
// this.buildCategoryIndex();
// window.addEventListener('resize', this.handleResize);
// }, 1000);
// } catch (error) {
// console.error(':', error);
// }
},
@ -384,6 +463,36 @@ export default {
edgeFontFamily: 'updateAllEdges',
},
methods: {
safeParseStyles(stylesStr) {
try {
return JSON.parse(stylesStr || '{}');
} catch (e) {
console.warn('Failed to parse styles:', stylesStr);
return {};
}
},
async getDefault(){
const response = await getGraphStyleActive();
const data = response.data;
if (!Array.isArray(data) || data.length === 0) {
this.configs = [];
this.parsedStyles = {};
return;
}
// is_active=1
const activeGroup = data[0];
this.configs = Array.isArray(activeGroup.configs) ? activeGroup.configs : [];
// label -> style
const styleMap = {};
this.configs.forEach(config => {
const label = config.current_label;
styleMap[label] = this.safeParseStyles(config.styles);
});
this.parsedStyles = styleMap;
console.log(this.parsedStyles)
},
//
toggleDropdown() {
this.isDropdownOpen = !this.isDropdownOpen;
@ -871,22 +980,22 @@ export default {
node: {
style: {
fill: (d) => {
const label = d.data?.label;
if (label === 'Disease') return '#EF4444'; //
if (label === 'Drug') return '#91cc75'; // 绿
if (label === 'Symptom') return '#fac858'; //
if (label === 'Check') return '#336eee'; //
return '#59d1d4'; //
},
stroke: (d) => {
const label = d.data?.label;
if (label === 'Disease') return '#B91C1C';
if (label === 'Drug') return '#047857';
if (label === 'Check') return '#1D4ED8'; //
if (label === 'Symptom') return '#B45309';
return '#40999b';
},
// fill: (d) => {
// const label = d.data?.label;
// if (label === 'Disease') return '#EF4444'; //
// if (label === 'Drug') return '#91cc75'; // 绿
// if (label === 'Symptom') return '#fac858'; //
// if (label === 'Check') return '#336eee'; //
// return '#59d1d4'; //
// },
// stroke: (d) => {
// const label = d.data?.label;
// if (label === 'Disease') return '#B91C1C';
// if (label === 'Drug') return '#047857';
// if (label === 'Check') return '#1D4ED8'; //
// if (label === 'Symptom') return '#B45309';
// return '#40999b';
// },
labelText: (d) => d.data.name,
labelPlacement: 'center',
labelWordWrap: true,
@ -921,16 +1030,16 @@ export default {
edge: {
style: {
labelText: (d) => d.data.relationship.properties.label,
stroke: (d) => {
// target label
const targetLabel = this._nodeLabelMap.get(d.source); // d.target ID
// target
if (targetLabel === 'Disease') return 'rgba(239,68,68,0.5)';
if (targetLabel === 'Drug') return 'rgba(145,204,117,0.5)';
if (targetLabel === 'Symptom') return 'rgba(250,200,88,0.5)';
if (targetLabel === 'Check') return 'rgba(51,110,238,0.5)'; //
return 'rgba(89,209,212,0.5)'; // default
},
// stroke: (d) => {
// // target label
// const targetLabel = this._nodeLabelMap.get(d.source); // d.target ID
// // target
// if (targetLabel === 'Disease') return 'rgba(239,68,68,0.5)';
// if (targetLabel === 'Drug') return 'rgba(145,204,117,0.5)';
// if (targetLabel === 'Symptom') return 'rgba(250,200,88,0.5)';
// if (targetLabel === 'Check') return 'rgba(51,110,238,0.5)'; //
// return 'rgba(89,209,212,0.5)'; // default
// },
// labelFill: (d) => {
// // target label
// const targetLabel = this._nodeLabelMap.get(d.target); // d.target ID

179
vue/src/system/GraphStyle.vue

@ -32,7 +32,7 @@
</div>
<div class="form-group">
<label>字体名称:</label>
<label>字体名称</label>
<select v-model="nodeFontFamily">
<option value="Microsoft YaHei, sans-serif">微软雅黑</option>
<option value="SimSun, serif">宋体SimSun</option>
@ -42,7 +42,7 @@
</div>
<div class="form-group">
<label>字体大小:</label>
<label>字体大小</label>
<div class="slider-wrapper">
<input v-model.number="nodeFontSize" type="range" min="10" max="24" step="1" class="theme-slider"/>
<span class="val-text-black">{{ nodeFontSize }}px</span>
@ -50,14 +50,14 @@
</div>
<div class="color-picker-item">
<label>字体颜色:</label>
<label>字体颜色</label>
<div class="color-picker-border">
<input v-model="nodeFontColor" type="color" class="square-picker"/>
</div>
</div>
<div class="form-group">
<label>图形:</label>
<label>图形</label>
<select v-model="nodeShape">
<option value="circle">圆形</option>
<option value="diamond">菱形</option>
@ -67,7 +67,7 @@
</div>
<div class="form-group">
<label>尺寸:</label>
<label>尺寸</label>
<input
:value="nodeSize"
type="number"
@ -78,21 +78,21 @@
</div>
<div class="color-picker-item">
<label>填充颜色:</label>
<label>填充颜色</label>
<div class="color-picker-border">
<input v-model="nodeFill" type="color" class="square-picker"/>
</div>
</div>
<div class="color-picker-item">
<label>边框颜色:</label>
<label>边框颜色</label>
<div class="color-picker-border">
<input v-model="nodeStroke" type="color" class="square-picker"/>
</div>
</div>
<div class="form-group">
<label>边框尺寸:</label>
<label>边框尺寸</label>
<input
:value="nodeLineWidth"
type="number"
@ -116,7 +116,7 @@
</div>
<div class="form-group">
<label>字体名称:</label>
<label>字体名称</label>
<select v-model="edgeFontFamily">
<option value="Microsoft YaHei, sans-serif">微软雅黑</option>
<option value="SimSun, serif">宋体</option>
@ -126,7 +126,7 @@
</div>
<div class="form-group">
<label>字体大小:</label>
<label>字体大小</label>
<div class="slider-wrapper">
<input v-model.number="edgeFontSize" type="range" min="8" max="16" step="1" class="theme-slider"/>
<span class="val-text-black">{{ edgeFontSize }}px</span>
@ -134,14 +134,14 @@
</div>
<div class="color-picker-item">
<label>字体颜色:</label>
<label>字体颜色</label>
<div class="color-picker-border">
<input v-model="edgeFontColor" type="color" class="square-picker"/>
</div>
</div>
<div class="form-group">
<label>连边类型:</label>
<label>连边类型</label>
<select v-model="edgeType">
<option value="line">直线</option>
<option value="polyline">折线</option>
@ -151,7 +151,7 @@
</div>
<div class="form-group">
<label>线粗细:</label>
<label>线粗细</label>
<input
:value="edgeLineWidth"
type="number"
@ -162,7 +162,7 @@
</div>
<div class="color-picker-item">
<label>线条颜色:</label>
<label>线条颜色</label>
<div class="color-picker-border">
<input v-model="edgeStroke" type="color" class="square-picker"/>
</div>
@ -304,7 +304,7 @@ import {
deleteGraphStyle,
batchDeleteGraphStyle,
deleteGraphStyleGroup,
applyGraphStyleGroup
applyGraphStyleGroup, getGraphStyleActive
} from '@/api/style';
import {ElMessageBox, ElMessage} from 'element-plus';
import {markRaw} from 'vue';
@ -684,19 +684,15 @@ export default {
this.saveDialogVisible = true;
},
async confirmSave() {
if (!this.saveForm.group_name || !this
.saveForm.group_name.trim()) {
return this.$message.warning("请选择或输入方案名称"
);
if (!this.saveForm.group_name || !this.saveForm.group_name.trim()) {
return ElMessage.warning("请选择或输入方案名称");
}
if (!this.saveForm.canvas_name || !this
.saveForm.canvas_name.trim()) {
return this.$message.warning("请输入配置名称"
);
if (!this.saveForm.canvas_name || !this.saveForm.canvas_name.trim()) {
return ElMessage.warning("请输入配置名称");
}
const payload = {
canvas_name: this.saveForm.canvas_name,
group_name: this.saveForm.group_name,
canvas_name: this.saveForm.canvas_name.trim(),
group_name: this.saveForm.group_name.trim(),
current_label: this.activeTags,
styles: {...this.tagStyles[tagToLabelMap[this.activeTags]]}
};
@ -704,6 +700,8 @@ export default {
if (res.code === 200) {
ElMessage.success("保存成功");
this.saveDialogVisible = false;
this.saveForm.canvas_name = '';
this.saveForm.group_name = '';
this.resetAllTagsToDefault();
this.fetchConfigs();
}
@ -725,51 +723,114 @@ export default {
this.updateAllElements();
ElMessage.info(`已重置【${this.activeTags}】样式`);
},
// --- ---
async deleteSingleConfig(id) {
if (this.usingConfigIds.includes(id)) {
return ElMessage.error("该配置正在应用中,请取消应用或切换方案后再删除");
}
try {
await ElMessageBox.confirm('确定删除此配置吗?', '提示');
const res = await deleteGraphStyle(id);
if (res.code === 200) {
this.usingConfigIds = this.usingConfigIds.filter(cid => cid !== id);
ElMessage.success("删除成功");
this.fetchConfigs();
this.updateAllElements();
}
} catch (err) {}
},
// --- ---
async deleteGroup(groupId) {
const group = this.styleGroups.find(g => g.id === groupId);
if (!group) return;
// 1.
const isGroupUsing = group.configs.some(c => this.usingConfigIds.includes(c.id));
if (isGroupUsing || group.is_active) {
return ElMessage.error("该方案中包含正在应用的配置,无法删除");
}
// 2.
if (this.styleGroups.length <= 1) {
return ElMessage.error("系统至少需保留一个方案,无法全部删除");
}
try {
await ElMessageBox.confirm('确定删除整个方案吗?', '提示');
const res = await deleteGraphStyleGroup(groupId);
if (res.code === 200) {
ElMessage.success("方案已删除");
this.fetchConfigs();
this.updateAllElements();
}
} catch (err) {}
},
// --- ---
async handleUnifiedBatchDelete() {
// 1.
if (this.checkedConfigIds.length === 0 && this.checkedGroupIds.length === 0) {
return ElMessage.warning("请先勾选要删除的项目");
}
// 2.
const isAnyCheckedConfigUsing = this.checkedConfigIds.some(id => this.usingConfigIds.includes(id));
const isAnyCheckedGroupUsing = this.styleGroups
.filter(g => this.checkedGroupIds.includes(g.id))
.some(g => g.configs.some(c => this.usingConfigIds.includes(c.id)));
if (isAnyCheckedConfigUsing || isAnyCheckedGroupUsing) {
return ElMessageBox.alert(
'选中的项目中包含“正在应用”的配置,请先取消应用后再执行删除操作。',
'无法执行删除',
{ type: 'error', confirmButtonText: '我知道了' }
);
}
// 3. ()
if (this.checkedGroupIds.length >= this.styleGroups.length && this.styleGroups.length > 0) {
return ElMessage.error("系统至少需要保留一个方案,请勿全部勾选删除");
}
try {
await ElMessageBox.confirm(
'确定执行批量删除吗?',
'批量删除',
'确定执行批量删除吗?此操作不可恢复。',
'批量删除确认',
{
confirmButtonText: '确定',
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
}
);
for (const gid of this.checkedGroupIds) await deleteGraphStyleGroup(gid);
if (this.checkedConfigIds.length > 0) await batchDeleteGraphStyle({ids: this.checkedConfigIds});
//
if (this.checkedGroupIds.length > 0) {
for (const gid of this.checkedGroupIds) {
await deleteGraphStyleGroup(gid);
}
}
if (this.checkedConfigIds.length > 0) {
await batchDeleteGraphStyle({ids: this.checkedConfigIds});
}
ElMessage.success("批量删除成功");
this.clearSelection();
this.fetchConfigs();
this.updateAllElements();
} catch (e) {}
} catch (e) {
console.log("用户取消或删除失败", e);
}
},
clearSelection() {
this.checkedConfigIds = [];
this.checkedGroupIds = [];
},
handleResize() {
if (this._graph && this.$refs.graphContainer) this._graph.setSize(this.$refs.graphContainer.clientWidth, this.$refs.graphContainer.clientHeight);
if (this._graph && this.$refs.graphContainer) {
this._graph.setSize(this.$refs.graphContainer.clientWidth, this.$refs.graphContainer.clientHeight);
}
}
}
}
@ -821,7 +882,7 @@ export default {
}
.control-panel {
width: 260px;
width: 250px;
background: #ffffff;
box-shadow: 4px 0 12px rgba(0, 0, 0, 0.08);
padding: 10px;
@ -833,7 +894,7 @@ export default {
}
.config-list-panel {
width: 320px;
width: 250px;
background: #ffffff;
box-shadow: -4px 0 12px rgba(0, 0, 0, 0.08);
padding: 18px;
@ -845,14 +906,15 @@ export default {
}
.panel-header-container {
margin-bottom: 15px;
margin-bottom: 10px;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding-bottom: 10px;
border-bottom: 1px solid #E2E6F3;
}
/*.header-line {
@ -878,7 +940,7 @@ export default {
.tag-pill {
flex-shrink: 0;
padding: 0 10px;
padding: 1px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
@ -897,14 +959,11 @@ export default {
background-color: #4a68db;
}
.section {
margin-bottom: 25px;
}
.section-title {
display: flex;
align-items: center;
font-size: 16px;
font-size: 14px;
font-weight: bold;
color: #334155;
margin-bottom: 12px;
@ -918,7 +977,7 @@ export default {
position: absolute;
left: 0;
width: 4px;
height: 16px;
height: 15px;
background-color: #1559f3;
border-radius: 2px;
}
@ -927,15 +986,16 @@ export default {
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 14px;
margin-bottom: 12px;
font-size: 13px;
margin-bottom: 10px;
color: #475569;
}
.checkbox-label {
width: 150px;
width: 80px;
flex-shrink: 0;
text-align: left;
margin-right: 8px;
}
.theme-checkbox {
@ -948,13 +1008,15 @@ export default {
.form-group, .color-picker-item {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
margin-bottom: 10px;
font-size: 13px;
}
.form-group label, .color-picker-item label {
width: 80px;
flex-shrink: 0;
text-align: right;
margin-right: 8px;
}
.form-group select, .form-group input[type="number"] {
@ -962,6 +1024,7 @@ export default {
padding: 5px;
border: 1px solid #e2e8f0;
border-radius: 4px;
width: 100px;
}
.slider-wrapper {
@ -1014,9 +1077,6 @@ export default {
}
.button-footer {
display: flex;
gap: 10px;
padding-top: 10px;
}
.btn-confirm-save {
@ -1024,10 +1084,12 @@ export default {
color: #fff;
border: none;
flex: 1;
padding: 10px;
padding: 5px 14px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
width: 100px;
font-size: 12px;
margin-right: 15px;
}
.btn-reset-style {
@ -1035,9 +1097,11 @@ export default {
color: #1559f3;
border: 1px solid #1559f3;
flex: 1;
padding: 10px;
padding: 5px 14px;
border-radius: 4px;
cursor: pointer;
width: 100px;
font-size: 12px;
}
.graph-container {
@ -1081,11 +1145,10 @@ export default {
.card-using {
background-color: #eff6ff !important;
outline: 1.5px solid #1559f3;
}
.card-checked {
border-left: 4px solid #ef4444 !important;
border-left: 4px solid rgb(239, 68, 68) !important;
}

Loading…
Cancel
Save