Przeglądaj źródła

bugfix:补全index

XieXing 4 miesięcy temu
rodzic
commit
4277abe80d

+ 91 - 32
README.md

@@ -1,17 +1,43 @@
 # 部署与上线指南
 # 部署与上线指南
 
 
-本文档详细说明如何在服务器的 `/tmp/tmp` 目录下拉取 `dev` 分支代码并完成前后端的编译与生产环境部署。
+本文档详细说明如何在服务器的 `/tmp/tmp` 目录下拉取 `dev` 分支代码,并完成前后端的编译与生产环境部署。
 
 
 ## 1. 环境准备
 ## 1. 环境准备
 
 
-确保服务器已安装以下环境:
+### 方案A:服务器直接编译(需要安装 Go)
+
+确保服务器已安装以下环境:
 - **Git**: 用于拉取代码
 - **Git**: 用于拉取代码
 - **Node.js** (≥ 20.19) & **npm**: 用于前端打包
 - **Node.js** (≥ 20.19) & **npm**: 用于前端打包
 - **Go** (≥ 1.24): 用于后端编译
 - **Go** (≥ 1.24): 用于后端编译
 
 
+#### 安装 Go 1.24+
+
+```bash
+# 1. 下载 Go 1.24
+wget https://go.dev/dl/go1.24.0.linux-amd64.tar.gz
+
+# 2. 解压到 /usr/local
+sudo rm -rf /usr/local/go
+sudo tar -C /usr/local -xzf go1.24.0.linux-amd64.tar.gz
+
+# 3. 配置环境变量
+echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
+echo 'export GOPATH=$HOME/go' >> ~/.bashrc
+echo 'export GOPROXY=https://goproxy.cn,direct' >> ~/.bashrc
+source ~/.bashrc
+
+# 4. 验证安装
+go version
+```
+
+### 方案B:本地编译上传(推荐,无需服务器安装 Go)
+
+在本地 Windows 环境编译好二进制文件,然后上传到服务器。详见后续步骤。
+
 ## 2. 拉取代码
 ## 2. 拉取代码
 
 
-进入目标目录并拉取远程 `dev` 分支代码:
+进入目标目录并拉取远程 `dev` 分支代码:
 
 
 ```bash
 ```bash
 # 1. 创建并进入目录
 # 1. 创建并进入目录
@@ -19,19 +45,19 @@ mkdir -p /tmp/tmp
 cd /tmp/tmp
 cd /tmp/tmp
 
 
 # 2. 拉取代码
 # 2. 拉取代码
-# 情况A:如果是首次部署(目录为空)
+# 情况A:如果是首次部署(目录为空)
 git clone -b dev http://192.168.0.3:3000/ShuDao-SafeAI/ShuDaoMAIN.git .
 git clone -b dev http://192.168.0.3:3000/ShuDao-SafeAI/ShuDaoMAIN.git .
 
 
-# 情况B如果目录已存在且已有仓库
+# 情况B:如果目录已存在且已有仓库
 # git checkout dev
 # git checkout dev
 # git pull origin dev
 # git pull origin dev
 ```
 ```
 
 
-> **注意**请根据实际情况替换 git 仓库地址。
+> **注意**:请根据实际情况替换 git 仓库地址。
 
 
 ## 3. 前端编译 (shudao-vue-frontend)
 ## 3. 前端编译 (shudao-vue-frontend)
 
 
-进入前端目录,安装依赖并打包:
+进入前端目录,安装依赖并打包:
 
 
 ```bash
 ```bash
 # 1. 进入前端目录
 # 1. 进入前端目录
@@ -44,11 +70,11 @@ npm install --registry=https://registry.npmmirror.com
 npm run build
 npm run build
 ```
 ```
 
 
-构建完成后,会在 `shudao-vue-frontend/dist` 目录下生成静态资源(包含 `assets` 目录和 `index.html`)
+构建完成后,会在 `shudao-vue-frontend/dist` 目录下生成静态资源(包含 `assets` 目录和 `index.html`)
 
 
 ## 4. 整合产物到后端
 ## 4. 整合产物到后端
 
 
