ソースを参照

feat(health): 新增 SystemPrompt 管理功能 + web 品牌与产品素材

- 后端: SystemPrompt 实体/Mapper/Service/Controller + publicapi
- Web 管理端: systemPrompt.js API + SystemPromptManagement.vue 管理页
- cfc-langgraph: requirements_frozen.txt 依赖冻结
- web/img: 品牌/产品/引用源图片素材(cert/diagram/product-intro/products/ref)
- axonhub: AxonHub LLM 网关配置(compose+config)
- docs: web 品牌方向计划文档

未追踪 uploads/(临时 json 结果) 不入库
Xiaogang Liao 1 ヶ月 前
コミット
e9a500f252
55 ファイル変更1112 行追加0 行削除
  1. 38 0
      axonhub/config.yml
  2. 50 0
      axonhub/docker-compose.yml
  3. 78 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminSystemPromptController.java
  4. 47 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/publicapi/SystemPromptPublicController.java
  5. 37 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/SystemPrompt.java
  6. 7 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/SystemPromptMapper.java
  7. 17 0
      cfc-backend/src/main/java/com/etotem/cfc/service/SystemPromptService.java
  8. 68 0
      cfc-backend/src/main/java/com/etotem/cfc/service/impl/SystemPromptServiceImpl.java
  9. 123 0
      cfc-langgraph/requirements_frozen.txt
  10. 34 0
      cfc-web/src/api/systemPrompt.js
  11. 186 0
      cfc-web/src/views/admin/SystemPromptManagement.vue
  12. 427 0
      docs/superpowers/plans/2026-08-14-web-brand-direction.md
  13. BIN
      web/img/cert/cert-leveluk-endorsement.jpg
  14. BIN
      web/img/diagram_1_en.png
  15. BIN
      web/img/diagram_2_en.png
  16. BIN
      web/img/diagram_3_en.png
  17. BIN
      web/img/diagram_4_en.png
  18. BIN
      web/img/product-intro/gut-8dims-diagram.jpg
  19. BIN
      web/img/product-intro/gut-kit-5.jpg
  20. BIN
      web/img/product-intro/gut-kit-7.jpg
  21. BIN
      web/img/product-intro/gut-kit-cover.jpg
  22. BIN
      web/img/products/jr4.jpg
  23. BIN
      web/img/products/k8.jpg
  24. BIN
      web/img/products/sd501dx.jpg
  25. BIN
      web/img/products/super501.jpg
  26. BIN
      web/img/ref-10.png
  27. BIN
      web/img/ref-11.png
  28. BIN
      web/img/ref-12.png
  29. BIN
      web/img/ref-13.png
  30. BIN
      web/img/ref-14.png
  31. BIN
      web/img/ref-15.png
  32. BIN
      web/img/ref-16.png
  33. BIN
      web/img/ref-17.png
  34. BIN
      web/img/ref-18.png
  35. BIN
      web/img/ref-19.png
  36. BIN
      web/img/ref-20.png
  37. BIN
      web/img/ref-21.png
  38. BIN
      web/img/ref-3.png
  39. BIN
      web/img/ref-7.png
  40. BIN
      web/img/ref-9.png
  41. BIN
      web/img/ref-boiling-water.png
  42. BIN
      web/img/ref-heat-sunlight.png
  43. BIN
      web/img/ref-hot-water-antimony.png
  44. BIN
      web/img/ref-nejm.png
  45. BIN
      web/img/ref-ohio-state.png
  46. BIN
      web/img/ref-pet-sources.png
  47. BIN
      web/img/ref-pmc-2025-review.png
  48. BIN
      web/img/ref-pnas.png
  49. BIN
      web/img/ref-sciencedaily.png
  50. BIN
      web/img/ref-screenshots/chinas-dbps-spatial.jpg
  51. BIN
      web/img/ref-screenshots/enagic-comparison.svg
  52. BIN
      web/img/ref-screenshots/gut-microbiome-damage.jpg
  53. BIN
      web/img/ref-screenshots/pesticides-46-detection.jpg
  54. BIN
      web/img/ref-who-report.png
  55. BIN
      web/img/yan.png

+ 38 - 0
axonhub/config.yml

@@ -0,0 +1,38 @@
+server:
+  host: "0.0.0.0"
+  port: 8090
+  name: "AxonHub"
+  request_timeout: "30s"
+  llm_request_timeout: "600s"
+  debug: false
+  disable_ssl_verify: false
+  cors:
+    enabled: true
+    allowed_origins:
+      - "*"
+    allowed_methods:
+      - "GET"
+      - "POST"
+      - "DELETE"
+      - "PATCH"
+      - "PUT"
+      - "OPTIONS"
+      - "HEAD"
+    allowed_headers:
+      - "Content-Type"
+      - "Authorization"
+      - "X-API-Key"
+      - "X-Goog-Api-Key"
+      - "AH-Thread-Id"
+      - "AH-Trace-Id"
+    allow_credentials: true
+    max_age: "1m"
+  api:
+    auth:
+      allow_no_auth: true
+      key_prefix: "ah"
+
+db:
+  dialect: "postgres"
+  dsn: "postgres://axonhub:axonhub_password@postgres:5432/axonhub?sslmode=disable"
+  disable_auto_migration: false

+ 50 - 0
axonhub/docker-compose.yml

@@ -0,0 +1,50 @@
+services:
+  postgres:
+    image: postgres:16-alpine
+    container_name: axonhub-postgres
+    environment:
+      POSTGRES_DB: axonhub
+      POSTGRES_USER: axonhub
+      POSTGRES_PASSWORD: ${DB_PASSWORD:-axonhub_password}
+    volumes:
+      - postgres_data:/var/lib/postgresql/data
+    ports:
+      - "5432:5432"
+    networks:
+      - axonhub-network
+    restart: unless-stopped
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U axonhub"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+
+  axonhub:
+    image: looplj/axonhub:latest
+    container_name: axonhub-app
+    environment:
+      AXONHUB_DB_DIALECT: postgres
+      AXONHUB_DB_DSN: postgres://axonhub:${DB_PASSWORD:-axonhub_password}@postgres:5432/axonhub?sslmode=disable
+    ports:
+      - "8090:8090"
+    volumes:
+      - ./config.yml:/app/config.yml:ro
+    networks:
+      - axonhub-network
+    depends_on:
+      postgres:
+        condition: service_healthy
+    restart: unless-stopped
+    healthcheck:
+      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8090/health"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 40s
+
+volumes:
+  postgres_data:
+
+networks:
+  axonhub-network:
+    driver: bridge

