|
|
@@ -21,6 +21,102 @@ const levelImages = [level1, level1, level1, level2, level3, level4, level5, lev
|
|
|
const getLevelImage = (depth: number) => levelImages[Math.min(depth, 5)]
|
|
|
const getLevelSize = (depth: number) => (depth <= 2 ? 60 : 40)
|
|
|
|
|
|
+// 节点样式配置
|
|
|
+interface NodeStyleConfig {
|
|
|
+ fontColor: string
|
|
|
+ fontSize: number
|
|
|
+ fontWeight: 'bold' | 'normal'
|
|
|
+ lineColor: string
|
|
|
+}
|
|
|
+
|
|
|
+const getNodeStyleByDepth = (depth: number): NodeStyleConfig => {
|
|
|
+ if (depth === 3) {
|
|
|
+ return {
|
|
|
+ fontColor: '#FF9807',
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 'normal',
|
|
|
+ lineColor: '#FF9807'
|
|
|
+ }
|
|
|
+ } else if (depth >= 4) {
|
|
|
+ return {
|
|
|
+ fontColor: '#D1C9B2',
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 'normal',
|
|
|
+ lineColor: '#D1C9B2'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ fontColor: '#FFE9B6',
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ lineColor: '#FFE9B6'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 从节点 category 中提取 depth
|
|
|
+const getDepthFromCategory = (category: string): number => {
|
|
|
+ const match = (category || '').match(/level(\d+)/)
|
|
|
+ return match ? Number(match[1]) : 0
|
|
|
+}
|
|
|
+
|
|
|
+// 获取图表中所有节点的位置信息
|
|
|
+const getNodePositions = (chartInstance: any): Map<string, { x?: number; y?: number }> => {
|
|
|
+ const nodePositionMap = new Map<string, { x?: number; y?: number }>()
|
|
|
+ if (!chartInstance) return nodePositionMap
|
|
|
+
|
|
|
+ const currentOption = chartInstance.getOption()
|
|
|
+ const currentSeries = currentOption.series?.[0]
|
|
|
+ const currentNodes = currentSeries?.data || []
|
|
|
+
|
|
|
+ currentNodes.forEach((node: any) => {
|
|
|
+ if (node.id && (node.x !== undefined || node.y !== undefined)) {
|
|
|
+ nodePositionMap.set(node.id, { x: node.x, y: node.y })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return nodePositionMap
|
|
|
+}
|
|
|
+
|
|
|
+// 创建节点配置
|
|
|
+interface CreateNodeConfigOptions {
|
|
|
+ node: any
|
|
|
+ position?: { x?: number; y?: number }
|
|
|
+ opacity?: number
|
|
|
+ fixed?: boolean
|
|
|
+}
|
|
|
+
|
|
|
+const createNodeConfig = (options: CreateNodeConfigOptions) => {
|
|
|
+ const { node, position = {}, opacity = 1, fixed = true } = options
|
|
|
+ const depth = getDepthFromCategory(node.category || '')
|
|
|
+ const style = getNodeStyleByDepth(depth)
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ ...position,
|
|
|
+ fixed,
|
|
|
+ symbol: node.symbol,
|
|
|
+ symbolSize: node.symbolSize,
|
|
|
+ itemStyle: {
|
|
|
+ color: style.fontColor,
|
|
|
+ opacity
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ formatter: node.name,
|
|
|
+ fontSize: style.fontSize,
|
|
|
+ fontWeight: style.fontWeight,
|
|
|
+ color: style.fontColor,
|
|
|
+ position: 'bottom',
|
|
|
+ distance: 6,
|
|
|
+ align: 'center',
|
|
|
+ textShadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
+ textShadowBlur: 4,
|
|
|
+ textShadowOffsetX: 0,
|
|
|
+ textShadowOffsetY: 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const transformData = (data: any[]): any[] => {
|
|
|
return data.map(item => ({
|
|
|
...item,
|
|
|
@@ -40,9 +136,26 @@ const loadImage = (src: string): Promise<HTMLImageElement> => {
|
|
|
}
|
|
|
|
|
|
// 缩放区间和默认值(与 ECharts 的 scaleLimit / zoom 保持一致)
|
|
|
-const SCALE_MIN = 0.2
|
|
|
+const SCALE_MIN = 0.1
|
|
|
const SCALE_MAX = 1.2
|
|
|
const DEFAULT_ZOOM = 0.5
|
|
|
+const IS_PC = window.innerWidth > 1024
|
|
|
+
|
|
|
+// Force 布局配置
|
|
|
+const FORCE_LAYOUT_CONFIG = {
|
|
|
+ repulsion: 1500,
|
|
|
+ edgeLength: [80, 80],
|
|
|
+ gravity: 0.05,
|
|
|
+ layoutAnimation: false
|
|
|
+}
|
|
|
+
|
|
|
+// 禁用布局的配置(用于保持节点位置)
|
|
|
+const DISABLED_FORCE_CONFIG = {
|
|
|
+ initLayout: null,
|
|
|
+ friction: 0,
|
|
|
+ repulsion: 0,
|
|
|
+ gravity: 0
|
|
|
+}
|
|
|
|
|
|
const createCombinedSymbol = async (
|
|
|
levelImageSrc: string,
|
|
|
@@ -142,7 +255,7 @@ function A9knowlege() {
|
|
|
const category = `level${depth}`
|
|
|
const symbolSize = getLevelSize(depth)
|
|
|
const symbol = `image://${getLevelImage(depth)}`
|
|
|
- // 保存 thumb 信息用于后续处理
|
|
|
+
|
|
|
nodes.push({
|
|
|
id,
|
|
|
name: item.label,
|
|
|
@@ -153,9 +266,14 @@ function A9knowlege() {
|
|
|
depth,
|
|
|
thumb: item.thumb ? baseUrl + item.thumb : null
|
|
|
})
|
|
|
- if (parentId) links.push({ source: parentId, target: id, targetDepth: depth })
|
|
|
- if (Array.isArray(item.children))
|
|
|
+
|
|
|
+ if (parentId) {
|
|
|
+ links.push({ source: parentId, target: id, targetDepth: depth })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Array.isArray(item.children)) {
|
|
|
item.children.forEach((child: any) => traverse(child, id, depth + 1))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
data.forEach((root: any) => traverse(root, null, 0))
|
|
|
@@ -170,12 +288,14 @@ function A9knowlege() {
|
|
|
await Promise.all(thumbPromises)
|
|
|
|
|
|
const maxDepth = nodes.reduce((m, n) => {
|
|
|
- const match = (n.category || '').match(/level(\d+)/)
|
|
|
- const d = match ? Number(match[1]) : 0
|
|
|
+ const d = getDepthFromCategory(n.category || '')
|
|
|
return Math.max(m, d)
|
|
|
}, 0)
|
|
|
- const categories: any[] = []
|
|
|
- for (let i = 0; i <= maxDepth; i++) categories.push({ name: `level${i}` })
|
|
|
+
|
|
|
+ const categories = Array.from({ length: maxDepth + 1 }, (_, i) => ({
|
|
|
+ name: `level${i}`
|
|
|
+ }))
|
|
|
+
|
|
|
return { nodes, links, categories }
|
|
|
}
|
|
|
|
|
|
@@ -208,87 +328,41 @@ function A9knowlege() {
|
|
|
name: 'KnowledgeGraph',
|
|
|
type: 'graph',
|
|
|
layout: 'force',
|
|
|
- data: graph.nodes.map((n: any) => {
|
|
|
- const match = (n.category || '').match(/level(\d+)/)
|
|
|
- const depth = match ? Number(match[1]) : 0
|
|
|
- let fontColor = '#FFE9B6'
|
|
|
- let fontSize = 16
|
|
|
- let fontWeight: 'bold' | 'normal' = 'bold'
|
|
|
- if (depth === 3) {
|
|
|
- fontColor = '#FF9807'
|
|
|
- fontWeight = 'normal'
|
|
|
- } else if (depth >= 4) {
|
|
|
- fontColor = '#D1C9B2'
|
|
|
- fontSize = 14
|
|
|
- fontWeight = 'normal'
|
|
|
- }
|
|
|
- return {
|
|
|
- ...n,
|
|
|
- symbol: n.symbol,
|
|
|
- symbolSize: n.symbolSize,
|
|
|
- itemStyle: {
|
|
|
- color: fontColor
|
|
|
- },
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- formatter: n.name,
|
|
|
- fontSize,
|
|
|
- fontWeight,
|
|
|
- color: fontColor,
|
|
|
- position: 'bottom',
|
|
|
- distance: 6,
|
|
|
- align: 'center',
|
|
|
- textShadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
- textShadowBlur: 4,
|
|
|
- textShadowOffsetX: 0,
|
|
|
- textShadowOffsetY: 0
|
|
|
- }
|
|
|
- }
|
|
|
- }),
|
|
|
+ data: graph.nodes.map((n: any) => createNodeConfig({ node: n, fixed: false })),
|
|
|
links: graph.links.map((link: any) => {
|
|
|
- const depth = link.targetDepth
|
|
|
- let lineColor = '#FFE9B6'
|
|
|
- if (depth === 3) {
|
|
|
- lineColor = '#FF9807'
|
|
|
- } else if (depth >= 4) {
|
|
|
- lineColor = '#D1C9B2'
|
|
|
- }
|
|
|
+ const style = getNodeStyleByDepth(link.targetDepth)
|
|
|
return {
|
|
|
...link,
|
|
|
lineStyle: {
|
|
|
- color: lineColor
|
|
|
+ color: style.lineColor
|
|
|
}
|
|
|
}
|
|
|
}),
|
|
|
categories: graph.categories,
|
|
|
- roam: 'move',
|
|
|
+ roam: IS_PC ? true : 'move',
|
|
|
zoom: DEFAULT_ZOOM,
|
|
|
scaleLimit: {
|
|
|
min: SCALE_MIN,
|
|
|
max: SCALE_MAX
|
|
|
},
|
|
|
label: { position: 'bottom', distance: 6, align: 'center' },
|
|
|
- focusNodeAdjacency: true,
|
|
|
- emphasis: {
|
|
|
- focus: 'adjacency',
|
|
|
- lineStyle: {
|
|
|
- width: 3,
|
|
|
- opacity: 1
|
|
|
- }
|
|
|
- },
|
|
|
+ // emphasis: {
|
|
|
+ // focus: 'adjacency',
|
|
|
+ // lineStyle: {
|
|
|
+ // width: 3,
|
|
|
+ // opacity: 1
|
|
|
+ // }
|
|
|
+ // },
|
|
|
// force 布局参数:调大 repulsion 让斥力更明显
|
|
|
// 降低 gravity 避免被拉回中心过紧
|
|
|
- force: {
|
|
|
- repulsion: 900,
|
|
|
- edgeLength: [80, 80],
|
|
|
- gravity: 0.05
|
|
|
- },
|
|
|
+ force: FORCE_LAYOUT_CONFIG,
|
|
|
// 连线为直线:curveness 设为 0
|
|
|
lineStyle: { curveness: 0 },
|
|
|
edgeLabel: { show: false },
|
|
|
edgeSymbol: ['none', 'none']
|
|
|
}
|
|
|
- ]
|
|
|
+ ],
|
|
|
+ animationDurationUpdate: 0
|
|
|
}
|
|
|
|
|
|
myChart.setOption(option, true)
|
|
|
@@ -296,32 +370,62 @@ function A9knowlege() {
|
|
|
// 初始化滑块位置
|
|
|
setZoomPercent((DEFAULT_ZOOM - SCALE_MIN) / (SCALE_MAX - SCALE_MIN))
|
|
|
|
|
|
- const keepHighlight = () => {
|
|
|
- if (currentId.current && graphRef.current) {
|
|
|
- const graph = graphRef.current
|
|
|
- const nodeIndex = graph.nodes.findIndex((n: any) => n.raw && n.raw.id === currentId.current)
|
|
|
- if (nodeIndex !== -1) {
|
|
|
- // 重新高亮当前节点
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'highlight',
|
|
|
- dataIndex: nodeIndex
|
|
|
- })
|
|
|
- }
|
|
|
- } else {
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'downplay'
|
|
|
+ // 更新图表节点和连线配置的通用函数
|
|
|
+ const updateChartNodesAndLinks = (
|
|
|
+ nodes: any[],
|
|
|
+ links: any[],
|
|
|
+ opacityMap?: Map<string, number>
|
|
|
+ ) => {
|
|
|
+ const nodePositionMap = getNodePositions(myChart)
|
|
|
+ const updatedNodes = nodes.map((n: any) => {
|
|
|
+ const position = nodePositionMap.get(n.id) || {}
|
|
|
+ const opacity = opacityMap?.get(n.id) ?? 1
|
|
|
+ return createNodeConfig({
|
|
|
+ node: n,
|
|
|
+ position,
|
|
|
+ opacity
|
|
|
})
|
|
|
- }
|
|
|
+ })
|
|
|
+
|
|
|
+ const updatedLinks = links.map((link: any) => {
|
|
|
+ const linkKey = `${link.source}-${link.target}`
|
|
|
+ const reverseKey = `${link.target}-${link.source}`
|
|
|
+ // 如果没有提供 opacityMap,默认透明度为 1(重置状态)
|
|
|
+ // 如果提供了 opacityMap,检查是否有关联的连线
|
|
|
+ const opacity = opacityMap
|
|
|
+ ? opacityMap.get(linkKey) ?? opacityMap.get(reverseKey) ?? 0.1
|
|
|
+ : 1
|
|
|
+ return {
|
|
|
+ ...link,
|
|
|
+ lineStyle: {
|
|
|
+ ...link.lineStyle,
|
|
|
+ opacity
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ myChart.setOption(
|
|
|
+ {
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: updatedNodes,
|
|
|
+ links: updatedLinks,
|
|
|
+ force: DISABLED_FORCE_CONFIG
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ { notMerge: false }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复所有节点和连线的高亮状态(取消高亮)
|
|
|
+ const resetHighlight = () => {
|
|
|
+ updateChartNodesAndLinks(graph.nodes, graph.links)
|
|
|
}
|
|
|
|
|
|
myChart.on('click', function (params: any) {
|
|
|
if (!params || !params.data) return
|
|
|
|
|
|
- // 取消所有高亮
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'downplay'
|
|
|
- })
|
|
|
-
|
|
|
if (params.dataType === 'edge') {
|
|
|
// 关系线
|
|
|
const edge = params.data
|
|
|
@@ -330,75 +434,102 @@ function A9knowlege() {
|
|
|
|
|
|
if (sourceIndex !== -1 && targetIndex !== -1) {
|
|
|
// 高亮源节点
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'highlight',
|
|
|
- dataIndex: sourceIndex
|
|
|
- })
|
|
|
- // 高亮目标节点
|
|
|
- setTimeout(() => {
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'highlight',
|
|
|
- dataIndex: targetIndex
|
|
|
- })
|
|
|
- }, 10)
|
|
|
}
|
|
|
} else {
|
|
|
// 节点
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'highlight',
|
|
|
- dataIndex: params.dataIndex
|
|
|
+ const node = params.data
|
|
|
+ const targetId = params.data.id
|
|
|
+ const related = new Set([targetId])
|
|
|
+
|
|
|
+ // 创建节点和连线的透明度映射
|
|
|
+ const opacityMap = new Map<string, number>()
|
|
|
+
|
|
|
+ // 标记关联元素并设置透明度映射
|
|
|
+ graph.links.forEach((link: any) => {
|
|
|
+ if (link.source === targetId || link.target === targetId) {
|
|
|
+ related.add(link.source)
|
|
|
+ related.add(link.target)
|
|
|
+ // 设置关联连线的透明度
|
|
|
+ const linkKey = `${link.source}-${link.target}`
|
|
|
+ const reverseKey = `${link.target}-${link.source}`
|
|
|
+ opacityMap.set(linkKey, 1)
|
|
|
+ opacityMap.set(reverseKey, 1)
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
- const node = params.data
|
|
|
+ // 设置节点透明度
|
|
|
+ graph.nodes.forEach((n: any) => {
|
|
|
+ opacityMap.set(n.id, related.has(n.id) ? 1 : 0.1)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 更新图表节点和连线
|
|
|
+ updateChartNodesAndLinks(graph.nodes, graph.links, opacityMap)
|
|
|
+
|
|
|
if (node && node.raw && node.raw.id) {
|
|
|
- if (currentId.current === node.raw.id) return
|
|
|
+ // 如果点击的节点是已选中的节点,则取消选中状态
|
|
|
+ if (currentId.current === node.raw.id) {
|
|
|
+ currentId.current = null
|
|
|
+ setDetail(null)
|
|
|
+ resetHighlight() // 恢复所有节点和连线的高亮状态
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 高亮选中的节点
|
|
|
|
|
|
const fetchDetail = async () => {
|
|
|
try {
|
|
|
currentId.current = node.raw.id
|
|
|
setDetailLoading(true)
|
|
|
const response = await http.get(`/show/dict/detail/${node.raw.id}`)
|
|
|
- if (response.code === 0 && response.data) {
|
|
|
- const { label, name, rtf } = response.data
|
|
|
- let detailData: any = { name }
|
|
|
-
|
|
|
- // 存在 label 显示 Panel
|
|
|
- if (label) {
|
|
|
- try {
|
|
|
- const parsedLabel = JSON.parse(label.replace(/\\\\"/g, '\\"'))
|
|
|
- detailData = {
|
|
|
- ...detailData,
|
|
|
- label: name,
|
|
|
- content: Array.isArray(parsedLabel) ? parsedLabel : [],
|
|
|
- type: 'label'
|
|
|
- }
|
|
|
- setDetail(detailData)
|
|
|
- setSidebarVisible(true)
|
|
|
- } catch (e) {
|
|
|
- console.error('解析 label 失败:', e)
|
|
|
+
|
|
|
+ if (response.code !== 0 || !response.data) {
|
|
|
+ setDetail(null)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const { label, name, rtf } = response.data
|
|
|
+ let detailData: any = { name }
|
|
|
+
|
|
|
+ // 存在 label 显示 Panel
|
|
|
+ if (label) {
|
|
|
+ try {
|
|
|
+ const parsedLabel = JSON.parse(label.replace(/\\\\"/g, '\\"'))
|
|
|
+ detailData = {
|
|
|
+ ...detailData,
|
|
|
+ label: name,
|
|
|
+ content: Array.isArray(parsedLabel) ? parsedLabel : [],
|
|
|
+ type: 'label'
|
|
|
}
|
|
|
+ setDetail(detailData)
|
|
|
+ setSidebarVisible(true)
|
|
|
+ return
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析 label 失败:', e)
|
|
|
}
|
|
|
- // 存在 rtf 显示 Panel2
|
|
|
- else if (rtf) {
|
|
|
- try {
|
|
|
- const parsedRtf = JSON.parse(rtf)
|
|
|
- const content = parsedRtf?.txtArr?.[0]?.txt || ''
|
|
|
- detailData = {
|
|
|
- ...detailData,
|
|
|
- label: name,
|
|
|
- content: content,
|
|
|
- type: 'rtf'
|
|
|
- }
|
|
|
- setDetail(detailData)
|
|
|
- setSidebarVisible(true)
|
|
|
- } catch (e) {
|
|
|
- console.error('解析 rtf 失败:', e)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 存在 rtf 显示 Panel2
|
|
|
+ if (rtf) {
|
|
|
+ try {
|
|
|
+ const parsedRtf = JSON.parse(rtf)
|
|
|
+ const content = parsedRtf?.txtArr?.[0]?.txt || ''
|
|
|
+ detailData = {
|
|
|
+ ...detailData,
|
|
|
+ label: name,
|
|
|
+ content: content,
|
|
|
+ type: 'rtf'
|
|
|
}
|
|
|
- } else {
|
|
|
- setDetail(null)
|
|
|
+ setDetail(detailData)
|
|
|
+ setSidebarVisible(true)
|
|
|
+ return
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析 rtf 失败:', e)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ setDetail(null)
|
|
|
} catch (error) {
|
|
|
+ console.error('获取详情失败:', error)
|
|
|
setDetail(null)
|
|
|
} finally {
|
|
|
setDetailLoading(false)
|
|
|
@@ -410,26 +541,22 @@ function A9knowlege() {
|
|
|
})
|
|
|
myChart.getZr().on('click', function (params: any) {
|
|
|
if (!params.target) {
|
|
|
+ // 点击空白处,取消选中状态和高亮
|
|
|
currentId.current = null
|
|
|
- myChart.dispatchAction({
|
|
|
- type: 'downplay'
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- myChart.on('mouseover', function () {
|
|
|
- keepHighlight()
|
|
|
- })
|
|
|
- myChart.on('graphroam', function (params: any) {
|
|
|
- keepHighlight()
|
|
|
- if (typeof params?.zoom === 'number') {
|
|
|
- const p = (params.zoom - SCALE_MIN) / (SCALE_MAX - SCALE_MIN)
|
|
|
- setZoomPercent(Math.max(0, Math.min(1, p)))
|
|
|
+ resetHighlight() // 恢复所有节点和连线的高亮状态
|
|
|
}
|
|
|
})
|
|
|
+ if (!IS_PC) {
|
|
|
+ myChart.on('graphroam', function (params: any) {
|
|
|
+ if (typeof params?.zoom === 'number') {
|
|
|
+ const p = (params.zoom - SCALE_MIN) / (SCALE_MAX - SCALE_MIN)
|
|
|
+ setZoomPercent(Math.max(0, Math.min(1, p)))
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
const resizeHandler = () => myChart.resize()
|
|
|
window.addEventListener('resize', resizeHandler)
|
|
|
- ;(chartInstance.current as any)._resizeHandler = resizeHandler
|
|
|
}, [knowlegeData, dataLoading])
|
|
|
|
|
|
const handleClosePanel = () => {
|
|
|
@@ -499,22 +626,24 @@ function A9knowlege() {
|
|
|
<div id='echart-container' ref={echartRef} />
|
|
|
</div>
|
|
|
|
|
|
- <div className={styles.scaleControl}>
|
|
|
- <div className={styles.scaleControlItem} onClick={() => handleZoomStep(-0.1)}>
|
|
|
- <img src={require('./images/icon_zoomin.png')} alt='' />
|
|
|
- <div className={styles.scaleControlItemText}>缩小</div>
|
|
|
- </div>
|
|
|
- <div className={styles.scaleControlItemLine} ref={scaleLineRef}>
|
|
|
- <div
|
|
|
- className={styles.scaleControlItemLineInner}
|
|
|
- style={{ left: `${zoomPercent * 100}%` }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div className={styles.scaleControlItem} onClick={() => handleZoomStep(0.1)}>
|
|
|
- <img src={require('./images/icon_zoomax.png')} alt='' />
|
|
|
- <div className={styles.scaleControlItemText}>放大</div>
|
|
|
+ {!IS_PC && (
|
|
|
+ <div className={styles.scaleControl}>
|
|
|
+ <div className={styles.scaleControlItem} onClick={() => handleZoomStep(-0.1)}>
|
|
|
+ <img src={require('./images/icon_zoomin.png')} alt='' />
|
|
|
+ <div className={styles.scaleControlItemText}>缩小</div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.scaleControlItemLine} ref={scaleLineRef}>
|
|
|
+ <div
|
|
|
+ className={styles.scaleControlItemLineInner}
|
|
|
+ style={{ left: `${zoomPercent * 100}%` }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className={styles.scaleControlItem} onClick={() => handleZoomStep(0.1)}>
|
|
|
+ <img src={require('./images/icon_zoomax.png')} alt='' />
|
|
|
+ <div className={styles.scaleControlItemText}>放大</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ )}
|
|
|
|
|
|
<div className={`${styles.sidebar} ${!sidebarVisible ? styles.sidebarHidden : ''}`}>
|
|
|
{detailLoading ? (
|