-将前端生成的静态资源复制到后端对应的目录中
+将前端生成的静态资源复制到后端对应的目录中:
 
 
 ```bash
 ```bash
 # 1. 回到项目根目录
 # 1. 回到项目根目录
@@ -57,7 +83,7 @@ cd ..
 # 2. 进入后端目录
 # 2. 进入后端目录
 cd shudao-go-backend
 cd shudao-go-backend
 
 
-# 3. 清理旧资源(可选,建议执行以避免缓存问题)
+# 3. 清理旧资源(可选,建议执行以避免缓存问题)
 rm -rf assets
 rm -rf assets
 rm -f views/index.html
 rm -f views/index.html
 
 
@@ -69,45 +95,78 @@ cp -r ../shudao-vue-frontend/dist/assets .
 cp ../shudao-vue-frontend/dist/index.html views/
 cp ../shudao-vue-frontend/dist/index.html views/
 ```
 ```
 
 
-> **说明**:
-> - `assets/` 目录存放 JS/CSS/图片等静态资源,后端通过 `beego.SetStaticPath("/assets", "assets")` 进行映射。
-> - `views/index.html` 是前端入口页面,后端控制器会渲染此模板。
+> **说明**:
+> - `assets/` 目录存放 JS/CSS/图片等静态资源,后端通过 `beego.SetStaticPath("/assets", "assets")` 进行映射。
+> - `views/index.html` 是前端入口页面,后端控制器会渲染此模板。
+
+## 5. 后端编译与打包 (shudao-go-backend)
+
+### 方法1:使用 Beego bee 工具打包(推荐)
+
+```bash
+# 1. 进入后端目录
+cd shudao-go-backend
+
+# 2. 安装 bee 工具(如果未安装)
+go install github.com/beego/bee/v2@latest
 
 
-## 5. 后端编译与运行 (shudao-go-backend)
+# 3. 使用 bee pack 打包(会自动交叉编译为 Linux 并打包所有资源)
+bee pack -be GOOS=linux -be GOARCH=amd64
+
+# 4. 生成的压缩包:shudao-go-backend.tar.gz
+# 将此文件上传到服务器 /opt/www/shudao_main/shudao-go-backend/ 目录
+```
 
 
-编译 Go 后端服务并启动:
+### 方法2:直接使用 go build 编译
 
 
 ```bash
 ```bash
-# 1. 整理依赖
+# 1. 进入后端目录
+cd shudao-go-backend
+
+# 2. 整理依赖
 go mod tidy
 go mod tidy
 
 
-# 2. 编译二进制文件
-# GOOS=linux GOARCH=amd64 确保编译为 Linux 可执行文件
-GOOS=linux GOARCH=amd64 go build -o shudao-chat-go main.go
+# 3. 交叉编译为 Linux 可执行文件
+# Windows PowerShell:
+$env:GOOS="linux"; $env:GOARCH="amd64"; go build -o shudao-go-backend main.go
+
+# Linux/Mac:
+GOOS=linux GOARCH=amd64 go build -o shudao-go-backend main.go
+```
+
+### 服务器部署
+
+```bash
+# 1. 进入部署目录
+cd /opt/www/shudao_main/shudao-go-backend
+
+# 2. 解压(如果使用 bee pack)
+tar -xzf shudao-go-backend.tar.gz
+
+# 3. 进入后端目录
+cd shudao-go-backend
 
 
-# 3. 赋予执行权限
-chmod +x shudao-chat-go
+# 4. 赋予执行权限
+chmod +x shudao-go-backend
 
 
-# 4. 停止旧服务(如果存在)
-# 查找进程ID
-# ps -ef | grep shudao-chat-go
-# 杀掉进程
-# kill -9 <PID>
+# 5. 停止旧服务(如果存在)
+ps -ef | grep shudao-go-backend
+# 找到进程ID后执行:kill -9 <PID>
 
 
-# 5. 启动新服务 (后台运行)
-nohup ./shudao-chat-go > nohup.out 2>&1 &
+# 6. 启动新服务 (后台运行)
+nohup ./shudao-go-backend > nohup.out 2>&1 &
 
 
-# 6. 查看启动日志
+# 7. 查看启动日志
 tail -f nohup.out
 tail -f nohup.out
 ```
 ```
 
 
 ## 6. 验证部署
 ## 6. 验证部署
 
 