+ 78 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminSystemPromptController.java

@@ -0,0 +1,78 @@
+package com.etotem.cfc.controller.admin;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.SystemPrompt;
+import com.etotem.cfc.service.SystemPromptService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import com.etotem.cfc.util.ParamUtils;
+
+@Tag(name = "系统提示词管理", description = "后台管理系统提示词")
+@RestController
+@RequestMapping("/api/admin/system-prompt")
+public class AdminSystemPromptController {
+
+    @Resource
+    private SystemPromptService systemPromptService;
+
+    @Operation(summary = "分页查询提示词")
+    @PostMapping("/list")
+    public Result<Map<String, Object>> list(@RequestBody Map<String, Object> params) {
+        String keyword = (String) params.get("keyword");
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
+        return Result.success(systemPromptService.list(keyword, page, size));
+    }
+
+    @Operation(summary = "新增/编辑提示词")
+    @PostMapping("/save")
+    public Result<SystemPrompt> save(@RequestBody SystemPrompt prompt,
+                                     @RequestAttribute(value = "role", required = false) String role,
+                                     @RequestAttribute(value = "userId", required = false) Long userId) {
+        if (prompt.getPromptKey() == null || prompt.getPromptKey().isEmpty()) {
+            return Result.error("promptKey不能为空");
+        }
+        if (prompt.getTitle() == null || prompt.getTitle().isEmpty()) {
+            return Result.error("标题不能为空");
+        }
+        if (prompt.getContent() == null || prompt.getContent().isEmpty()) {
+            return Result.error("内容不能为空");
+        }
+        String operator = "admin" + (userId != null ? "_" + userId : "");
+        // 新增时设置 default_content
+        if (prompt.getId() == null) {
+            prompt.setDefaultContent(prompt.getContent());
+        }
+        return Result.success(systemPromptService.savePrompt(prompt, operator));
+    }
+
+    @Operation(summary = "重置为默认内容")
+    @PostMapping("/reset")
+    public Result<SystemPrompt> reset(@RequestBody Map<String, Object> params) {
+        String promptKey = (String) params.get("promptKey");
+        if (promptKey == null || promptKey.isEmpty()) {
+            return Result.error("promptKey不能为空");
+        }
+        return Result.success(systemPromptService.resetToDefault(promptKey));
+    }
+
+    @Operation(summary = "删除提示词")
+    @PostMapping("/delete")
+    public Result delete(@RequestBody Map<String, Object> params) {
+        Long id = ParamUtils.getLong(params.get("id"));
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+        systemPromptService.removeById(id);
+        return Result.success();
+    }
+}

+ 47 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/publicapi/SystemPromptPublicController.java

@@ -0,0 +1,47 @@
+package com.etotem.cfc.controller.publicapi;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.SystemPrompt;
+import com.etotem.cfc.service.SystemPromptService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@Tag(name = "系统提示词公开查询", description = "供 LangGraph 动态加载提示词")
+@RestController
+@RequestMapping("/api/system-prompts")
+public class SystemPromptPublicController {
+
+    @Resource
+    private SystemPromptService systemPromptService;
+
+    @Operation(summary = "查询指定提示词(供 LangGraph 调用)", hidden = true)
+    @PostMapping("/get")
+    public Result<SystemPrompt> get(@RequestBody Map<String, Object> params) {
+        String promptKey = (String) params.get("promptKey");
+        if (promptKey == null || promptKey.isEmpty()) {
+            return Result.error("promptKey不能为空");
+        }
+        SystemPrompt prompt = systemPromptService.getByKey(promptKey);
+        if (prompt == null) {
+            return Result.error("提示词不存在或已禁用");
+        }
+        return Result.success(prompt);
+    }
+
+    @Operation(summary = "查询所有启用提示词(供 LangGraph 全量缓存)", hidden = true)
+    @PostMapping("/list")
+    public Result<List<SystemPrompt>> list() {
+        return Result.success(systemPromptService.list()
+                .stream()
+                .filter(p -> p.getStatus() != null && p.getStatus() == 1)
+                .collect(java.util.stream.Collectors.toList()));
+    }
+}

+ 37 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/SystemPrompt.java

@@ -0,0 +1,37 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("system_prompts")
+public class SystemPrompt implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String promptKey;
+
+    private String title;
+
+    private String content;
+
+    private String defaultContent;
+
+    private Integer version;
+
+    private Integer status;
+
+    private String remark;
+
+    private String updatedBy;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 7 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/SystemPromptMapper.java

@@ -0,0 +1,7 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.SystemPrompt;
+
+public interface SystemPromptMapper extends BaseMapper<SystemPrompt> {
+}

+ 17 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/SystemPromptService.java

@@ -0,0 +1,17 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.etotem.cfc.entity.SystemPrompt;
+
+import java.util.Map;
+
+public interface SystemPromptService extends IService<SystemPrompt> {
+
+    Map<String, Object> list(String keyword, int page, int size);
+
+    SystemPrompt getByKey(String promptKey);
+
+    SystemPrompt savePrompt(SystemPrompt prompt, String operator);
+
+    SystemPrompt resetToDefault(String promptKey);
+}

+ 68 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/impl/SystemPromptServiceImpl.java

