diff --git a/cms/admin/src/App.tsx b/cms/admin/src/App.tsx
index 955a7e8..34b7716 100644
--- a/cms/admin/src/App.tsx
+++ b/cms/admin/src/App.tsx
@@ -3,19 +3,20 @@ import { useState, useEffect } from 'react'
const API_BASE = '' // 同域,相对路径
const SIDEBAR = [
{ key: 'config', label: '1 全局配置' },
- { key: 'home', label: '2 首页模块' },
- { key: 'ameba', label: '3 阿美巴页面' },
- { key: 'evolution', label: '4 企业进化' },
- { key: 'methods', label: '5 方法与能力' },
- { key: 'services', label: '6 产品服务' },
- { key: 'cases', label: '7 案例管理' },
- { key: 'articles', label: '8 认知观察' },
- { key: 'selfcheck', label: '9 自我对照' },
- { key: 'diag_q', label: '10 诊断问卷' },
- { key: 'diag_config', label: '11 诊断配置' },
- { key: 'diag_reports', label: '12 诊断记录' },
- { key: 'diag_leads', label: '13 留资统计' },
- { key: 'contact_leads', label: '14 咨询线索' },
+ { key: 'texts', label: '2 全站文案' },
+ { key: 'home', label: '3 首页模块' },
+ { key: 'ameba', label: '4 阿美巴页面' },
+ { key: 'evolution', label: '5 企业进化' },
+ { key: 'methods', label: '6 方法与能力' },
+ { key: 'services', label: '7 产品服务' },
+ { key: 'cases', label: '8 案例管理' },
+ { key: 'articles', label: '9 认知观察' },
+ { key: 'selfcheck', label: '10 自我对照' },
+ { key: 'diag_q', label: '11 诊断问卷' },
+ { key: 'diag_config', label: '12 诊断配置' },
+ { key: 'diag_reports', label: '13 诊断记录' },
+ { key: 'diag_leads', label: '14 留资统计' },
+ { key: 'contact_leads', label: '15 咨询线索' },
{ key: 'password', label: '⚙ 修改密码' },
]
@@ -66,6 +67,7 @@ export default function App(){
{SIDEBAR.find(s=>s.key===cur)?.label}
内容与布局解耦 · 只改内容不改架构
{cur==='config' &&
}
+ {cur==='texts' &&
}
{cur==='home' &&
}
{cur==='ameba' &&
}
{cur==='evolution' &&
}
@@ -85,6 +87,17 @@ export default function App(){
)
}
+function Texts({token}:{token:string}){
+ const [list,setList]=useState
([])
+ useEffect(()=>{api('/api/cms/texts',token).then(j=>setList(j.data||[]))},[])
+ const update=(i:number,key:string,value:any)=>{const n=[...list]; n[i]={...n[i],[key]:value}; setList(n)}
+ const save=()=>api('/api/cms/texts',token,{method:'PUT',body:JSON.stringify(list)}).then(j=>alert(j.msg||'保存成功'))
+ return 全站文案
这里的内容会覆盖首页、阿美巴页和全站行动区的默认文字。保存后刷新前台即可看到。
+ {list.map((it,i)=>
)}
+
+
+}
+
function ContactLeads({token}:{token:string}){
const [list,setList]=useState([])
useEffect(()=>{api('/api/cms/contact-leads',token).then(j=> setList(j.data||[]))},[])
@@ -164,7 +177,7 @@ function Methods({token}:{token:string}){
>
}
function Services({token}:{token:string}){
- const [list,setList]=useState([]); const [f,setF]=useState({})
+ const [list,setList]=useState([]); const [f,setF]=useState({status:'published',type:'custom'})
useEffect(()=>{api('/api/cms/services',token).then(j=> setList(j.data||[]))},[])
return <>
产品服务列表(上下架、排序、价格校验)
| 名称 | 类型 | 价格 | 状态 | 排序 | 操作 |
{list.map(s=> | {s.name} | {s.type} | {s.price||'-'} | {s.status} | {s.sort_weight} | |
)}
@@ -172,13 +185,13 @@ function Services({token}:{token:string}){
-
+
>
}
function Cases({token}:{token:string}){
- const [list,setList]=useState([]); const [f,setF]=useState({})
+ const [list,setList]=useState([]); const [f,setF]=useState({status:'published'})
useEffect(()=>{api('/api/cms/cases',token).then(j=> setList(j.data||[]))},[])
return <>
案例列表(9段插槽)
| 标题 | 行业 | 状态 | 排序 | 操作 |
{list.map(c=> | {c.title} | {c.industry} | {c.status} | {c.sort_weight} | |
)}
@@ -187,13 +200,13 @@ function Cases({token}:{token:string}){
{['original_state','goal','gap','root_cause','ameba_intervention','evolution_path','actions','phased_result','continuous_iteration'].map(k=>(
))}
-
+
>
}
function Articles({token}:{token:string}){
- const [list,setList]=useState([]); const [f,setF]=useState({})
+ const [list,setList]=useState([]); const [f,setF]=useState({status:'published'})
useEffect(()=>{api('/api/cms/articles',token).then(j=> setList(j.data||[]))},[])
return <>
文章列表
| 标题 | 分类 | 推荐权重 | 状态 | 操作 |
{list.map(a=> | {a.title} | {a.category} | {a.recommend_weight} | {a.status} | |
)}
@@ -202,7 +215,7 @@ function Articles({token}:{token:string}){
setF({...f,cover_url:url})} />
{f.cover_url &&
}
-
+
>
diff --git a/cms/server/.env.example b/cms/server/.env.example
new file mode 100644
index 0000000..ad47d71
--- /dev/null
+++ b/cms/server/.env.example
@@ -0,0 +1,15 @@
+NODE_ENV=production
+CMS_PORT=13001
+JWT_SECRET=replace-with-a-long-random-secret
+CMS_ADMIN_PASSWORD=change-on-first-login
+
+# MySQL 生产配置
+DB_HOST=127.0.0.1
+DB_PORT=33306
+DB_USER=admin
+DB_PASSWORD=replace-with-database-password
+DB_NAME=ameiba_cms
+DB_CONNECTION_LIMIT=10
+
+UPLOAD_DIR=/var/www/ameiba-cn-new/cms/server/uploads
+CORS_ORIGIN=https://www.ameiba.cn
diff --git a/cms/server/package-lock.json b/cms/server/package-lock.json
index e3e91cc..7c73c22 100644
--- a/cms/server/package-lock.json
+++ b/cms/server/package-lock.json
@@ -9,12 +9,12 @@
"version": "1.0.0",
"dependencies": {
"bcryptjs": "^2.4.3",
- "better-sqlite3": "^11.0.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
+ "mysql2": "^3.11.3",
"pdfkit": "^0.15.0"
}
},
@@ -27,6 +27,16 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/node": {
+ "version": "26.5.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.0.tgz",
+ "integrity": "sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~8.9.0"
+ }
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
@@ -83,6 +93,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
@@ -109,51 +128,6 @@
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT"
},
- "node_modules/better-sqlite3": {
- "version": "11.10.0",
- "resolved": "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
- "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "bindings": "^1.5.0",
- "prebuild-install": "^7.1.1"
- }
- },
- "node_modules/bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "license": "MIT",
- "dependencies": {
- "file-uri-to-path": "1.0.0"
- }
- },
- "node_modules/bl": {
- "version": "4.1.0",
- "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
- "license": "MIT",
- "dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
- "node_modules/bl/node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/body-parser": {
"version": "1.20.6",
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.6.tgz",
@@ -196,30 +170,6 @@
"pako": "~1.0.5"
}
},
- "node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
- }
- },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@ -299,12 +249,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "license": "ISC"
- },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz",
@@ -404,21 +348,6 @@
"ms": "2.0.0"
}
},
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
- "license": "MIT",
- "dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/deep-equal": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-2.2.3.tgz",
@@ -451,15 +380,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/deep-extend": {
- "version": "0.6.0",
- "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
- "license": "MIT",
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -513,15 +433,6 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dfa": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/dfa/-/dfa-1.2.0.tgz",
@@ -578,15 +489,6 @@
"node": ">= 0.8"
}
},
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
- "license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -652,15 +554,6 @@
"node": ">= 0.6"
}
},
- "node_modules/expand-template": {
- "version": "2.0.3",
- "resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz",
- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
- "license": "(MIT OR WTFPL)",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/express": {
"version": "4.22.2",
"resolved": "https://registry.npmmirror.com/express/-/express-4.22.2.tgz",
@@ -707,12 +600,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "license": "MIT"
- },
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz",
@@ -781,12 +668,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "license": "MIT"
- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
@@ -805,6 +686,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -842,12 +732,6 @@
"node": ">= 0.4"
}
},
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz",
- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
- "license": "MIT"
- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
@@ -955,38 +839,12 @@
"node": ">=0.10.0"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
- "node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "license": "ISC"
- },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -1130,6 +988,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz",
@@ -1358,6 +1222,27 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz",
+ "integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1427,18 +1312,6 @@
"node": ">= 0.6"
}
},
- "node_modules/mimic-response": {
- "version": "3.1.0",
- "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
@@ -1460,12 +1333,6 @@
"mkdirp": "bin/cmd.js"
}
},
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "license": "MIT"
- },
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
@@ -1491,11 +1358,54 @@
"node": ">= 6.0.0"
}
},
- "node_modules/napi-build-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
- "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
- "license": "MIT"
+ "node_modules/mysql2": {
+ "version": "3.24.4",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz",
+ "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.2",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.3",
+ "long": "^5.3.2",
+ "lru.min": "^1.1.4",
+ "named-placeholders": "^1.1.6",
+ "sql-escaper": "^1.5.1"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 8"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
},
"node_modules/negotiator": {
"version": "0.6.3",
@@ -1506,18 +1416,6 @@
"node": ">= 0.6"
}
},
- "node_modules/node-abi": {
- "version": "3.96.0",
- "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.96.0.tgz",
- "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
- "license": "MIT",
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
@@ -1596,15 +1494,6 @@
"node": ">= 0.8"
}
},
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
@@ -1656,32 +1545,6 @@
"node": ">= 0.4"
}
},
- "node_modules/prebuild-install": {
- "version": "7.1.2",
- "resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.2.tgz",
- "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
- "license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.0",
- "expand-template": "^2.0.3",
- "github-from-package": "0.0.0",
- "minimist": "^1.2.3",
- "mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^1.0.1",
- "node-abi": "^3.3.0",
- "pump": "^3.0.0",
- "rc": "^1.2.7",
- "simple-get": "^4.0.0",
- "tar-fs": "^2.0.0",
- "tunnel-agent": "^0.6.0"
- },
- "bin": {
- "prebuild-install": "bin.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -1701,16 +1564,6 @@
"node": ">= 0.10"
}
},
- "node_modules/pump": {
- "version": "3.0.4",
- "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz",
- "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"node_modules/qs": {
"version": "6.15.3",
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.3.tgz",
@@ -1751,21 +1604,6 @@
"node": ">= 0.8"
}
},
- "node_modules/rc": {
- "version": "1.2.8",
- "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz",
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
- "dependencies": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- },
- "bin": {
- "rc": "cli.js"
- }
- },
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -2029,49 +1867,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/simple-concat": {
- "version": "1.0.1",
- "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz",
- "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "node_modules/sql-escaper": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz",
+ "integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==",
"license": "MIT",
- "dependencies": {
- "decompress-response": "^6.0.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=2.0.0",
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
}
},
"node_modules/statuses": {
@@ -2119,57 +1927,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
- "node_modules/strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/tar-fs": {
- "version": "2.1.5",
- "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.5.tgz",
- "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
- "license": "MIT",
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- }
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "license": "MIT",
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tar-stream/node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
@@ -2191,18 +1948,6 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
- "node_modules/tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz",
@@ -2222,6 +1967,13 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
+ "node_modules/undici-types": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/unicode-properties": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/unicode-properties/-/unicode-properties-1.4.1.tgz",
@@ -2339,12 +2091,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
diff --git a/cms/server/package.json b/cms/server/package.json
index 0763f68..a8acde7 100644
--- a/cms/server/package.json
+++ b/cms/server/package.json
@@ -1,7 +1,7 @@
{
"name": "ameba-cms-server",
"version": "1.0.0",
- "description": "阿美巴传统官网1.0 CMS后台 - 轻量SQLite方案",
+ "description": "阿美巴传统官网 CMS 服务",
"type": "module",
"scripts": {
"dev": "node src/index.js",
@@ -15,7 +15,7 @@
"multer": "^1.4.5-lts.1",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
- "better-sqlite3": "^11.0.0",
+ "mysql2": "^3.11.3",
"dotenv": "^16.4.5",
"pdfkit": "^0.15.0"
},
diff --git a/cms/server/src/db.js b/cms/server/src/db.js
index befc603..cc076de 100644
--- a/cms/server/src/db.js
+++ b/cms/server/src/db.js
@@ -1,348 +1,72 @@
-import Database from 'better-sqlite3';
-import { fileURLToPath } from 'url';
+import mysql from 'mysql2/promise';
import path from 'path';
import fs from 'fs';
import 'dotenv/config';
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const DB_PATH = process.env.CMS_DB_PATH || path.join(__dirname, '../cms.db');
-const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, '../uploads');
+const DB_HOST = process.env.DB_HOST || '127.0.0.1';
+const DB_PORT = Number(process.env.DB_PORT || 33306);
+const DB_USER = process.env.DB_USER || 'admin';
+const DB_PASSWORD = process.env.DB_PASSWORD || '';
+const DB_NAME = process.env.DB_NAME || 'ameiba_cms';
+const UPLOAD_DIR = process.env.UPLOAD_DIR || path.resolve(process.cwd(), 'uploads');
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
+let pool;
-const db = new Database(DB_PATH);
-
-// 启用 WAL 模式提高性能
-db.pragma('journal_mode = WAL');
-
-export function initDB() {
- // 全局配置
- db.exec(`CREATE TABLE IF NOT EXISTS global_config (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- site_title TEXT,
- slogan TEXT,
- seo_title TEXT,
- seo_desc TEXT,
- seo_keywords TEXT,
- hotline TEXT,
- contact_email TEXT,
- contact_address TEXT,
- cta_text TEXT,
- cta_link TEXT,
- updated_at TEXT
- )`);
-
- // 首页板块
- db.exec(`CREATE TABLE IF NOT EXISTS home_sections (
- id TEXT PRIMARY KEY,
- key TEXT UNIQUE,
- title TEXT,
- subtitle TEXT,
- summary TEXT,
- visible INTEGER DEFAULT 1,
- sort_order INTEGER DEFAULT 0,
- updated_at TEXT
- )`);
-
- // 阿美巴页面
- db.exec(`CREATE TABLE IF NOT EXISTS ameba_page (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- brand_position TEXT,
- mission TEXT,
- philosophy TEXT,
- practice TEXT,
- updated_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS experts (
- id TEXT PRIMARY KEY,
- name TEXT NOT NULL,
- title TEXT,
- bio TEXT,
- avatar_url TEXT,
- sort_weight INTEGER DEFAULT 0,
- status TEXT DEFAULT 'published',
- created_at TEXT,
- updated_at TEXT
- )`);
-
- // 企业进化
- db.exec(`CREATE TABLE IF NOT EXISTS evolution_thought (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- content TEXT,
- updated_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS four_values (
- id TEXT PRIMARY KEY,
- key TEXT UNIQUE,
- name TEXT,
- definition TEXT,
- description TEXT,
- image_url TEXT,
- sort_order INTEGER DEFAULT 0,
- status TEXT DEFAULT 'published',
- updated_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS amb_states (
- id TEXT PRIMARY KEY,
- key TEXT UNIQUE,
- name TEXT,
- definition TEXT,
- description TEXT,
- image_url TEXT,
- sort_order INTEGER DEFAULT 0,
- fixed_note TEXT,
- status TEXT DEFAULT 'published',
- updated_at TEXT
- )`);
-
- // 方法与能力
- db.exec(`CREATE TABLE IF NOT EXISTS evolution_process (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- steps TEXT,
- scenarios TEXT,
- description TEXT,
- updated_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS ten_systems (
- id TEXT PRIMARY KEY,
- name TEXT,
- description TEXT,
- sort_order INTEGER DEFAULT 0,
- status TEXT DEFAULT 'published',
- updated_at TEXT
- )`);
-
- // 产品服务
- db.exec(`CREATE TABLE IF NOT EXISTS services (
- id TEXT PRIMARY KEY,
- slug TEXT UNIQUE,
- name TEXT,
- stage TEXT,
- intro TEXT,
- service_content TEXT,
- delivery TEXT,
- suitable TEXT,
- price REAL,
- price_unit TEXT,
- advantage TEXT,
- detail_html TEXT,
- status TEXT DEFAULT 'draft',
- sort_weight INTEGER DEFAULT 0,
- type TEXT DEFAULT 'custom',
- created_at TEXT,
- updated_at TEXT
- )`);
-
- // 案例 9段
- db.exec(`CREATE TABLE IF NOT EXISTS cases (
- id TEXT PRIMARY KEY,
- slug TEXT UNIQUE,
- industry TEXT,
- scale TEXT,
- amb_tags TEXT,
- value_tags TEXT,
- title TEXT,
- cover_url TEXT,
- original_state TEXT,
- goal TEXT,
- gap TEXT,
- root_cause TEXT,
- ameba_intervention TEXT,
- evolution_path TEXT,
- actions TEXT,
- phased_result TEXT,
- continuous_iteration TEXT,
- publish_time TEXT,
- status TEXT DEFAULT 'draft',
- sort_weight INTEGER DEFAULT 0,
- created_at TEXT,
- updated_at TEXT
- )`);
-
- // 认知观察
- db.exec(`CREATE TABLE IF NOT EXISTS articles (
- id TEXT PRIMARY KEY,
- slug TEXT UNIQUE,
- title TEXT,
- subtitle TEXT,
- category TEXT,
- cover_url TEXT,
- content_html TEXT,
- publish_time TEXT,
- recommend_weight INTEGER DEFAULT 0,
- status TEXT DEFAULT 'draft',
- created_at TEXT,
- updated_at TEXT
- )`);
-
- // 轻量自我对照
- db.exec(`CREATE TABLE IF NOT EXISTS self_check (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- guide TEXT,
- questions TEXT,
- logic_text TEXT,
- next_guide TEXT,
- updated_at TEXT
- )`);
-
- // 诊断问卷题目
- db.exec(`CREATE TABLE IF NOT EXISTS diagnosis_questions (
- id TEXT PRIMARY KEY,
- group_key TEXT,
- group_name TEXT,
- type TEXT,
- title TEXT,
- options TEXT,
- score_config TEXT,
- sort_order INTEGER DEFAULT 0,
- status TEXT DEFAULT 'published',
- skip_logic TEXT,
- created_at TEXT,
- updated_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS diagnosis_sessions (
- id TEXT PRIMARY KEY,
- token TEXT UNIQUE,
- answers TEXT,
- progress TEXT,
- created_at TEXT,
- updated_at TEXT,
- completed_at TEXT,
- ip TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS diagnosis_reports (
- id TEXT PRIMARY KEY,
- session_id TEXT,
- token TEXT UNIQUE,
- share_token TEXT UNIQUE,
- scores TEXT,
- amb TEXT,
- gap TEXT,
- root_cause TEXT,
- path_suggestion TEXT,
- expires_at TEXT,
- created_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS diagnosis_leads (
- id TEXT PRIMARY KEY,
- session_id TEXT,
- report_id TEXT,
- name TEXT,
- company TEXT,
- phone TEXT,
- created_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS contact_leads (
- id TEXT PRIMARY KEY,
- name TEXT NOT NULL,
- company TEXT,
- phone TEXT NOT NULL,
- need TEXT,
- created_at TEXT
- )`);
-
- db.exec(`CREATE TABLE IF NOT EXISTS diagnosis_config (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- weights TEXT,
- thresholds TEXT,
- templates TEXT,
- share_expire_days INTEGER DEFAULT 30,
- updated_at TEXT
- )`);
-
- // 管理员
- db.exec(`CREATE TABLE IF NOT EXISTS admins (
- id TEXT PRIMARY KEY,
- username TEXT UNIQUE,
- password_hash TEXT,
- created_at TEXT
- )`);
+async function ensureDatabase() {
+ const connection = await mysql.createConnection({ host: DB_HOST, port: DB_PORT, user: DB_USER, password: DB_PASSWORD, charset: 'utf8mb4' });
+ await connection.query(`CREATE DATABASE IF NOT EXISTS \`${DB_NAME.replace(/`/g, '')}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
+ await connection.end();
}
-// 创建兼容 sqlite3 回调风格的包装器
+function mysqlSchema(sql) {
+ return sql.replace(/INTEGER PRIMARY KEY CHECK \(id = 1\)/gi, 'INT PRIMARY KEY')
+ .replace(/TEXT PRIMARY KEY/gi, 'VARCHAR(191) PRIMARY KEY')
+ .replace(/TEXT UNIQUE/gi, 'VARCHAR(191) UNIQUE')
+ .replace(/\bTEXT\b/gi, 'LONGTEXT').replace(/LONGTEXT UNIQUE/gi, 'VARCHAR(191) UNIQUE')
+ .replace(/LONGTEXT DEFAULT '([^']*)'/gi, "VARCHAR(191) DEFAULT '$1'")
+ .replace(/\bREAL\b/gi, 'DECIMAL(18,4)').replace(/\bINTEGER\b/gi, 'INT');
+}
+
+export async function initDB() {
+ await ensureDatabase();
+ pool = mysql.createPool({ host: DB_HOST, port: DB_PORT, user: DB_USER, password: DB_PASSWORD, database: DB_NAME, charset: 'utf8mb4', waitForConnections: true, connectionLimit: Number(process.env.DB_CONNECTION_LIMIT || 10), timezone: 'Z' });
+ const tables = [
+ `CREATE TABLE IF NOT EXISTS global_config (id INTEGER PRIMARY KEY CHECK (id = 1), site_title TEXT, slogan TEXT, seo_title TEXT, seo_desc TEXT, seo_keywords TEXT, hotline TEXT, contact_email TEXT, contact_address TEXT, cta_text TEXT, cta_link TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS home_sections (id TEXT PRIMARY KEY, \`key\` TEXT UNIQUE, title TEXT, subtitle TEXT, summary TEXT, visible INTEGER DEFAULT 1, sort_order INTEGER DEFAULT 0, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS site_texts (id VARCHAR(191) PRIMARY KEY, label TEXT, value LONGTEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS ameba_page (id INTEGER PRIMARY KEY CHECK (id = 1), brand_position TEXT, mission TEXT, philosophy TEXT, practice TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS experts (id TEXT PRIMARY KEY, name TEXT NOT NULL, title TEXT, bio TEXT, avatar_url TEXT, sort_weight INTEGER DEFAULT 0, status TEXT DEFAULT 'published', created_at TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS evolution_thought (id INTEGER PRIMARY KEY CHECK (id = 1), content TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS four_values (id TEXT PRIMARY KEY, \`key\` TEXT UNIQUE, name TEXT, definition TEXT, description TEXT, image_url TEXT, sort_order INTEGER DEFAULT 0, status TEXT DEFAULT 'published', updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS amb_states (id TEXT PRIMARY KEY, \`key\` TEXT UNIQUE, name TEXT, definition TEXT, description TEXT, image_url TEXT, sort_order INTEGER DEFAULT 0, fixed_note TEXT, status TEXT DEFAULT 'published', updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS evolution_process (id INTEGER PRIMARY KEY CHECK (id = 1), steps TEXT, scenarios TEXT, description TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS ten_systems (id TEXT PRIMARY KEY, name TEXT, description TEXT, sort_order INTEGER DEFAULT 0, status TEXT DEFAULT 'published', updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS services (id TEXT PRIMARY KEY, slug TEXT UNIQUE, name TEXT, stage TEXT, intro TEXT, service_content TEXT, delivery TEXT, suitable TEXT, price DECIMAL(18,4), price_unit TEXT, advantage TEXT, detail_html TEXT, status TEXT DEFAULT 'draft', sort_weight INTEGER DEFAULT 0, type TEXT DEFAULT 'custom', created_at TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS cases (id TEXT PRIMARY KEY, slug TEXT UNIQUE, industry TEXT, scale TEXT, amb_tags TEXT, value_tags TEXT, title TEXT, cover_url TEXT, original_state TEXT, goal TEXT, gap TEXT, root_cause TEXT, ameba_intervention TEXT, evolution_path TEXT, actions TEXT, phased_result TEXT, continuous_iteration TEXT, publish_time TEXT, status TEXT DEFAULT 'draft', sort_weight INTEGER DEFAULT 0, created_at TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS articles (id TEXT PRIMARY KEY, slug TEXT UNIQUE, title TEXT, subtitle TEXT, category TEXT, cover_url TEXT, content_html TEXT, publish_time TEXT, recommend_weight INTEGER DEFAULT 0, status TEXT DEFAULT 'draft', created_at TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS self_check (id INTEGER PRIMARY KEY CHECK (id = 1), guide TEXT, questions TEXT, logic_text TEXT, next_guide TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS diagnosis_questions (id TEXT PRIMARY KEY, group_key TEXT, group_name TEXT, type TEXT, title TEXT, options TEXT, score_config TEXT, sort_order INTEGER DEFAULT 0, status TEXT DEFAULT 'published', skip_logic TEXT, created_at TEXT, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS diagnosis_sessions (id TEXT PRIMARY KEY, token TEXT UNIQUE, answers TEXT, progress TEXT, created_at TEXT, updated_at TEXT, completed_at TEXT, ip TEXT)`,
+ `CREATE TABLE IF NOT EXISTS diagnosis_reports (id TEXT PRIMARY KEY, session_id TEXT, token TEXT UNIQUE, share_token TEXT UNIQUE, scores TEXT, amb TEXT, gap TEXT, root_cause TEXT, path_suggestion TEXT, expires_at TEXT, created_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS diagnosis_leads (id TEXT PRIMARY KEY, session_id TEXT, report_id TEXT, name TEXT, company TEXT, phone TEXT, created_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS contact_leads (id TEXT PRIMARY KEY, name TEXT NOT NULL, company TEXT, phone TEXT NOT NULL, need TEXT, created_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS diagnosis_config (id INTEGER PRIMARY KEY CHECK (id = 1), weights TEXT, thresholds TEXT, templates TEXT, share_expire_days INTEGER DEFAULT 30, updated_at TEXT)`,
+ `CREATE TABLE IF NOT EXISTS admins (id TEXT PRIMARY KEY, username TEXT UNIQUE, password_hash TEXT, created_at TEXT)`,
+ ];
+ for (const table of tables) await pool.query(mysqlSchema(table));
+}
+
+function normalizeSql(sql) { return sql.replace(/INSERT OR IGNORE/gi, 'INSERT IGNORE'); }
+
export function getDB() {
+ if (!pool) throw new Error('Database is not initialized');
return {
- // 同步查询单行
- get(sql, params, callback) {
- try {
- if (typeof params === 'function') {
- callback = params;
- params = [];
- }
- const row = db.prepare(sql).get(...(Array.isArray(params) ? params : []));
- callback(null, row || null);
- } catch (err) {
- callback(err);
- }
- },
-
- // 同步查询所有行
- all(sql, params, callback) {
- try {
- if (typeof params === 'function') {
- callback = params;
- params = [];
- }
- const rows = db.prepare(sql).all(...(Array.isArray(params) ? params : []));
- callback(null, rows);
- } catch (err) {
- callback(err);
- }
- },
-
- // 同步执行
- run(sql, params, callback) {
- try {
- if (typeof params === 'function') {
- callback = params;
- params = [];
- }
- const stmt = db.prepare(sql);
- const result = stmt.run(...(Array.isArray(params) ? params : []));
- // 绑定 this 上下文
- const context = {
- lastID: result.lastInsertRowid,
- changes: result.changes
- };
- if (callback) callback.call(context, null);
- return context;
- } catch (err) {
- if (callback) callback(err);
- throw err;
- }
- },
-
- // 序列化执行(兼容性)
- serialize(callback) {
- if (callback) callback();
- },
-
- // 准备语句
- prepare(sql) {
- const stmt = db.prepare(sql);
- return {
- run(...args) {
- const callback = args.pop();
- try {
- const result = stmt.run(...args);
- callback.call({ lastID: result.lastInsertRowid, changes: result.changes }, null);
- } catch (err) {
- callback(err);
- }
- },
- finalize(callback) {
- if (callback) callback();
- }
- };
- }
+ get(sql, params, callback) { if (typeof params === 'function') { callback = params; params = []; } pool.query(normalizeSql(sql), params || []).then(([rows]) => callback(null, rows[0] || null)).catch(callback); },
+ all(sql, params, callback) { if (typeof params === 'function') { callback = params; params = []; } pool.query(normalizeSql(sql), params || []).then(([rows]) => callback(null, rows)).catch(callback); },
+ run(sql, params, callback) { if (typeof params === 'function') { callback = params; params = []; } return pool.query(normalizeSql(sql), params || []).then(([result]) => { const context = { lastID: result.insertId, changes: result.affectedRows }; if (callback) callback.call(context, null); return context; }).catch(err => { if (callback) callback(err); else throw err; }); },
+ serialize(callback) { if (callback) callback(); },
+ prepare(sql) { return { run(...args) { const callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; return pool.query(normalizeSql(sql), args).then(([result]) => { if (callback) callback.call({ lastID: result.insertId, changes: result.affectedRows }, null); }).catch(err => callback && callback(err)); }, finalize(callback) { if (callback) callback(); } }; },
};
}
-export { DB_PATH, UPLOAD_DIR };
+export { UPLOAD_DIR, DB_HOST, DB_PORT, DB_NAME };
diff --git a/cms/server/src/export-sqlite.js b/cms/server/src/export-sqlite.js
new file mode 100644
index 0000000..bd20810
--- /dev/null
+++ b/cms/server/src/export-sqlite.js
@@ -0,0 +1,11 @@
+import Database from 'better-sqlite3';
+import fs from 'fs';
+
+const sqlite = new Database(process.env.SQLITE_DB_PATH || './cms.db', { readonly: true });
+const output = process.env.SQLITE_EXPORT_PATH || './sqlite-export.json';
+const tables = ['global_config', 'home_sections', 'site_texts', 'ameba_page', 'experts', 'evolution_thought', 'four_values', 'amb_states', 'evolution_process', 'ten_systems', 'services', 'cases', 'articles', 'self_check', 'diagnosis_questions', 'diagnosis_sessions', 'diagnosis_reports', 'diagnosis_leads', 'contact_leads', 'diagnosis_config', 'admins'];
+const data = {};
+for (const table of tables) { try { data[table] = sqlite.prepare(`SELECT * FROM ${table}`).all(); } catch { data[table] = []; } }
+fs.writeFileSync(output, JSON.stringify(data));
+sqlite.close();
+console.log(`SQLite export complete: ${output}`);
diff --git a/cms/server/src/import-mysql.js b/cms/server/src/import-mysql.js
new file mode 100644
index 0000000..9709fe8
--- /dev/null
+++ b/cms/server/src/import-mysql.js
@@ -0,0 +1,19 @@
+import mysql from 'mysql2/promise';
+import fs from 'fs';
+import 'dotenv/config';
+
+const input = process.env.SQLITE_EXPORT_PATH || './sqlite-export.json';
+const data = JSON.parse(fs.readFileSync(input, 'utf8'));
+const connection = await mysql.createConnection({ host: process.env.DB_HOST || '127.0.0.1', port: Number(process.env.DB_PORT || 33306), user: process.env.DB_USER || 'admin', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'ameiba_cms', charset: 'utf8mb4' });
+for (const [table, rows] of Object.entries(data)) {
+ for (const row of rows) {
+ const columns = Object.keys(row);
+ const escaped = columns.map(c => `\`${c}\``).join(',');
+ const values = columns.map(() => '?').join(',');
+ const updates = columns.filter(c => c !== 'id').map(c => `\`${c}\`=VALUES(\`${c}\`)`).join(',');
+ await connection.query(`INSERT INTO \`${table}\` (${escaped}) VALUES (${values}) ON DUPLICATE KEY UPDATE ${updates || '`id`=VALUES(`id`)'}`, columns.map(c => row[c]));
+ }
+ console.log(`${table}: ${(rows || []).length} rows`);
+}
+await connection.end();
+console.log('MySQL import complete');
diff --git a/cms/server/src/index.js b/cms/server/src/index.js
index 75412a6..33e803c 100644
--- a/cms/server/src/index.js
+++ b/cms/server/src/index.js
@@ -21,6 +21,7 @@ const db = getDB();
app.use(cors({ origin: process.env.CORS_ORIGIN || true }));
app.use(express.json({ limit: '5mb' }));
app.use(express.urlencoded({ extended: true }));
+app.use((req, res, next) => { res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); next(); });
app.use(authMiddleware);
// 静态:上传文件
@@ -139,6 +140,8 @@ app.post('/api/contact/leads', (req, res) => {
// 通用工具
const nowISO = () => new Date().toISOString();
const genId = (p) => `${p}-${Date.now()}-${Math.random().toString(36).slice(2,6)}`;
+const queryOne = (sql, params=[]) => new Promise((resolve, reject) => db.get(sql, params, (err, row) => err ? reject(err) : resolve(row || null)));
+const queryAll = (sql, params=[]) => new Promise((resolve, reject) => db.all(sql, params, (err, rows) => err ? reject(err) : resolve(rows || [])));
// 全局配置
app.get('/api/cms/config', (req, res) => {
@@ -153,6 +156,16 @@ app.put('/api/cms/config', (req, res) => {
});
});
+// 可配置站点文案。页面只读取公开聚合中的 texts,后台通过此处集中维护。
+app.get('/api/cms/texts', (req, res) => db.all(`SELECT * FROM site_texts ORDER BY id`, (e, rows) => res.json({ code: 0, data: rows || [] })));
+app.put('/api/cms/texts', (req, res) => {
+ const items = Array.isArray(req.body) ? req.body : [];
+ const stmt = db.prepare(`INSERT INTO site_texts (id, label, value, updated_at) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE label=VALUES(label), value=VALUES(value), updated_at=VALUES(updated_at)`);
+ Promise.all(items.filter(it => it?.id).map(it => stmt.run(it.id, it.label || it.id, it.value || '', nowISO())))
+ .then(() => stmt.finalize(() => res.json({ code: 0, msg: '保存成功' })))
+ .catch(err => res.status(500).json({ code: 500, msg: err.message }));
+});
+
// 首页板块
app.get('/api/cms/home-sections', (req, res) => {
db.all(`SELECT * FROM home_sections ORDER BY sort_order DESC`, (err, rows) => res.json({code:0, data: rows}));
@@ -192,12 +205,12 @@ app.get('/api/cms/four-values', (req,res)=> db.all(`SELECT * FROM four_values OR
app.post('/api/cms/four-values', (req,res)=>{
const b=req.body; if(!b.key||!b.name) return res.status(400).json({code:400,msg:'key和名称必填'});
const id=genId('fv');
- db.run(`INSERT INTO four_values (id, key, name, definition, description, image_url, sort_order, status, updated_at) VALUES (?,?,?,?,?,?,?,?,?)`,
+ db.run(`INSERT INTO four_values (id, \`key\`, name, definition, description, image_url, sort_order, status, updated_at) VALUES (?,?,?,?,?,?,?,?,?)`,
[id,b.key,b.name,b.definition,b.description,b.image_url,b.sort_order||0,b.status||'published',nowISO()], e=> res.json({code:0,data:{id}}));
});
app.put('/api/cms/four-values/:id', (req,res)=>{
const b=req.body;
- db.run(`UPDATE four_values SET key=?, name=?, definition=?, description=?, image_url=?, sort_order=?, status=?, updated_at=? WHERE id=?`,
+ db.run(`UPDATE four_values SET \`key\`=?, name=?, definition=?, description=?, image_url=?, sort_order=?, status=?, updated_at=? WHERE id=?`,
[b.key,b.name,b.definition,b.description,b.image_url,b.sort_order,b.status,nowISO(),req.params.id], e=> res.json({code:0,msg:'更新成功'}));
});
app.delete('/api/cms/four-values/:id', (req,res)=> db.run(`DELETE FROM four_values WHERE id=?`,[req.params.id], e=> res.json({code:0,msg:'删除成功'})));
@@ -238,7 +251,7 @@ app.post('/api/cms/services', (req,res)=>{
const id=genId('svc');
const slug = b.slug || `svc-${Date.now()}`;
db.run(`INSERT INTO services (id, slug, name, stage, intro, service_content, delivery, suitable, price, price_unit, advantage, detail_html, status, sort_weight, type, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
- [id,slug,b.name,b.stage,b.intro,b.service_content,b.delivery,b.suitable,b.price?Number(b.price):null,b.price_unit,b.advantage,b.detail_html,b.status||'draft',b.sort_weight||0,b.type||'custom',nowISO(),nowISO()], e=> res.json({code:0,data:{id,slug}}));
+ [id,slug,b.name,b.stage,b.intro,b.service_content,b.delivery,b.suitable,b.price?Number(b.price):null,b.price_unit,b.advantage,b.detail_html,b.status||'published',b.sort_weight||0,b.type||'custom',nowISO(),nowISO()], e=> res.json({code:0,data:{id,slug}}));
});
app.put('/api/cms/services/:id', (req,res)=>{
const b=req.body;
@@ -256,7 +269,7 @@ app.post('/api/cms/cases', (req,res)=>{
const id=genId('case');
const slug = b.slug || `case-${Date.now()}`;
db.run(`INSERT INTO cases (id, slug, industry, scale, amb_tags, value_tags, title, cover_url, original_state, goal, gap, root_cause, ameba_intervention, evolution_path, actions, phased_result, continuous_iteration, publish_time, status, sort_weight, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
- [id,slug,b.industry,b.scale,JSON.stringify(b.amb_tags||[]),JSON.stringify(b.value_tags||[]),b.title,b.cover_url,b.original_state,b.goal,b.gap,b.root_cause,b.ameba_intervention,b.evolution_path,b.actions,b.phased_result,b.continuous_iteration,b.publish_time||nowISO(),b.status||'draft',b.sort_weight||0,nowISO(),nowISO()], e=> res.json({code:0,data:{id,slug}}));
+ [id,slug,b.industry,b.scale,JSON.stringify(b.amb_tags||[]),JSON.stringify(b.value_tags||[]),b.title,b.cover_url,b.original_state,b.goal,b.gap,b.root_cause,b.ameba_intervention,b.evolution_path,b.actions,b.phased_result,b.continuous_iteration,b.publish_time||nowISO(),b.status||'published',b.sort_weight||0,nowISO(),nowISO()], e=> res.json({code:0,data:{id,slug}}));
});
app.put('/api/cms/cases/:id', (req,res)=>{
const b=req.body;
@@ -273,7 +286,7 @@ app.post('/api/cms/articles', (req,res)=>{
const id=genId('art');
const slug = b.slug || `art-${Date.now()}`;
db.run(`INSERT INTO articles (id, slug, title, subtitle, category, cover_url, content_html, publish_time, recommend_weight, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`,
- [id,slug,b.title,b.subtitle,b.category,b.cover_url,b.content_html,b.publish_time||nowISO(),b.recommend_weight||0,b.status||'draft',nowISO(),nowISO()], e=> res.json({code:0,data:{id,slug}}));
+ [id,slug,b.title,b.subtitle,b.category,b.cover_url,b.content_html,b.publish_time||nowISO(),b.recommend_weight||0,b.status||'published',nowISO(),nowISO()], e=> res.json({code:0,data:{id,slug}}));
});
app.put('/api/cms/articles/:id', (req,res)=>{
const b=req.body;
@@ -292,25 +305,25 @@ app.put('/api/cms/self-check', (req,res)=>{
// 前端公开聚合接口(供前端一次性拉取,不需要鉴权,使用GET放行)
app.get('/api/cms/public', (req,res)=>{
- const data={};
- db.serialize(()=>{
- db.get(`SELECT * FROM global_config WHERE id=1`, (e,r)=> data.config=r);
- db.all(`SELECT * FROM home_sections WHERE visible=1 ORDER BY sort_order DESC`, (e,r)=> data.home_sections=r);
- db.get(`SELECT * FROM ameba_page WHERE id=1`, (e,r)=> data.ameba=r);
- db.all(`SELECT * FROM experts WHERE status='published' ORDER BY sort_weight DESC`, (e,r)=> data.experts=r);
- db.get(`SELECT * FROM evolution_thought WHERE id=1`, (e,r)=> data.evolution_thought=r);
- db.all(`SELECT * FROM four_values WHERE status='published' ORDER BY sort_order DESC`, (e,r)=> data.four_values=r);
- db.all(`SELECT * FROM amb_states WHERE status='published' ORDER BY sort_order DESC`, (e,r)=> data.amb_states=r);
- db.get(`SELECT * FROM evolution_process WHERE id=1`, (e,r)=> data.evolution_process=r);
- db.all(`SELECT * FROM ten_systems WHERE status='published' ORDER BY sort_order DESC`, (e,r)=> data.ten_systems=r);
- db.all(`SELECT * FROM services WHERE status='published' ORDER BY sort_weight DESC`, (e,r)=> data.services=r);
- db.all(`SELECT * FROM cases WHERE status='published' ORDER BY sort_weight DESC`, (e,r)=> data.cases=r);
- db.all(`SELECT * FROM articles WHERE status='published' ORDER BY recommend_weight DESC`, (e,r)=> data.articles=r);
- db.get(`SELECT * FROM self_check WHERE id=1`, (e,r)=> {
- data.self_check=r;
- res.json({code:0,data});
- });
- });
+ Promise.all([
+ queryOne(`SELECT * FROM global_config WHERE id=1`),
+ queryAll(`SELECT * FROM home_sections WHERE visible=1 ORDER BY sort_order DESC`),
+ queryOne(`SELECT * FROM ameba_page WHERE id=1`),
+ queryAll(`SELECT * FROM experts WHERE status='published' ORDER BY sort_weight DESC`),
+ queryOne(`SELECT * FROM evolution_thought WHERE id=1`),
+ queryAll(`SELECT * FROM four_values WHERE status='published' ORDER BY sort_order DESC`),
+ queryAll(`SELECT * FROM amb_states WHERE status='published' ORDER BY sort_order DESC`),
+ queryOne(`SELECT * FROM evolution_process WHERE id=1`),
+ queryAll(`SELECT * FROM ten_systems WHERE status='published' ORDER BY sort_order DESC`),
+ queryAll(`SELECT * FROM services WHERE status='published' ORDER BY sort_weight DESC`),
+ queryAll(`SELECT * FROM cases WHERE status='published' ORDER BY sort_weight DESC`),
+ queryAll(`SELECT * FROM articles WHERE status='published' ORDER BY recommend_weight DESC`),
+ queryOne(`SELECT * FROM self_check WHERE id=1`),
+ queryAll(`SELECT id, label, value FROM site_texts ORDER BY id`),
+ ]).then(([config, home_sections, ameba, experts, evolution_thought, four_values, amb_states, evolution_process, ten_systems, services, cases, articles, self_check, texts]) => {
+ const textMap = Object.fromEntries(texts.map(item => [item.id, item.value]));
+ res.json({ code: 0, data: { config, home_sections, ameba, experts, evolution_thought, four_values, amb_states, evolution_process, ten_systems, services, cases, articles, self_check, texts: textMap } });
+ }).catch(err => res.status(500).json({ code: 500, msg: err.message }));
});
// ================= 诊断系统 =================
diff --git a/cms/server/src/migrate-sqlite.js b/cms/server/src/migrate-sqlite.js
new file mode 100644
index 0000000..9588671
--- /dev/null
+++ b/cms/server/src/migrate-sqlite.js
@@ -0,0 +1,27 @@
+/* 一次性迁移工具:保留服务器原 SQLite 内容到 MySQL。
+ * 在服务器 npm install 新依赖前执行,SQLITE_DB_PATH 指向旧 cms.db。
+ */
+import Database from 'better-sqlite3';
+import mysql from 'mysql2/promise';
+import 'dotenv/config';
+
+const sqlitePath = process.env.SQLITE_DB_PATH || './cms.db';
+const sqlite = new Database(sqlitePath, { readonly: true });
+const mysqlConfig = { host: process.env.DB_HOST || '127.0.0.1', port: Number(process.env.DB_PORT || 33306), user: process.env.DB_USER || 'admin', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'ameiba_cms', charset: 'utf8mb4' };
+const connection = await mysql.createConnection(mysqlConfig);
+
+const tables = ['global_config', 'home_sections', 'site_texts', 'ameba_page', 'experts', 'evolution_thought', 'four_values', 'amb_states', 'evolution_process', 'ten_systems', 'services', 'cases', 'articles', 'self_check', 'diagnosis_questions', 'diagnosis_sessions', 'diagnosis_reports', 'diagnosis_leads', 'contact_leads', 'diagnosis_config', 'admins'];
+for (const table of tables) {
+ let rows = [];
+ try { rows = sqlite.prepare(`SELECT * FROM ${table}`).all(); } catch { continue; }
+ for (const row of rows) {
+ const columns = Object.keys(row);
+ const escaped = columns.map(c => `\`${c}\``).join(',');
+ const placeholders = columns.map(() => '?').join(',');
+ await connection.query(`INSERT IGNORE INTO \`${table}\` (${escaped}) VALUES (${placeholders})`, columns.map(c => row[c]));
+ }
+ console.log(`${table}: ${rows.length} rows`);
+}
+await connection.end();
+sqlite.close();
+console.log('SQLite -> MySQL migration complete');
diff --git a/cms/server/src/seed.js b/cms/server/src/seed.js
index b6b8712..c3933dd 100644
--- a/cms/server/src/seed.js
+++ b/cms/server/src/seed.js
@@ -16,7 +16,7 @@ db.run(`INSERT OR IGNORE INTO admins (id, username, password_hash, created_at) V
db.get(`SELECT id FROM global_config WHERE id=1`, (err, row) => {
if (!row) {
db.run(`INSERT INTO global_config (id, site_title, slogan, seo_title, seo_desc, seo_keywords, hotline, contact_email, contact_address, cta_text, cta_link, updated_at) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- ['阿美巴咨询|企业进化陪跑', '让企业进化被看见、被度量、被落地', '阿美巴咨询|企业进化陪跑 · 体系化增长落地', '以企业进化为主线,四大价值坐标与A/M/B状态坐标为观察系', '阿美巴,企业进化,四大价值,A/M/B', '400-XXX-XXXX', 'contact@aiameiba.com', '【占位】成都市', '咨询获取方案', '/contact', now]);
+ ['阿美巴咨询|企业进化陪跑', '让企业进化被看见、被度量、被落地', '阿美巴咨询|企业进化陪跑 · 体系化增长落地', '以企业进化为主线,四大价值坐标与A/M/B状态坐标为观察系', '阿美巴,企业进化,四大价值,A/M/B', '400-XXX-XXXX', 'contact@aiameiba.com', '成都', '咨询获取方案', '/contact', now]);
}
});
// 首页板块
@@ -29,7 +29,35 @@ const sections = [
['home-services', 'home-services', '产品服务 · 下一步行动', '禁止电商逻辑', '服务摘要', 1, 50],
['home-insights', 'home-insights', '认知观察', '持续观察与沉淀', '观察摘要', 1, 40],
];
-sections.forEach(s => db.run(`INSERT OR IGNORE INTO home_sections (id, key, title, subtitle, summary, visible, sort_order, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [...s, now]));
+sections.forEach(s => db.run(`INSERT OR IGNORE INTO home_sections (id, \`key\`, title, subtitle, summary, visible, sort_order, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [...s, now]));
+
+const siteTexts = [
+ ['home.eyebrow', '首页首屏眉题', '阿美巴 · 企业进化陪跑'],
+ ['home.title', '首页首屏标题', '让企业进化\n被看见、被度量、被落地'],
+ ['home.description', '首页首屏描述', '以“企业进化”为主线,以四大价值坐标与 A/M/B 状态坐标为观察系,陪跑企业构建可复制、可持续的增长经营体系。'],
+ ['home.trust', '首页首屏信任说明', '30秒理解主张:进化思想 → 价值坐标 → 状态坐标 → 行动路径'],
+ ['home.primary_cta', '首页主按钮', '开始企业进化诊断'],
+ ['home.secondary_cta', '首页咨询按钮', '咨询获取方案'],
+ ['home.light_cta', '首页轻量对照按钮', '轻量对照'],
+ ['home.notice', '首页提示', '以真实业务内容为准,后台发布后前台即时生效。'],
+ ['home.about_title', '首页关于标题', '我们是谁'],
+ ['home.about_text', '首页关于描述', '围绕企业进化经营哲学,陪跑企业把战略、组织、机制与人的成长转化为可执行、可复盘的经营系统。'],
+ ['home.cases_title', '首页案例标题', '实践案例'],
+ ['home.services_title', '首页服务标题', '产品服务 · 下一步行动'],
+ ['home.insights_title', '首页洞察标题', '认知观察'],
+ ['global_cta.title', '全站行动标题', '下一步如何行动?'],
+ ['global_cta.description', '全站行动描述', '任意页面均可直接发起咨询,选择轻量对照或深度陪跑路径。'],
+ ['global_cta.primary', '全站主按钮', '咨询获取方案'],
+ ['global_cta.secondary', '全站次按钮', '看看你的企业'],
+ ['ameba.header_title', '阿美巴页标题', '阿美巴'],
+ ['ameba.header_description', '阿美巴页简介', '以企业进化经营哲学为底色,坚持利他、务实、陪跑,帮助企业把方法落到经营现场。'],
+ ['ameba.philosophy_title', '阿美巴哲学标题', '经营哲学'],
+ ['ameba.practice_title', '阿美巴实践标题', '服务理念'],
+ ['ameba.about_title', '阿美巴介绍标题', '我们是谁'],
+ ['ameba.about_text', '阿美巴介绍正文', '围绕企业进化与体系化增长,提供长期、务实、可复盘的企业管理咨询服务。'],
+ ['footer.note', '页脚说明', '内容以后台配置为准,持续更新。'],
+];
+siteTexts.forEach(([id, label, value]) => db.run(`INSERT OR IGNORE INTO site_texts (id, label, value, updated_at) VALUES (?, ?, ?, ?)`, [id, label, value, now]));
// 阿美巴页面
db.get(`SELECT id FROM ameba_page WHERE id=1`, (e, r) => {
if (!r) db.run(`INSERT INTO ameba_page (id, brand_position, mission, philosophy, practice, updated_at) VALUES (1, ?, ?, ?, ?, ?)`,
@@ -46,14 +74,14 @@ const fvs = [
['fv-3', 'value3', '经营价值坐标', '观察经营机制', '经营价值说明【占位】', 80],
['fv-4', 'value4', '人的价值坐标', '观察人的成长', '人的价值说明【占位】', 70],
];
-fvs.forEach(f => db.run(`INSERT OR IGNORE INTO four_values (id, key, name, definition, description, sort_order, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [...f, now]));
+fvs.forEach(f => db.run(`INSERT OR IGNORE INTO four_values (id, \`key\`, name, definition, description, sort_order, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [...f, now]));
// amb_states 固定说明
const ambs = [
['amb-A', 'A', 'A 状态|自进化', '具备自驱动', 'A状态说明【占位】', 'A/M/B不绑定企业规模的说明文案固定:同一企业不同价值维度可处不同状态', 100],
['amb-M', 'M', 'M 状态|待突破', '存在卡点', 'M状态说明【占位】', '同上固定说明', 90],
['amb-B', 'B', 'B 状态|待构建', '基础待构建', 'B状态说明【占位】', '同上固定说明', 80],
];
-ambs.forEach(a => db.run(`INSERT OR IGNORE INTO amb_states (id, key, name, definition, description, fixed_note, sort_order, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [...a, now]));
+ambs.forEach(a => db.run(`INSERT OR IGNORE INTO amb_states (id, \`key\`, name, definition, description, fixed_note, sort_order, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [...a, now]));
// ten_systems
for (let i = 1; i <= 10; i++) {
db.run(`INSERT OR IGNORE INTO ten_systems (id, name, description, sort_order, updated_at) VALUES (?, ?, ?, ?, ?)`, [`sys-${i}`, `系统${i}`, `第${i}系统说明【占位】`, 100 - i * 5, now]);
diff --git a/src/components/Layout/GlobalCTA.tsx b/src/components/Layout/GlobalCTA.tsx
index 076ff5f..9c00a78 100644
--- a/src/components/Layout/GlobalCTA.tsx
+++ b/src/components/Layout/GlobalCTA.tsx
@@ -1,4 +1,5 @@
import { Link } from 'react-router-dom'
+import { useCMSTexts } from '@/services/cms'
interface Props {
title?: string
@@ -17,6 +18,11 @@ export default function GlobalCTA({
secondaryLabel = '看看你的企业',
secondaryTo = '/self-check',
}: Props) {
+ const { data: texts } = useCMSTexts()
+ title = texts['global_cta.title'] || title
+ desc = texts['global_cta.description'] || desc
+ primaryLabel = texts['global_cta.primary'] || primaryLabel
+ secondaryLabel = texts['global_cta.secondary'] || secondaryLabel
return (
diff --git a/src/pages/Ameba/index.tsx b/src/pages/Ameba/index.tsx
index 723ea43..1f9702c 100644
--- a/src/pages/Ameba/index.tsx
+++ b/src/pages/Ameba/index.tsx
@@ -1,10 +1,12 @@
import { Link } from 'react-router-dom'
import GlobalCTA from '@/components/Layout/GlobalCTA'
-import { useCMS, getCMSAmeba } from '@/services/cms'
+import { useCMS, useCMSTexts, getCMSAmeba } from '@/services/cms'
export default function Ameba() {
const { data } = useCMS(getCMSAmeba, null)
+ const { data: texts } = useCMSTexts()
const content = data || {}
+ const text = (key: string, fallback: string) => texts[key] || fallback
return (
@@ -13,13 +15,10 @@ export default function Ameba() {
首页
{' '}
- / 阿美巴
-
-
阿美巴
-
{content.brand_position || '以企业进化经营哲学为底色,坚持“利他、务实、陪跑”。不做纸上谈兵,只做可落地、可量化、有结果的实操辅导。'}
-
- 【占位|待业务确认】团队介绍、资质、里程碑、价值观等由知识母盘灌入。专家网络仅作辅助信任证据,不作为核心支柱。
+ / {text('ameba.header_title', '阿美巴')}
+
{text('ameba.header_title', '阿美巴')}
+
{content.brand_position || text('ameba.header_description', '以企业进化经营哲学为底色,坚持利他、务实、陪跑,帮助企业把方法落到经营现场。')}
@@ -27,18 +26,12 @@ export default function Ameba() {
-
经营哲学
+
{text('ameba.philosophy_title', '经营哲学')}
{content.philosophy || '阿米巴哲学本土化实践,强调人人经营、数据透明、持续改善。'}
-
- 【占位】哲学主张、原则、行为准则待确认。
-
-
服务理念
+
{text('ameba.practice_title', '服务理念')}
{content.practice || '教练式陪跑:顾问入企,手把手带教,体系化交付 SOP 与工具包。'}
-
- 【占位】陪跑模式、交付标准待确认。
-
@@ -46,14 +39,12 @@ export default function Ameba() {
-
我们是谁 · 占位
+
{text('ameba.about_title', '我们是谁')}
- 成都阿美巴企业管理咨询有限公司,专注体系化增长落地。核心团队十年以上实战经验,覆盖制造、服务、科技、商贸等领域。
+ {text('ameba.about_text', '围绕企业进化与体系化增长,提供长期、务实、可复盘的企业管理咨询服务。')}
-
- {content.mission || '【占位|待业务确认】公司简介、团队、资质证书、合作客户 LOGO 墙均待业务方提供定稿素材。'}
-
+ {content.mission &&
{content.mission}
}
diff --git a/src/pages/Home/home.css b/src/pages/Home/home.css
index 1293491..5afe8e3 100644
--- a/src/pages/Home/home.css
+++ b/src/pages/Home/home.css
@@ -7,6 +7,8 @@
.hero-copy h1 { font-size: 40px; font-weight: 900; line-height: 1.15; margin: 8px 0 14px; }
.hero-desc { color: rgba(255,255,255,0.82); font-size: 15px; line-height: 1.8; max-width: 560px; }
.hero-actions { display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap; }
+.hero-actions .btn-ghost { color: #fff; border-color: rgba(255,255,255,0.55); background: rgba(255,255,255,0.08); }
+.hero-actions .btn-ghost:hover { color: #163E70; background: #fff; border-color: #fff; }
.hero-trust { margin-top: 16px; font-size: 12px; color: rgba(255,255,255,0.6); border-top: 1px solid rgba(255,255,255,0.12); padding-top: 12px; }
.visual-card { background: #fff; color: var(--text); border-radius: 16px; padding: 24px; box-shadow: 0 20px 60px rgba(0,0,0,0.2); }
.visual-title { font-weight: 800; color: var(--primary); margin-bottom: 16px; }
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index 5add0d9..85f2554 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -1,7 +1,7 @@
import { Link } from 'react-router-dom'
import GlobalCTA from '@/components/Layout/GlobalCTA'
import { FOUR_VALUES as FALLBACK_FOUR, SERVICES as FALLBACK_SERVICES, CASES as FALLBACK_CASES, INSIGHTS as FALLBACK_INSIGHTS } from '@/data/mock'
-import { useCMS, getCMSFourValues, getCMSServices, getCMSCases, getCMSInsights } from '@/services/cms'
+import { useCMS, useCMSTexts, getCMSFourValues, getCMSServices, getCMSCases, getCMSInsights } from '@/services/cms'
import './home.css'
export default function Home() {
@@ -9,36 +9,37 @@ export default function Home() {
const { data: services } = useCMS(getCMSServices, FALLBACK_SERVICES)
const { data: cases } = useCMS(getCMSCases, FALLBACK_CASES)
const { data: insights } = useCMS(getCMSInsights, FALLBACK_INSIGHTS)
+ const { data: texts } = useCMSTexts()
+ const text = (key: string, fallback: string) => texts[key] || fallback
+ const heroTitle = text('home.title', '让企业进化\n被看见、被度量、被落地').split('\n')
return (
{/* 首屏:3秒识别品牌 */}
-
阿美巴 · 企业进化陪跑
+
{text('home.eyebrow', '阿美巴 · 企业进化陪跑')}
- 让企业进化
-
- 被看见、被度量、被落地
+ {heroTitle.map((line, i) => {i > 0 &&
}{line})}
- 以“企业进化”为主线,以四大价值坐标与 A/M/B 状态坐标为观察系,陪跑企业构建可复制、可持续的增长经营体系。
+ {text('home.description', '以“企业进化”为主线,以四大价值坐标与 A/M/B 状态坐标为观察系,陪跑企业构建可复制、可持续的增长经营体系。')}
- 一期为骨架与占位,不虚构业务数据。
+ {text('home.notice', '以真实业务内容为准,后台发布后前台即时生效。')}
- 开始企业进化诊断
+ {text('home.primary_cta', '开始企业进化诊断')}
- 咨询获取方案
+ {text('home.secondary_cta', '咨询获取方案')}
- 轻量对照
+ {text('home.light_cta', '轻量对照')}
- 30秒理解主张:进化思想 → 价值坐标 → 状态坐标 → 行动路径
+ {text('home.trust', '30秒理解主张:进化思想 → 价值坐标 → 状态坐标 → 行动路径')}
@@ -75,15 +76,15 @@ export default function Home() {
Ameba
-
关于阿美巴
+
{text('home.about_title', '关于阿美巴')}
克制理性的企业服务风格,以方法与陪跑见长,非培训话术与AI噱头。
- 阿美巴以企业进化经营哲学为底色,结合中国中小企业实际,提供从诊断到落地陪跑的全链路服务。
+ {text('home.about_text', '阿美巴以企业进化经营哲学为底色,结合中国中小企业实际,提供从诊断到落地陪跑的全链路服务。')}
- 【占位|待业务确认】品牌故事、创始团队、使命愿景等由知识母盘灌入。
+ {text('home.about_note', '品牌故事、创始团队、使命愿景等内容请在后台配置。')}
@@ -152,7 +153,7 @@ export default function Home() {
Cases
-
实践案例(占位)
+
{text('home.cases_title', '实践案例')}
固定叙事插槽:原状 → 目标 → 差距 → 根因 → 介入 → 路径 → 动作 → 结果 → 迭代。无虚构数据。
@@ -175,7 +176,7 @@ export default function Home() {
Services
-
产品服务 · 下一步行动
+
{text('home.services_title', '产品服务 · 下一步行动')}
禁止电商商城逻辑;重心是“下一步如何行动”。标准产品预留价格,定制展示“咨询/获取方案”。
@@ -204,7 +205,7 @@ export default function Home() {
Insights
-
认知观察
+
{text('home.insights_title', '认知观察')}
围绕进化思想、价值坐标、A/M/B 的观察与思考。
diff --git a/src/pages/SelfCheck/index.tsx b/src/pages/SelfCheck/index.tsx
index caa6271..175f8ab 100644
--- a/src/pages/SelfCheck/index.tsx
+++ b/src/pages/SelfCheck/index.tsx
@@ -1,8 +1,9 @@
-import { useState } from 'react'
+import { useState, useMemo } from 'react'
import { Link } from 'react-router-dom'
import GlobalCTA from '@/components/Layout/GlobalCTA'
+import { useCMS, getCMSSelfCheck } from '@/services/cms'
-type CheckItem = { id: string; question: string; dimension: string }
+type CheckItem = { id: string; question: string; dimension: string; options?: string[] }
const CHECK_ITEMS: CheckItem[] = [
{ id: 'q1', question: '战略方向在团队中是否清晰一致?', dimension: '战略价值' },
@@ -12,6 +13,14 @@ const CHECK_ITEMS: CheckItem[] = [
]
export default function SelfCheck() {
+ const { data: cmsCheck } = useCMS(getCMSSelfCheck, null)
+ const items = useMemo(() => {
+ try {
+ const parsed = JSON.parse(cmsCheck?.questions || '[]')
+ if (!Array.isArray(parsed) || !parsed.length) return CHECK_ITEMS
+ return parsed.map((item: any, index: number) => ({ id: item.id || `q${index + 1}`, question: item.q || item.question || '', dimension: item.dimension || `价值维度 ${index + 1}`, options: item.options }))
+ } catch { return CHECK_ITEMS }
+ }, [cmsCheck])
const [answers, setAnswers] = useState>({})
const [showResult, setShowResult] = useState(false)
@@ -29,7 +38,7 @@ export default function SelfCheck() {
看看你的企业
- 轻量自我对照工具,帮助你快速定位在四大价值 × A/M/B 的大致位置。
+ {cmsCheck?.guide || '轻量自我对照工具,帮助你快速定位在四大价值 × A/M/B 的大致位置。'}
一期禁止长问卷、复杂评分、自动诊断报告。仅做轻量对照,深度诊断需线下陪跑。
@@ -39,17 +48,19 @@ export default function SelfCheck() {
-
轻量对照(4 题)
+
轻量对照({items.length} 题)
每题选择最贴近现状的选项,结果仅作自我对照参考,不生成诊断报告。
- {CHECK_ITEMS.map(item => (
+ {items.map(item => (
))}
@@ -78,14 +90,14 @@ export default function SelfCheck() {
对照结果(占位示意)
- 基于你的选择,系统提示关注维度:
+ {cmsCheck?.logic_text || '基于你的选择,系统提示关注维度:'}
{Object.entries(answers)
- .map(([k, v]) => `${CHECK_ITEMS.find(i => i.id === k)?.dimension}:${v}`)
+ .map(([k, v]) => `${items.find(i => i.id === k)?.dimension}:${v}`)
.join(';') || '(请选择后再查看)'}
- 【占位|一期不做复杂评分】此处仅为轻量对照提示,不生成诊断报告。深度诊断请预约陪跑。
+ {cmsCheck?.next_guide || '此处仅为轻量对照提示,不生成诊断报告。深度诊断请预约陪跑。'}
diff --git a/src/services/cms.ts b/src/services/cms.ts
index 2091a09..036e282 100644
--- a/src/services/cms.ts
+++ b/src/services/cms.ts
@@ -19,7 +19,9 @@ export interface CMSPublicData {
home_sections: any[]
ameba: any
evolution_thought: any
+ evolution_process: any
self_check: any
+ texts: Record
}
let cache: CMSPublicData | null = null
@@ -28,7 +30,7 @@ let pending: Promise | null = null
export async function fetchCMSPublic(): Promise {
if (cache) return cache
if (pending) return pending
- pending = fetch(CMS_PUBLIC).then(async r=>{
+ pending = fetch(`${CMS_PUBLIC}${CMS_PUBLIC.includes('?') ? '&' : '?'}t=${Date.now()}`, { cache: 'no-store' }).then(async r=>{
if(!r.ok) throw new Error('cms fetch '+r.status)
const j=await r.json()
if(j.code!==0) throw new Error(j.msg)
@@ -171,6 +173,10 @@ export async function getCMSMethods(): Promise{ const d=awai
export async function getCMSAmb(): Promise>{ const d=await fetchCMSPublic(); return normalizeAmb(d?.amb_states as any) }
export async function getCMSConfig(){ const d=await fetchCMSPublic(); return d?.config || null }
export async function getCMSAmeba(){ const d=await fetchCMSPublic(); return d?.ameba || null }
+export async function getCMSTexts(){ const d=await fetchCMSPublic(); return d?.texts || {} }
+export async function getCMSEvolutionThought(){ const d=await fetchCMSPublic(); return d?.evolution_thought || null }
+export async function getCMSEvolutionProcess(){ const d=await fetchCMSPublic(); return d?.evolution_process || null }
+export async function getCMSSelfCheck(){ const d=await fetchCMSPublic(); return d?.self_check || null }
// Hook for React
import { useEffect, useState } from 'react'
@@ -184,3 +190,7 @@ export function useCMS(loader: ()=>Promise, fallback: T){
},[])
return {data, loading}
}
+
+export function useCMSTexts(){
+ return useCMS(getCMSTexts, {} as Record)
+}