-- 检查进程是否存在
+- 检查进程是否存在:
   ```bash
   ```bash
-  ps -ef | grep shudao-chat-go
+  ps -ef | grep shudao-go-backend
   ```
   ```
-- 访问服务地址确认页面加载正常且 API 调用成功。
+- 访问服务地址,确认页面加载正常且 API 调用成功。
 
 
 ---
 ---
 
 

+ 28 - 28
shudao-vue-frontend/src/utils/markdown.js

@@ -42,9 +42,9 @@ const renderLatexWithKatex = (latex, displayMode = false) => {
  */
  */
 const preprocessLatex = (text) => {
 const preprocessLatex = (text) => {
   if (!text) return text
   if (!text) return text
-  
+
   let processed = text
   let processed = text
-  
+
   // 首先处理块级公式 $$...$$(必须在行内公式之前处理)
   // 首先处理块级公式 $$...$$(必须在行内公式之前处理)
   // 支持多行块级公式
   // 支持多行块级公式
   processed = processed.replace(/\$\$([\s\S]+?)\$\$/g, (match, latex) => {
   processed = processed.replace(/\$\$([\s\S]+?)\$\$/g, (match, latex) => {
@@ -53,7 +53,7 @@ const preprocessLatex = (text) => {
     // 使用特殊标记包裹,防止被 Markdown 解析
     // 使用特殊标记包裹,防止被 Markdown 解析
     return `<div class="katex-block-wrapper">${rendered}</div>`
     return `<div class="katex-block-wrapper">${rendered}</div>`
   })
   })
-  
+
   // 然后处理行内公式 $...$
   // 然后处理行内公式 $...$
   // 注意:不匹配已经处理过的 katex-block-wrapper 标记
   // 注意:不匹配已经处理过的 katex-block-wrapper 标记
   processed = processed.replace(/(?<!<div class="katex-block-wrapper">.*)\$([^\$\n]+?)\$(?!.*<\/div>)/g, (match, latex) => {
   processed = processed.replace(/(?<!<div class="katex-block-wrapper">.*)\$([^\$\n]+?)\$(?!.*<\/div>)/g, (match, latex) => {
@@ -61,21 +61,21 @@ const preprocessLatex = (text) => {
     const rendered = renderLatexWithKatex(trimmedLatex, false)
     const rendered = renderLatexWithKatex(trimmedLatex, false)
     return `<span class="katex-inline-wrapper">${rendered}</span>`
     return `<span class="katex-inline-wrapper">${rendered}</span>`
   })
   })
-  
+
   // 处理 \(...\) 格式的行内公式
   // 处理 \(...\) 格式的行内公式
   processed = processed.replace(/\\\((.+?)\\\)/g, (match, latex) => {
   processed = processed.replace(/\\\((.+?)\\\)/g, (match, latex) => {
     const trimmedLatex = latex.trim()
     const trimmedLatex = latex.trim()
     const rendered = renderLatexWithKatex(trimmedLatex, false)
     const rendered = renderLatexWithKatex(trimmedLatex, false)
     return `<span class="katex-inline-wrapper">${rendered}</span>`
     return `<span class="katex-inline-wrapper">${rendered}</span>`
   })
   })
-  
+
   // 处理 \[...\] 格式的块级公式
   // 处理 \[...\] 格式的块级公式
   processed = processed.replace(/\\\[([\s\S]+?)\\\]/g, (match, latex) => {
   processed = processed.replace(/\\\[([\s\S]+?)\\\]/g, (match, latex) => {
     const trimmedLatex = latex.trim()
     const trimmedLatex = latex.trim()
     const rendered = renderLatexWithKatex(trimmedLatex, true)
     const rendered = renderLatexWithKatex(trimmedLatex, true)
     return `<div class="katex-block-wrapper">${rendered}</div>`
     return `<div class="katex-block-wrapper">${rendered}</div>`
   })
   })
-  
+
   return processed
   return processed
 }
 }
 
 
@@ -85,7 +85,7 @@ const renderer = {
     // marked v4+ 传入的是token对象
     // marked v4+ 传入的是token对象
     // 需要从token中提取href和text
     // 需要从token中提取href和text
     let href, text, title
     let href, text, title
-    
+
     if (typeof token === 'object' && token !== null) {
     if (typeof token === 'object' && token !== null) {
       // token对象格式
       // token对象格式
       href = token.href || token.url || '#'
       href = token.href || token.url || '#'
@@ -97,12 +97,12 @@ const renderer = {
       text = arguments[2] || arguments[1] || href
       text = arguments[2] || arguments[1] || href
       title = arguments[1] || href
       title = arguments[1] || href
     }
     }
-    
+
     // 确保是字符串类型
     // 确保是字符串类型
     href = String(href)
     href = String(href)
     text = String(text)
     text = String(text)
     title = String(title)
     title = String(title)
-    
+
     // HTML转义,防止XSS
     // HTML转义,防止XSS
     const escapeHtml = (str) => {
     const escapeHtml = (str) => {
       return str
       return str
@@ -112,47 +112,47 @@ const renderer = {
         .replace(/"/g, '&quot;')
         .replace(/"/g, '&quot;')
         .replace(/'/g, '&#039;')
         .replace(/'/g, '&#039;')
     }
     }
-    
+
     const escapedHref = escapeHtml(href)
     const escapedHref = escapeHtml(href)
     const escapedText = escapeHtml(text)
     const escapedText = escapeHtml(text)
     const escapedTitle = escapeHtml(title)
     const escapedTitle = escapeHtml(title)
-    
+
     // 渲染为灰色胶囊样式
     // 渲染为灰色胶囊样式
     const capsuleStyle = `
     const capsuleStyle = `
       display: inline-flex;
       display: inline-flex;
       align-items: center;
       align-items: center;
-      gap: 4px;
+      gap: 3px;
       background-color: #E8EAED;
       background-color: #E8EAED;
       color: #5F6368;
       color: #5F6368;
-      font-size: 13px;
-      padding: 4px 10px;
-      border-radius: 12px;
+      font-size: 11px;
+      padding: 1px 8px;
+      border-radius: 10px;
       cursor: pointer;
       cursor: pointer;
-      margin: 0 4px;
+      margin: 0 2px;
       border: 1px solid #DADCE0;
       border: 1px solid #DADCE0;
       font-weight: 500;
       font-weight: 500;
       transition: all 0.2s ease;
       transition: all 0.2s ease;
-      line-height: 1.4;
-      max-width: 300px;
+      line-height: 1.2;
+      max-width: 260px;
       white-space: nowrap;
       white-space: nowrap;
       overflow: hidden;
       overflow: hidden;
       text-overflow: ellipsis;
       text-overflow: ellipsis;
       vertical-align: middle;
       vertical-align: middle;
       text-decoration: none;
       text-decoration: none;
     `.replace(/\s+/g, ' ').trim()
     `.replace(/\s+/g, ' ').trim()
-    
+
     const hoverStyle = `
     const hoverStyle = `
       this.style.backgroundColor='#D2D4D8';
       this.style.backgroundColor='#D2D4D8';
       this.style.borderColor='#BABDBF';
       this.style.borderColor='#BABDBF';
       this.style.boxShadow='0 1px 3px rgba(0, 0, 0, 0.1)';
       this.style.boxShadow='0 1px 3px rgba(0, 0, 0, 0.1)';
     `
     `
-    
+
     const resetStyle = `
     const resetStyle = `
       this.style.backgroundColor='#E8EAED';
       this.style.backgroundColor='#E8EAED';
       this.style.borderColor='#DADCE0';
       this.style.borderColor='#DADCE0';
       this.style.boxShadow='none';
       this.style.boxShadow='none';
     `
     `
-    
+
     return `<a 
     return `<a 
       href="${escapedHref}" 
       href="${escapedHref}" 
       class="link-capsule" 
       class="link-capsule" 
@@ -161,7 +161,7 @@ const renderer = {
       onmouseover="${hoverStyle}"
       onmouseover="${hoverStyle}"
       onmouseout="${resetStyle}"
       onmouseout="${resetStyle}"
       title="${escapedTitle}"
       title="${escapedTitle}"
-    ><span style="font-size: 12px;">🔗</span><span>${escapedText}</span></a>`
+    ><span style="font-size: 9px;">🔗</span><span>${escapedText}</span></a>`
   }
   }
 }
 }
 
 
@@ -181,17 +181,17 @@ marked.use({
  */
  */
 export const renderMarkdown = (markdown) => {
 export const renderMarkdown = (markdown) => {
   if (!markdown) return ''
   if (!markdown) return ''
-  
+
   try {
   try {
     // 确保输入是字符串
     // 确保输入是字符串
     let markdownText = String(markdown)
     let markdownText = String(markdown)
-    
+
     // 预处理 LaTeX 公式(在 Markdown 解析之前)
     // 预处理 LaTeX 公式(在 Markdown 解析之前)
     markdownText = preprocessLatex(markdownText)
     markdownText = preprocessLatex(markdownText)
-    
+
     // 渲染 Markdown 为 HTML
     // 渲染 Markdown 为 HTML
     const rawHtml = marked.parse(markdownText)
     const rawHtml = marked.parse(markdownText)
-    
+
     // 使用 DOMPurify 清理 HTML,防止 XSS 攻击
     // 使用 DOMPurify 清理 HTML,防止 XSS 攻击
     const cleanHtml = DOMPurify.sanitize(rawHtml, {
     const cleanHtml = DOMPurify.sanitize(rawHtml, {
       ALLOWED_TAGS: [
       ALLOWED_TAGS: [
@@ -204,7 +204,7 @@ export const renderMarkdown = (markdown) => {
         'table', 'thead', 'tbody', 'tr', 'th', 'td',
         'table', 'thead', 'tbody', 'tr', 'th', 'td',
         'div', 'span',
         'div', 'span',
         // KaTeX 需要的标签 - 完整列表
         // KaTeX 需要的标签 - 完整列表
-        'math', 'semantics', 'mrow', 'mi', 'mn', 'mo', 'mfrac', 'msup', 'msub', 'msubsup', 
+        'math', 'semantics', 'mrow', 'mi', 'mn', 'mo', 'mfrac', 'msup', 'msub', 'msubsup',
         'mtext', 'mspace', 'annotation', 'menclose', 'mover', 'munder', 'munderover',
         'mtext', 'mspace', 'annotation', 'menclose', 'mover', 'munder', 'munderover',
         'mtable', 'mtr', 'mtd', 'msqrt', 'mroot', 'mpadded', 'mphantom', 'mglyph',
         'mtable', 'mtr', 'mtd', 'msqrt', 'mroot', 'mpadded', 'mphantom', 'mglyph',
         'svg', 'path', 'line', 'rect', 'circle', 'use', 'g', 'defs', 'symbol', 'foreignObject'
         'svg', 'path', 'line', 'rect', 'circle', 'use', 'g', 'defs', 'symbol', 'foreignObject'
@@ -220,7 +220,7 @@ export const renderMarkdown = (markdown) => {
       // 允许所有 data-* 属性
       // 允许所有 data-* 属性
       ALLOW_DATA_ATTR: true
       ALLOW_DATA_ATTR: true
     })
     })
-    
+
     return cleanHtml
     return cleanHtml
   } catch (error) {
   } catch (error) {
     console.error('Markdown 渲染失败:', error)
     console.error('Markdown 渲染失败:', error)

+ 6 - 0
shudao-vue-frontend/src/views/mobile/m-HazardDetection.vue

@@ -1860,6 +1860,8 @@ watch(showHistory, async (newVal) => {
             justify-content: center;
             justify-content: center;
             border-radius: 8px;
             border-radius: 8px;
             cursor: pointer;
             cursor: pointer;
+            opacity: 0;
+            transition: opacity 0.3s ease;
 
 
             .change-image-btn {
             .change-image-btn {
               background: #3e7bfa;
               background: #3e7bfa;
@@ -1877,6 +1879,10 @@ watch(showHistory, async (newVal) => {
               }
               }
             }
             }
           }
           }
+          
+          &:hover .image-overlay {
+            opacity: 1;
+          }
         }
         }
 
 
         .upload-content {
         .upload-content {