@@ -0,0 +1,68 @@
+package com.etotem.cfc.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.etotem.cfc.entity.SystemPrompt;
+import com.etotem.cfc.mapper.SystemPromptMapper;
+import com.etotem.cfc.service.SystemPromptService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class SystemPromptServiceImpl extends ServiceImpl<SystemPromptMapper, SystemPrompt> implements SystemPromptService {
+
+    @Override
+    public Map<String, Object> list(String keyword, int page, int size) {
+        Page<SystemPrompt> p = new Page<>(page, size);
+        LambdaQueryWrapper<SystemPrompt> wrapper = new LambdaQueryWrapper<>();
+        if (keyword != null && !keyword.isEmpty()) {
+            wrapper.like(SystemPrompt::getTitle, keyword)
+                    .or().like(SystemPrompt::getPromptKey, keyword);
+        }
+        wrapper.orderByDesc(SystemPrompt::getUpdatedAt);
+        this.page(p, wrapper);
+        Map<String, Object> data = new HashMap<>();
+        data.put("records", p.getRecords());
+        data.put("total", p.getTotal());
+        data.put("page", p.getCurrent());
+        data.put("size", p.getSize());
+        return data;
+    }
+
+    @Override
+    public SystemPrompt getByKey(String promptKey) {
+        return this.getOne(new LambdaQueryWrapper<SystemPrompt>()
+                .eq(SystemPrompt::getPromptKey, promptKey)
+                .eq(SystemPrompt::getStatus, 1));
+    }
+
+    @Override
+    public SystemPrompt savePrompt(SystemPrompt prompt, String operator) {
+        if (prompt.getId() != null) {
+            prompt.setUpdatedBy(operator);
+            this.updateById(prompt);
+        } else {
+            prompt.setUpdatedBy(operator);
+            prompt.setVersion(1);
+            prompt.setStatus(1);
+            this.save(prompt);
+        }
+        return prompt;
+    }
+
+    @Override
+    public SystemPrompt resetToDefault(String promptKey) {
+        SystemPrompt prompt = this.getOne(new LambdaQueryWrapper<SystemPrompt>()
+                .eq(SystemPrompt::getPromptKey, promptKey));
+        if (prompt != null && prompt.getDefaultContent() != null) {
+            prompt.setContent(prompt.getDefaultContent());
+            prompt.setVersion(prompt.getVersion() + 1);
+            this.updateById(prompt);
+        }
+        return prompt;
+    }
+}

+ 123 - 0
cfc-langgraph/requirements_frozen.txt

@@ -0,0 +1,123 @@
+aiohappyeyeballs==2.7.1
+aiohttp==3.14.2
+aiosignal==1.4.0
+annotated-doc==0.0.4
+annotated-types==0.7.0
+anyio==4.14.2
+attrs==26.1.0
+bcrypt==5.0.0
+build==1.5.0
+certifi==2026.6.17
+charset-normalizer==3.4.9
+chromadb==1.5.9
+click==8.4.2
+dataclasses-json==0.6.7
+distro==1.9.0
+durationpy==0.10
+fastapi==0.139.2
+filelock==3.31.1
+flatbuffers==25.12.19
+frozenlist==1.8.0
+fsspec==2026.6.0
+googleapis-common-protos==1.75.0
+greenlet==3.5.3
+grpcio==1.82.1
+gunicorn==26.0.0
+h11==0.16.0
+hf-xet==1.5.2
+httpcore==1.0.9
+httptools==0.8.0
+httpx==0.28.1
+httpx-sse==0.4.3
+huggingface_hub==1.24.0
+idna==3.18
+importlib_resources==7.1.0
+iniconfig==2.3.0
+jiter==0.16.0
+jsonpatch==1.33
+jsonpointer==3.1.1
+jsonschema==4.26.0
+jsonschema-specifications==2025.9.1
+kubernetes==36.0.3
+langchain==0.3.30
+langchain-chroma==0.2.6
+langchain-classic==1.0.8
+langchain-community==0.3.31
+langchain-core==0.3.86
+langchain-openai==0.3.35
+langchain-protocol==0.0.18
+langchain-text-splitters==0.3.11
+langgraph==0.6.11
+langgraph-checkpoint==3.0.1
+langgraph-prebuilt==0.6.5
+langgraph-sdk==0.2.15
+langsmith==0.10.9
+markdown-it-py==4.2.0
+marshmallow==3.26.2
+mdurl==0.1.2
+mmh3==5.2.1
+multidict==6.7.1
+mypy_extensions==1.1.0
+numpy==2.5.1
+oauthlib==3.3.1
+onnxruntime==1.27.0
+openai==2.46.0
+opentelemetry-api==1.44.0
+opentelemetry-exporter-otlp-proto-common==1.44.0
+opentelemetry-exporter-otlp-proto-grpc==1.44.0
+opentelemetry-proto==1.44.0
+opentelemetry-sdk==1.44.0
+opentelemetry-semantic-conventions==0.65b0
+orjson==3.11.9
+ormsgpack==1.12.2
+overrides==7.7.0
+packaging==25.0
+pluggy==1.6.0
+prometheus_client==0.25.0
+propcache==0.5.2
+protobuf==7.35.1
+pybase64==1.4.3
+pydantic==2.13.4
+pydantic-settings==2.14.2
+pydantic_core==2.46.4
+Pygments==2.20.0
+PyPDF2==3.0.1
+PyPika==0.51.1
+pyproject_hooks==1.2.0
+pytest==8.4.2
+pytest-asyncio==1.4.0
+python-dateutil==2.9.0.post0
+python-dotenv==1.2.2
+python-multipart==0.0.32
+PyYAML==6.0.3
+referencing==0.37.0
+regex==2026.7.19
+requests==2.34.2
+requests-oauthlib==2.0.0
+requests-toolbelt==1.0.0
+rich==15.0.0
+rpds-py==2026.6.3
+sentence-transformers==5.7.0
+shellingham==1.5.4
+six==1.17.0
+sniffio==1.3.1
+SQLAlchemy==2.0.51
+starlette==1.3.1
+tenacity==9.1.4
+tiktoken==0.13.0
+tokenizers==0.23.1
+tqdm==4.69.0
+typer==0.27.0
+typing-inspect==0.9.0
+typing-inspection==0.4.2
+typing_extensions==4.16.0
+urllib3==2.7.0
+uuid_utils==0.17.0
+uvicorn==0.51.0
+uvloop==0.22.1
+watchfiles==1.2.0
+websocket-client==1.9.0
+websockets==15.0.1
+xxhash==3.8.1
+yarl==1.24.5
+zstandard==0.25.0

+ 34 - 0
cfc-web/src/api/systemPrompt.js

@@ -0,0 +1,34 @@
+import request from '@/utils/request'
+
+// 系统提示词管理 API
+export function getPromptList(params) {
+  return request({
+    url: '/api/admin/system-prompt/list',
+    method: 'post',
+    data: params
+  })
+}
+
+export function savePrompt(data) {
+  return request({
+    url: '/api/admin/system-prompt/save',
+    method: 'post',
+    data
+  })
+}
+
+export function resetPrompt(promptKey) {
+  return request({
+    url: '/api/admin/system-prompt/reset',
+    method: 'post',
+    data: { promptKey }
+  })
+}
+
+export function deletePrompt(id) {
+  return request({
+    url: '/api/admin/system-prompt/delete',
+    method: 'post',
+    data: { id }
+  })
+}

+ 186 - 0
cfc-web/src/views/admin/SystemPromptManagement.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="system-prompt-page">
+    <el-card>
+      <div slot="header" class="clearfix">
+        <span>系统提示词管理</span>
+        <div style="float:right; display:flex; gap:10px;">
+          <el-button size="mini" type="info" @click="fetchList">刷新</el-button>
+          <el-button size="mini" type="primary" @click="showEdit(null)">新增提示词</el-button>
+        </div>
+      </div>
+
+      <el-form :model="filters" inline size="small" style="margin-bottom:16px;">
+        <el-form-item label="关键词">
+          <el-input v-model="filters.keyword" placeholder="搜索标题或键名" clearable @input="fetchList" />
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="list" border stripe v-loading="loading" empty-text="暂无数据" style="width:100%">
+        <el-table-column prop="promptKey" label="提示词键" width="160" />
+        <el-table-column prop="title" label="标题" width="140" />
+        <el-table-column prop="content" label="内容" min-width="300" show-overflow-tooltip />
+        <el-table-column prop="version" label="版本" width="60" />
+        <el-table-column prop="status" label="状态" width="70">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
+              {{ row.status === 1 ? '启用' : '禁用' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="160">
+          <template slot-scope="{ row }">
+            {{ row.updatedAt ? formatTime(row.updatedAt) : '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="180" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button type="text" size="small" @click="showEdit(row)">编辑</el-button>
+            <el-button type="text" size="small" @click="showPreview(row)">预览</el-button>
+            <el-button type="text" size="small" @click="resetToDefault(row)">重置</el-button>
+            <el-button type="text" size="small" style="color:#f56c6c" @click="deleteRow(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        style="margin-top: 16px; text-align: right"
+        :current-page="page"
+        :page-size="size"
+        :total="total"
+        layout="total, prev, pager, next"
+        @current-change="handlePageChange"
+      />
+    </el-card>
+
+    <!-- 编辑弹窗 -->
+    <el-dialog :visible.sync="dialog.visible" :title="dialog.isEdit ? '编辑提示词' : '新增提示词'" width="700px" :close-on-click-modal="false">
+      <el-form :model="dialog.form" label-width="100px" size="small">
+        <el-form-item label="提示词键" :required="!dialog.isEdit">
+          <el-input v-model="dialog.form.promptKey" placeholder="如 health_coach(英文下划线)" :disabled="dialog.isEdit" />
+        </el-form-item>
+        <el-form-item label="标题" required>
+          <el-input v-model="dialog.form.title" placeholder="如 儿童健康教练" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="dialog.form.status">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="内容" required>
+          <el-input v-model="dialog.form.content" type="textarea" :rows="18" placeholder="输入系统提示词内容..." />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input v-model="dialog.form.remark" placeholder="用途说明" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button size="small" @click="dialog.visible = false">取消</el-button>
+        <el-button size="small" type="primary" :loading="saving" @click="savePrompt">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 预览弹窗 -->
+    <el-dialog title="提示词预览" :visible.sync="previewVisible" width="600px">
+      <el-card shadow="never" style="white-space: pre-wrap; font-family: monospace; font-size: 13px; line-height: 1.8; background: #fafafa;">
+        {{ previewContent }}
+      </el-card>
+      <div slot="footer">
+        <el-button size="small" @click="previewVisible = false">关闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getPromptList, savePrompt, resetPrompt, deletePrompt } from '@/api/systemPrompt'
+
+export default {
+  name: 'SystemPromptManagement',
+  data() {
+    return {
+      list: [],
+      loading: false,
+      filters: { keyword: '' },
+      page: 1,
+      size: 20,
+      total: 0,
+      dialog: { visible: false, isEdit: false, form: {} },
+      saving: false,
+      previewVisible: false,
+      previewContent: ''
+    }
+  },
+  created() {
+    this.fetchList()
+  },
+  methods: {
+    formatTime(ts) {
+      if (!ts) return ''
+      const d = new Date(ts)
+      return d.toLocaleString('zh-CN', { hour12: false })
+    },
+    async fetchList() {
+      this.loading = true
+      try {
+        const res = await getPromptList({ keyword: this.filters.keyword, page: this.page, size: this.size })
+        if (res && res.data) {
+          this.list = res.data.records || []
+          this.total = res.data.total || 0
+        }
+      } finally {
+        this.loading = false
+      }
+    },
+    showEdit(row) {
+      this.dialog.isEdit = !!row
+      this.dialog.form = row ? Object.assign({}, row) : { promptKey: '', title: '', content: '', status: 1, remark: '' }
+      this.dialog.visible = true
+    },
+    async savePrompt() {
+      const form = this.dialog.form
+      if (!form.promptKey || !form.title || !form.content) {
+        this.$message.warning('请填写完整')
+        return
+      }
+      this.saving = true
+      try {
+        await savePrompt(form)
+        this.$message.success('保存成功')
+        this.dialog.visible = false
+        this.fetchList()
+      } finally {
+        this.saving = false
+      }
+    },
+    async resetToDefault(row) {
+      this.$confirm('确定要重置为出厂默认内容吗?', '提示', { type: 'warning' }).then(async () => {
+        await resetPrompt(row.promptKey)
+        this.$message.success('已重置')
+        this.fetchList()
+      }).catch(() => {})
+    },
+    deleteRow(row) {
+      this.$confirm('确定要删除该提示词吗?', '提示', { type: 'warning' }).then(async () => {
+        await deletePrompt(row.id)
+        this.$message.success('删除成功')
+        this.fetchList()
+      }).catch(() => {})
+    },
+    showPreview(row) {
+      this.previewContent = row.content
+      this.previewVisible = true
+    },
+    handlePageChange(page) {
+      this.page = page
+      this.fetchList()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.system-prompt-page {
+  padding: 0;
+}
+</style>

+ 427 - 0
docs/superpowers/plans/2026-08-14-web-brand-direction.md

@@ -0,0 +1,427 @@
+# 官网首页品牌定位文案改造(方案 B)实现计划
+
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
+
+**目标:** 将 `web/index.html`(企业官网首页)的品牌定位话术从"AI 智慧家庭成长平台 / 一站式幸福提案"切换为已批准的新方向——slogan「一个人的主动,一家人的健康」+ 定位「家庭精准生活方式管理——从代谢到心理,帮一家人把身体和状态都调顺」,并把"代谢→心理"变成首页显性叙事主线。
+
+**架构:** 纯静态 HTML 文案替换。只改 `web/index.html` 一个文件的文本节点与 `<meta>`/JSON-LD 内容,**不动**任何布局、class、CSS、SVG、脚本、图片引用。所有 old→new 字符串在本计划内给出精确全文,执行时逐条替换并逐条 grep 验证。
+
+**技术栈:** 静态 HTML5 + 内联 SVG(nginx 直接服务,无构建步骤)。
+
+**执行方式:** 内联执行(当前会话)。理由:单文件纯文案替换,全部精确字符串已在计划中,子代理交接无增益且有串改风险。
+
+**Worktree 说明:** 本仓库 `web/` 为 nginx 直服的线上静态站点,前一 session 已建立直接在主工作树恢复该文件的模式,故不做 worktree 隔离。
+
+---
+
+## 变更总览(10 处替换 + 2 条 FAQ 新增 + JSON-LD 同步)
+
+| # | 区域 | 行号(当前) | 变更 |
+|---|------|------------|------|
+| 1 | head meta/og/title | 6-16 | 定位话术替换 |
+| 2 | head JSON-LD | 34, 40, 50 | SoftwareApplication/Organization/FAQ 首问 + 新增 2 问 |
+| 3 | Hero badge | 119 | 定位词替换 |
+| 4 | Hero 主标题 | 120-123 | slogan 落地 |
+| 5 | Hero 描述 | 124-129 | 代谢→心理主线 |
+| 6 | Hero CTA | 131 | 行动号召 |
+| 7 | About desc + 使命 | 163-165, 173 | 主张式表达 |
+| 8 | 概念区收尾 | 261 后 | 追加 slogan 收尾句 |
+| 9 | 产品区 desc + 精准健康卡 | 273-278, 456 | 闭环主线 |
+| 10 | 五维 subtitle + 身卡 | 501, 628 | 桥接语言 |
+| 11 | FAQ 首答 + 新增 2 问 | 1155-1156, 1197 后 | 新定位 + slogan 出处 |
+
+---
+
+### 任务 1:head meta / og / title 定位替换(行 6-16)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换 meta description(行 6)**
+
+old:
+```html
+  <meta name="description" content="浠艾福主动健康学院 — 浠艾福(Care Family)AI 智慧家庭成长平台,以五行智慧为根、AI 大模型为翼,围绕身·心·智·行·富五个维度,为每个家庭定制 AI 时代的全生命周期成长方案。">
+```
+new:
+```html
+  <meta name="description" content="浠艾福做的是家庭精准生活方式管理——从代谢到心理,帮一家人把身体和状态都调顺。一个人的主动,一家人的健康。">
+```
+
+- [ ] **步骤 2:替换 keywords(行 7)**
+
+old:
+```html
+  <meta name="keywords" content="浠艾福,Care Family,AI家庭教育,家庭成长,五维能量,亲子成长,五行相生相克">
+```
+new:
+```html
+  <meta name="keywords" content="浠艾福,Care Family,家庭健康管理,精准生活方式管理,代谢健康,心理健康,五维能量,主动健康">
+```
+
+- [ ] **步骤 3:替换 og:title(行 8)**
+
+old:
+```html
+  <meta property="og:title" content="浠艾福主动健康学院 — AI 智慧家庭成长平台">
+```
+new:
+```html
+  <meta property="og:title" content="浠艾福 · 一个人的主动,一家人的健康">
+```
+
+- [ ] **步骤 4:替换 og:description(行 9)**
+
+old:
+```html
+  <meta property="og:description" content="给全家的一站式幸福提案 — AI + 身·心·智·行·富 五维全景幸福矩阵 · 五行相生相克">
+```
+new:
+```html
+  <meta property="og:description" content="家庭精准生活方式管理——从代谢到心理,帮一家人把身体和状态都调顺。">
+```
+
+- [ ] **步骤 5:替换 title(行 16)**
+
+old:
+```html
+  <title>浠艾福主动健康学院 — 家庭幸福健康成长平台</title>
+```
+new:
+```html
+  <title>浠艾福 · 家庭精准生活方式管理平台</title>
+```
+
+- [ ] **步骤 6:验证本任务全部替换**
+
+运行:`grep -c '一站式幸福提案\|五行智慧为根\|AI 智慧家庭成长平台\|家庭幸福健康成长平台' web/index.html`
+预期:0(JSON-LD 行 40 Organization 描述含"智慧健康成长解决方案",属任务 2 范围,先忽略)
+
+---
+
+### 任务 2:head JSON-LD 同步(行 34, 40, 50)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换 SoftwareApplication description(行 34)**
+
+old:
+```json
+        "description": "倾力打造的 AI 智慧家庭成长平台,以五行智慧为根、AI 大模型为翼,围绕身·心·智·行·富五个维度,为每个家庭定制全生命周期成长方案。连接家庭客户、专业服务商与平台运营三方角色,整合菌群检测数据(纳入健康档案)、DAN测评数据(纳入成长档案)与家庭成员数据,以 AI 技术 + 专业人员指导为核心服务。"
+```
+new:
+```json
+        "description": "家庭精准生活方式管理平台——从代谢到心理,帮一家人把身体和状态都调顺。以主动健康为理念、AI 大模型为引擎,围绕身·智·富·行·心五个维度,为每个家庭定制全生命周期成长方案。连接家庭客户、专业服务商与平台运营三方角色,整合菌群检测数据(纳入健康档案)、DAN测评数据(纳入成长档案)与家庭成员数据,以 AI 技术 + 专业人员指导为核心服务。"
+```
+
+- [ ] **步骤 2:替换 Organization description(行 40)**
+
+old:
+```json
+        "description": "以 AI 科技为引擎,以家庭为中心,致力于为中国家庭提供一站式智慧健康成长解决方案的科技企业。"
+```
+new:
+```json
+        "description": "以家庭为中心,致力于为中国家庭提供精准生活方式管理服务的科技企业——从代谢到心理,帮一家人把身体和状态都调顺。"
+```
+
+- [ ] **步骤 3:替换 FAQPage 首问答案(行 50)**
+
+old:
+```json
+          {"@type":"Question","name":"浠艾福是什么平台?","acceptedAnswer":{"@type":"Answer","text":"浠艾福(Care Family)是以<strong>主动健康</strong>为理念、以家庭为单位的 AI 智慧成长平台。围绕身·智·富·行·心五大维度,连接家庭客户、专业服务商与平台运营三方角色,以五维能量体系驱动全家持续成长。不是单一的育儿工具,而是覆盖全生命周期的一站式幸福解决方案。"}},
+```
+new:
+```json
+          {"@type":"Question","name":"浠艾福是什么平台?","acceptedAnswer":{"@type":"Answer","text":"浠艾福(Care Family)是一家人的<strong>精准生活方式管理</strong>平台——从<strong>代谢</strong>到<strong>心理</strong>,帮一家人把身体和状态都调顺。以<strong>主动健康</strong>为理念,围绕身·智·富·行·心五大维度,连接家庭客户、专业服务商与平台运营三方角色,以 AI + 专业人员双轨驱动全家持续成长。"}},
+```
+
+- [ ] **步骤 4:新增 2 条 FAQ JSON-LD(行 50 之后、行 51 之前插入)**
+
+插入内容(紧跟在行 50 首问条目 `}},` 之后):
+```json
+          {"@type":"Question","name":"为什么说一个人的主动,一家人的健康?","acceptedAnswer":{"@type":"Answer","text":"家庭健康具有传导性:一个人先做出改变,饮食习惯、作息节奏、情绪氛围会自然影响其他成员。浠艾福让改变从一个人开始,让全家的身体和状态一起被调顺。"}},
+          {"@type":"Question","name":"什么是从代谢到心理?","acceptedAnswer":{"@type":"Answer","text":"代谢是身体的地基——肠道菌群、营养、睡眠、运动;心理是生活的上层——情绪、关系、认知。浠艾福以五维体系覆盖两端:先调代谢,再调状态,身心一体、互相成就。"}},
+```
+
+- [ ] **步骤 5:验证 JSON-LD 仍是合法 JSON**
+
+运行:
+```bash
+python3 -c "
+import re,json
+s=open('web/index.html',encoding='utf-8').read()
+for m in re.findall(r'<script type=\"application/ld\+json\">(.*?)</script>',s,re.S):
+    json.loads(m)
+print('JSON-LD OK')
+"
+```
+预期:`JSON-LD OK`
+
+---
+
+### 任务 3:Hero 区(行 119-133)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换 hero-badge(行 119)**
+
+old: `<span class="hero-badge">家庭幸福健康成长平台</span>`
+new: `<span class="hero-badge">家庭精准生活方式管理</span>`
+
+- [ ] **步骤 2:替换 hero 主标题(行 120-123)**
+
+old:
+```html
+        <h1 class="hero-title">
+          给全家的<br>
+          <span class="highlight">一站式幸福提案</span>
+        </h1>
+```
+new:
+```html
+        <h1 class="hero-title">
+          一个人的主动,<br>
+          <span class="highlight">一家人的健康。</span>
+        </h1>
+```
+
+- [ ] **步骤 3:替换 hero 描述(行 124-129)**
+
+old:
+```html
+        <p class="hero-desc">
+          以 <strong>主动健康</strong> 为核心理念,倾力打造——<strong>浠艾福(Care Family)</strong>家庭幸福健康成长俱乐部,
+          以 <strong>五行智慧为根</strong>,以 <strong>AI 大模型为翼</strong>,
+          围绕 <strong>身·智·富·行·心</strong> 五大核心领域,
+          为每个家庭定制 AI 时代的全生命周期成长方案。
+        </p>
+```
+new:
+```html
+        <p class="hero-desc">
+          从 <strong>代谢</strong> 到 <strong>心理</strong>,帮一家人把 <strong>身体和状态</strong> 都调顺。
+          浠艾福以 <strong>主动健康</strong> 为理念,围绕 <strong>身·智·富·行·心</strong> 五大维度,
+          为每个家庭定制 <strong>精准的生活方式管理方案</strong>。
+        </p>
+```
+
+- [ ] **步骤 4:替换 hero CTA(行 131)**
+
+old: `<a href="#product" class="btn btn-primary">了解浠艾福</a>`
+new: `<a href="#product" class="btn btn-primary">从一次主动开始</a>`
+
+- [ ] **步骤 5:验证 Hero 区**
+
+运行:`grep -c '给全家的\|一站式幸福提案\|五行智慧为根\|了解浠艾福' web/index.html`
+预期:0
+
+---
+
+### 任务 4:About 区(行 163-165, 173)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换 section-desc(行 163-165)**
+
+old:
+```html
+        <p class="section-desc">
+          以 <strong>主动健康</strong> 为核心理念,以 AI 科技为引擎,以家庭为中心,致力于为中国家庭提供一站式智慧健康成长解决方案。
+        </p>
+```
+new:
+```html
+        <p class="section-desc">
+          我们相信,<strong>一个人的主动,能带动一家人的健康</strong>。浠艾福以 AI 精准分析为引擎,从代谢到心理,为每个家庭提供可落地的生活方式管理方案——让身体和状态,一起调顺。
+        </p>
+```
+
+- [ ] **步骤 2:替换企业使命(行 173)**
+
+old: `<p>用 AI 赋能每个家庭,让健康与幸福成为每个家庭的生活方式。</p>`
+new: `<p>用精准生活方式管理,让一个人的主动,点亮一家人的健康。</p>`
+
+- [ ] **步骤 3:验证 About 区**
+
+运行:`grep -c '一站式智慧健康成长解决方案\|用 AI 赋能每个家庭' web/index.html`
+预期:0
+
+---
+
+### 任务 5:概念区收尾追加 slogan 句(行 261 之后)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:定位概念区收尾段**
+
+读取行 255-265 确认精确文本。当前预期结构:
+```html
+        <div class="feature-card">
+          ...
+        </div>
+      </div>
+      <!-- 概念收尾锚点:追加句 -->
+    </div>
+  </section>
+```
+在概念区内容容器(`</div>` + `</div>` 之间)追加:
+```html
+      <p class="section-tip">一个人的主动,一家人的健康——改变从你先开始。</p>
+```
+确认追加点前无遗漏(追加后应位于 `</section>` 之前、最后一个 feature-card 容器之后)。
+
+- [ ] **步骤 2:验证**
+
+运行:`grep -c '改变从你先开始' web/index.html`
+预期:1
+
+---
+
+### 任务 6:产品区(行 273-278, 456)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换产品区 section-desc(行 273-278)**
+
+old:
+```html
+        <p class="section-desc">
+          面向中国家庭的全场景智慧生活服务生态 —— 以主动健康为核心理念,融合专业服务、智能分析、成长记录,
+          为每个家庭构建完整的健康与成长闭环。
+        </p>
+```
+new:
+```html
+        <p class="section-desc">
+          面向中国家庭的精准生活方式管理生态 —— 以主动健康为核心理念,融合专业服务、智能分析、成长记录,
+          从代谢到心理,为每个家庭构建完整的健康与成长闭环。
+        </p>
+```
+
+- [ ] **步骤 2:替换精准健康管理卡 desc(行 456 附近)**
+
+old: `<p class="feature-desc">基于权威肠道菌群检测数据,个性化精准评估代谢健康状态,科学指导生活方式调整。</p>`
+new: `<p class="feature-desc">基于权威肠道菌群检测数据,个性化精准评估代谢健康状态,科学指导生活方式调整——代谢顺了,状态自然好。</p>`
+
+- [ ] **步骤 3:验证**
+
+运行:`grep -c '从代谢到心理,为每个家庭构建' web/index.html`
+预期:1(仅产品区 1 处,Hero 区是独立文案)
+
+---
+
+### 任务 7:五维体系区(行 501, 628)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换五维 subtitle(行 501)**
+
+old: `<p class="section-subtitle">身 · 智 · 富 · 行 · 心 —— 五行相生相克,五维互相成就</p>`
+new: `<p class="section-subtitle">身 · 智 · 富 · 行 · 心 —— 代谢是地基,心理是上层,五维互相成就</p>`
+
+- [ ] **步骤 2:替换"身"维度卡 desc(行 628)**
+
+old: `<p class="dim-desc">通过系统化的饮食营养、运动与生活方式管理,让身体回到自然健康的状态。</p>`
+new: `<p class="dim-desc">以精准的代谢管理为地基——从饮食营养、运动到生活方式,让身体回到自然健康的状态。</p>`
+
+- [ ] **步骤 3:验证**
+
+运行:`grep -c '五行相生相克\|代谢是地基,心理是上层' web/index.html`
+预期:0 / 1
+
+---
+
+### 任务 8:FAQ 区(行 1155-1156, 1197 之后)
+
+**文件:** `web/index.html`
+
+- [ ] **步骤 1:替换 FAQ 首答(行 1155-1156)**
+
+old:
+```html
+            <div class="faq-answer">
+              <p>浠艾福(Care Family)是以<strong>主动健康</strong>为理念、以家庭为单位的 AI 智慧成长平台。围绕身·智·富·行·心五大维度,连接家庭客户、专业服务商与平台运营三方角色,以五维能量体系驱动全家持续成长。不是单一的育儿工具,而是覆盖全生命周期的一站式幸福解决方案。</p>
+            </div>
+```
+new:
+```html
+            <div class="faq-answer">
+              <p>浠艾福(Care Family)是一家人的<strong>精准生活方式管理</strong>平台——从<strong>代谢</strong>到<strong>心理</strong>,帮一家人把身体和状态都调顺。以<strong>主动健康</strong>为理念,围绕身·智·富·行·心五大维度,连接家庭客户、专业服务商与平台运营三方角色,以 AI + 专业人员双轨驱动全家持续成长。</p>
+            </div>
+```
+
+- [ ] **步骤 2:新增第 10 条 FAQ(行 1197 `</div>` 之后、section 闭合前)**
+
+读取行 1185-1205 确认插入点。插入内容(新 item):
+```html
+            <div class="faq-item">
+              <div class="faq-question">为什么说「一个人的主动,一家人的健康」?<span class="faq-icon">+</span></div>
+              <div class="faq-answer">
+                <p>家庭健康具有传导性:一个人先做出改变,饮食习惯、作息节奏、情绪氛围会自然影响其他成员。浠艾福让改变从一个人开始,让全家的身体和状态一起被调顺。</p>
+              </div>
+            </div>
+            <div class="faq-item">
+              <div class="faq-question">「从代谢到心理」是什么意思?<span class="faq-icon">+</span></div>
+              <div class="faq-answer">
+                <p>代谢是身体的地基——肠道菌群、营养、睡眠、运动;心理是生活的上层——情绪、关系、认知。浠艾福以五维体系覆盖两端:先调代谢,再调状态,身心一体、互相成就。</p>
+              </div>
+            </div>
+```
+
+- [ ] **步骤 3:验证**
+
+运行:
+```bash
+grep -c 'faq-item' web/index.html
+```
+预期:HTML 中 12(10 条 + 至少 1 条模板引用,行 1043 附近可能有模板;以 grep 实际结果为准并人工核对)
+运行:`grep -c '一站式幸福解决方案' web/index.html`
+预期:0
+
+---
+
+### 任务 9:全站残留扫描与收尾验证
+
+- [ ] **步骤 1:全量残留扫描**
+
+```bash
+for kw in '一站式幸福' 'AI 智慧家庭' '五行智慧为根' '五行相生相克' '给全家的' '了解浠艾福' '全生命周期成长方案' 'AI 时代的全生命周期' '用 AI 赋能每个家庭' '智慧成长'; do
+  n=$(grep -c "$kw" web/index.html)
+  echo "$n  $kw"
+done
+```
+预期:除「全生命周期成长方案」(JSON-LD 任务 2 已改,正文行 123/273 已改)外全部为 0。若某行 >0,人工核对是误报(如 `AI 智慧家庭` 出现在历史/案例文案中则评估是否在本次范围,超出范围则记录不动)。
+
+- [ ] **步骤 2:最终语法验证**
+
+运行:
+```bash
+python3 -c "
+import re
+s=open('web/index.html',encoding='utf-8').read()
+# 标签配对粗查
+for t in ['section','div','h1','p']:
+    o=len(re.findall(r'<%s[\s>]'%t,s)); c=len(re.findall(r'</%s>'%t,s))
+    print(t,o,c)
+# JSON-LD 合法性
+import json
+for m in re.findall(r'<script type=\"application/ld\+json\">(.*?)</script>',s,re.S):
+    json.loads(m)
+print('JSON-LD OK')
+"
+```
+预期:div 开闭数允许不等(模板与注释干扰,人工判断),JSON-LD OK。
+
+- [ ] **步骤 3:git 变更确认**
+
+运行:`git -C /app/cfc status --porcelain -- web/index.html`
+预期:` M web/index.html`(仅此文件)
+
+---
+
+## 备注与边界
+
+- **不修改**:布局、class、CSS、SVG、脚本、图片、其他文件。
+- **保留**:`主动健康` 核心理念(新文案中以理念形式保留)、五维体系(`身·智·富·行·心` 顺序保持原序)。
+- **风险预案**:若某 old 字符串在文件中出现多次导致 edit 失败,先 `grep -n` 定位行号,用更长上下文精确定位后再替换;绝不使用 sed 全局替换。
+- **验证基线**:本任务为纯文案替换,无构建/测试命令;验证=grep 计数 + JSON-LD 解析 + git diff 审阅。

BIN
web/img/cert/cert-leveluk-endorsement.jpg


BIN
web/img/diagram_1_en.png


BIN
web/img/diagram_2_en.png


BIN
web/img/diagram_3_en.png


BIN
web/img/diagram_4_en.png


BIN
web/img/product-intro/gut-8dims-diagram.jpg


BIN
web/img/product-intro/gut-kit-5.jpg


BIN
web/img/product-intro/gut-kit-7.jpg


BIN
web/img/product-intro/gut-kit-cover.jpg


BIN
web/img/products/jr4.jpg


BIN
web/img/products/k8.jpg


BIN
web/img/products/sd501dx.jpg


BIN
web/img/products/super501.jpg


BIN
web/img/ref-10.png


BIN
web/img/ref-11.png


BIN
web/img/ref-12.png


BIN
web/img/ref-13.png


BIN
web/img/ref-14.png


BIN
web/img/ref-15.png


BIN
web/img/ref-16.png


BIN
web/img/ref-17.png


BIN
web/img/ref-18.png


BIN
web/img/ref-19.png


BIN
web/img/ref-20.png


BIN
web/img/ref-21.png


BIN
web/img/ref-3.png


BIN
web/img/ref-7.png


BIN
web/img/ref-9.png


BIN
web/img/ref-boiling-water.png


BIN
web/img/ref-heat-sunlight.png


BIN
web/img/ref-hot-water-antimony.png


BIN
web/img/ref-nejm.png


BIN
web/img/ref-ohio-state.png


BIN
web/img/ref-pet-sources.png


BIN
web/img/ref-pmc-2025-review.png


BIN
web/img/ref-pnas.png


BIN
web/img/ref-sciencedaily.png


BIN
web/img/ref-screenshots/chinas-dbps-spatial.jpg


BIN
web/img/ref-screenshots/enagic-comparison.svg


BIN
web/img/ref-screenshots/gut-microbiome-damage.jpg


BIN
web/img/ref-screenshots/pesticides-46-detection.jpg


BIN
web/img/ref-who-report.png


BIN
web/img/yan.png