Browse Source

初始提交

快乐的梦鱼 1 month ago
parent
commit
96d7c08956
100 changed files with 21939 additions and 1187 deletions
  1. 9 0
      .editorconfig
  2. 39 1
      .gitignore
  3. 2 0
      .npmrc
  4. 7 0
      .vscode/extensions.json
  5. 252 0
      .vscode/settings.json
  6. 60 0
      AGENTS.md
  7. 18 60
      README.md
  8. 9 0
      eslint.config.js
  9. 13 0
      index.html
  10. 0 5
      miniprogram/app.ts
  11. 0 6
      miniprogram/app.wxss
  12. 0 4
      miniprogram/pages/home/home.json
  13. 0 3
      miniprogram/pages/home/home.ts
  14. 0 3
      miniprogram/pages/inherit/inherit.ts
  15. 0 4
      miniprogram/pages/travel/travel.json
  16. 0 3
      miniprogram/pages/travel/travel.ts
  17. 0 3
      miniprogram/pages/user/user.ts
  18. 0 2
      miniprogram/tailwind/tailwind.css
  19. 0 18
      miniprogram/tailwind/tailwind.wxss
  20. 19325 989
      package-lock.json
  21. 50 16
      package.json
  22. 468 0
      patches/weapp-svg-icon+1.0.0.patch
  23. 2 5
      postcss.config.js
  24. 16 23
      project.config.json
  25. 3 16
      project.private.config.json
  26. BIN
      public/logo.png
  27. 24 0
      refer/cultural-achievements.html
  28. 18 0
      refer/cultural-relics.html
  29. 65 0
      refer/explore.html
  30. 24 0
      refer/heritage-experience.html
  31. 131 0
      refer/heritage.html
  32. 143 0
      refer/home.html
  33. 45 0
      refer/profile.html
  34. 18 0
      refer/protected-areas.html
  35. 24 0
      refer/theory-research.html
  36. 18 0
      refer/traditional-villages.html
  37. 52 0
      refer/travel.html
  38. 202 0
      scripts/generate-svg-icons.mjs
  39. 8 0
      src/app.css
  40. 14 11
      miniprogram/app.json
  41. 6 0
      src/app.ts
  42. 9 0
      src/components/HelloWorld/HelloWorld.json
  43. 1 0
      src/components/HelloWorld/HelloWorld.scss
  44. 63 0
      src/components/HelloWorld/HelloWorld.ts
  45. 19 0
      src/components/HelloWorld/HelloWorld.wxml
  46. 7 0
      src/components/banner-swiper/index.json
  47. 31 0
      src/components/banner-swiper/index.ts
  48. 37 0
      src/components/banner-swiper/index.wxml
  49. 9 0
      src/components/banner-swiper/index.wxss
  50. 7 0
      src/components/news-item/index.json
  51. 17 0
      src/components/news-item/index.ts
  52. 10 0
      src/components/news-item/index.wxml
  53. 1 0
      src/components/news-item/index.wxss
  54. 5 0
      src/components/page-header/index.json
  55. 49 0
      src/components/page-header/index.ts
  56. 11 0
      src/components/page-header/index.wxml
  57. 15 0
      src/components/page-header/index.wxss
  58. 7 0
      src/components/recommend-card/index.json
  59. 35 0
      src/components/recommend-card/index.ts
  60. 21 0
      src/components/recommend-card/index.wxml
  61. 1 0
      src/components/recommend-card/index.wxss
  62. 7 0
      src/components/section-header/index.json
  63. 26 0
      src/components/section-header/index.ts
  64. 10 0
      src/components/section-header/index.wxml
  65. 1 0
      src/components/section-header/index.wxss
  66. 7 0
      src/components/volunteer-card/index.json
  67. 17 0
      src/components/volunteer-card/index.ts
  68. 15 0
      src/components/volunteer-card/index.wxml
  69. 1 0
      src/components/volunteer-card/index.wxss
  70. 6 0
      src/custom-tab-bar/index.json
  71. 32 0
      src/custom-tab-bar/index.ts
  72. 16 0
      src/custom-tab-bar/index.wxml
  73. 29 0
      src/custom-tab-bar/index.wxss
  74. 13 0
      src/pages/home/home.json
  75. 194 0
      src/pages/home/home.ts
  76. 58 0
      src/pages/home/home.wxml
  77. 1 0
      src/pages/home/home.wxss
  78. 0 0
      src/pages/inherit/inherit.json
  79. 11 0
      src/pages/inherit/inherit.ts
  80. 0 0
      src/pages/inherit/inherit.wxml
  81. 0 0
      src/pages/inherit/inherit.wxss
  82. 4 0
      src/pages/news/news.json
  83. 11 0
      src/pages/news/news.ts
  84. 1 1
      miniprogram/pages/home/home.wxml
  85. 0 0
      src/pages/news/news.wxss
  86. 4 0
      src/pages/travel/travel.json
  87. 11 0
      src/pages/travel/travel.ts
  88. 0 0
      src/pages/travel/travel.wxml
  89. 0 0
      src/pages/travel/travel.wxss
  90. 0 0
      src/pages/user/user.json
  91. 11 0
      src/pages/user/user.ts
  92. 0 0
      src/pages/user/user.wxml
  93. 0 0
      src/pages/user/user.wxss
  94. 10 0
      src/sitemap.json
  95. 5 0
      src/theme.json
  96. 10 0
      src/utils/svgIcon.ts
  97. 3 0
      src/utils/util.ts
  98. 5 0
      stylelint.config.js
  99. 0 14
      tailwind.config.js
  100. 0 0
      tsconfig.json

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 39 - 1
.gitignore

@@ -1 +1,39 @@
-node_modules/
+# dependencies
+node_modules
+.pnp
+.pnp.js
+
+# testing
+coverage
+
+# next.js
+.next/
+out/
+build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# turbo
+.turbo
+
+dist
+vite.config.ts.timestamp-*.mjs
+dist-web
+.weapp-vite/
+
+dist-plugin
+dist/web

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+# 默认镜像源
+# registry=https://registry.npmmirror.com

+ 7 - 0
.vscode/extensions.json

@@ -0,0 +1,7 @@
+{
+  "recommendations": [
+    "dbaeumer.vscode-eslint",
+    "stylelint.vscode-stylelint",
+    "weapp-vite.weapp-vite"
+  ]
+}

+ 252 - 0
.vscode/settings.json

@@ -0,0 +1,252 @@
+{
+  "prettier.enable": false,
+  "editor.formatOnSave": false,
+  "editor.codeActionsOnSave": {
+    "source.fixAll": "never",
+    "source.fixAll.eslint": "explicit",
+    "source.fixAll.stylelint": "never",
+    "source.organizeImports": "never"
+  },
+  "eslint.rules.customizations": [
+    {
+      "rule": "format/*",
+      "severity": "off"
+    },
+    {
+      "rule": "*-indent",
+      "severity": "off"
+    },
+    {
+      "rule": "*-spacing",
+      "severity": "off"
+    },
+    {
+      "rule": "*-spaces",
+      "severity": "off"
+    },
+    {
+      "rule": "*-order",
+      "severity": "off"
+    },
+    {
+      "rule": "*-dangle",
+      "severity": "off"
+    },
+    {
+      "rule": "*-newline",
+      "severity": "off"
+    },
+    {
+      "rule": "*quotes",
+      "severity": "off"
+    },
+    {
+      "rule": "*semi",
+      "severity": "off"
+    }
+  ],
+  "eslint.validate": [
+    "javascript",
+    "javascriptreact",
+    "typescript",
+    "typescriptreact",
+    "vue",
+    "html",
+    "markdown",
+    "json",
+    "jsonc",
+    "yaml",
+    "toml",
+    "xml",
+    "gql",
+    "graphql",
+    "astro",
+    "css",
+    "less",
+    "scss",
+    "pcss",
+    "postcss"
+  ],
+  "css.validate": false,
+  "less.validate": false,
+  "scss.validate": false,
+  "files.associations": {
+    "*.wxml": "html",
+    "*.wxss": "css"
+  },
+  "html.customData": [
+    "./.weapp-vite/mini-program.html-data.json"
+  ],
+  "tailwindCSS.experimental.classRegex": [
+    [
+      "clsx\\(([^)]*)\\)",
+      "(?:'|\"|`)([^']*)(?:'|\"|`)"
+    ],
+    [
+      "cn\\(([^)]*)\\)",
+      "(?:'|\"|`)([^']*)(?:'|\"|`)"
+    ],
+    [
+      "cva\\(([^)]*)\\)",
+      "[\"'`]([^\"'`]*).*?[\"'`]"
+    ],
+    [
+      "cx\\(([^)]*)\\)",
+      "(?:'|\"|`)([^']*)(?:'|\"|`)"
+    ]
+  ],
+  "tailwindCSS.classAttributes": [
+    "class",
+    "className",
+    "ngClass",
+    "class:list",
+    "hover-class"
+  ],
+  "tailwindCSS.includeLanguages": {
+    "wxml": "html"
+  },
+  "stylelint.validate": [
+    "css",
+    "scss",
+    "vue"
+  ],
+  "vue.server.hybridMode": true,
+  "typescript.disableReferencedProjectLoad": true,
+  "typescript.disableSolutionSearching": true,
+  "typescript.disableAutomaticTypeAcquisition": true,
+  "typescript.preferences.includePackageJsonAutoImports": "off",
+  "javascript.preferences.includePackageJsonAutoImports": "off",
+  "search.followSymlinks": false,
+  "files.watcherExclude": {
+    "**/.git/**": true,
+    "**/node_modules/**": true,
+    "**/dist/**": true,
+    "**/dist-*/**": true,
+    "**/dist-plugin/**": true,
+    "**/dist-web/**": true,
+    "**/build/**": true,
+    "**/coverage/**": true,
+    "**/miniprogram_npm/**": true,
+    "**/.cache/**": true,
+    "**/.smartapp-automator/**": true,
+    "**/.minidev/**": true,
+    "**/.playwright/**": true,
+    "**/.pnpm-store/**": true,
+    "**/.tmp/**": true,
+    "**/.turbo/**": true,
+    "**/.vite/**": true,
+    "**/.vite-inspect/**": true,
+    "**/.playwright-cli/**": true,
+    "**/.codex-tmp/**": true,
+    "**/.weapp-vite/**": true,
+    "**/.wevu-config/**": true,
+    "**/__temp__/**": true,
+    "**/docs/reports/**": true
+  },
+  "files.exclude": {
+    "node_modules": true,
+    "dist": true,
+    "**/dist-*": true,
+    "**/dist-plugin": true,
+    "**/dist-web": true,
+    "**/build": true,
+    "**/coverage": true,
+    "**/miniprogram_npm": true,
+    "**/.cache": true,
+    "**/.smartapp-automator": true,
+    "**/.codex-tmp": true,
+    "**/.minidev": true,
+    "**/.playwright": true,
+    "**/.playwright-cli": true,
+    "**/.pnpm-store": true,
+    "**/.tmp": true,
+    "**/.turbo": true,
+    "**/.vite": true,
+    "**/.vite-inspect": true,
+    "**/.weapp-vite": true,
+    "**/.wevu-config": true,
+    "**/__temp__": true,
+    "**/docs/reports": true
+  },
+  "search.exclude": {
+    "**/.git/**": true,
+    "**/node_modules/**": true,
+    "**/dist/**": true,
+    "**/dist-*/**": true,
+    "**/dist-plugin/**": true,
+    "**/dist-web/**": true,
+    "**/build/**": true,
+    "**/coverage/**": true,
+    "**/miniprogram_npm/**": true,
+    "**/.cache/**": true,
+    "**/.smartapp-automator/**": true,
+    "**/.minidev/**": true,
+    "**/.playwright/**": true,
+    "**/.pnpm-store/**": true,
+    "**/.tmp/**": true,
+    "**/.turbo/**": true,
+    "**/.vite/**": true,
+    "**/.vite-inspect/**": true,
+    "**/.playwright-cli/**": true,
+    "**/.codex-tmp/**": true,
+    "**/.weapp-vite/**": true,
+    "**/.wevu-config/**": true,
+    "**/__temp__/**": true,
+    "**/docs/reports/**": true
+  },
+  "tailwindCSS.files.exclude": [
+    "**/.git/**",
+    "**/node_modules/**",
+    "**/dist/**",
+    "**/dist-*/**",
+    "**/dist-plugin/**",
+    "**/dist-web/**",
+    "**/build/**",
+    "**/coverage/**",
+    "**/miniprogram_npm/**",
+    "**/.cache/**",
+    "**/.smartapp-automator/**",
+    "**/.minidev/**",
+    "**/.playwright/**",
+    "**/.pnpm-store/**",
+    "**/.tmp/**",
+    "**/.turbo/**",
+    "**/.vite/**",
+    "**/.vite-inspect/**",
+    "**/.playwright-cli/**",
+    "**/.codex-tmp/**",
+    "**/.weapp-vite/**",
+    "**/.wevu-config/**",
+    "**/__temp__/**",
+    "**/docs/reports/**"
+  ],
+  "tailwindCSS.codeActions": false,
+  "i18n-ally.usage.scanningIgnore": [
+    "**/.git/**",
+    "**/node_modules/**",
+    "**/dist/**",
+    "**/dist-*/**",
+    "**/dist-plugin/**",
+    "**/dist-web/**",
+    "**/build/**",
+    "**/coverage/**",
+    "**/miniprogram_npm/**",
+    "**/.cache/**",
+    "**/.smartapp-automator/**",
+    "**/.minidev/**",
+    "**/.playwright/**",
+    "**/.pnpm-store/**",
+    "**/.tmp/**",
+    "**/.turbo/**",
+    "**/.vite/**",
+    "**/.vite-inspect/**",
+    "**/.playwright-cli/**",
+    "**/.codex-tmp/**",
+    "**/.weapp-vite/**",
+    "**/.wevu-config/**",
+    "**/__temp__/**",
+    "**/docs/reports/**"
+  ],
+  "vue.tsserver.useWorkspaceTsdk": true,
+  "typescript.tsdk": "node_modules/typescript/lib"
+}

+ 60 - 0
AGENTS.md

@@ -0,0 +1,60 @@
+# AGENTS Guidelines
+
+## Local Docs First
+
+- After dependencies are installed, prefer reading local package docs under `node_modules/weapp-vite/dist/docs/` first.
+- Start with `node_modules/weapp-vite/dist/docs/index.md`, then read `README.md` and `mcp.md` as needed.
+- Prefer local package docs over stale model memory or old web pages when command behavior is unclear.
+
+## CLI Entry
+
+- This project supports both `weapp-vite` and `wv` CLI commands.
+- Treat `weapp-vite dev` and `wv dev` as equivalent forms.
+- Prefer project scripts such as `pnpm dev`, `pnpm build`, `pnpm open`, and `pnpm g` before ad-hoc shell commands.
+- Use `weapp-vite prepare` or `wv prepare` when managed support files under `.weapp-vite/` need to be refreshed.
+- Prefer `weapp-vite screenshot` or `wv screenshot` for mini-program screenshot acceptance.
+- Prefer `weapp-vite compare` or `wv compare` for mini-program screenshot diff, baseline comparison, and visual regression checks.
+- Prefer `weapp-vite ide logs --open` or `wv ide logs --open` for DevTools terminal log bridging.
+- Do not default to generic browser screenshot tools when the target is the mini-program runtime in WeChat DevTools.
+
+## AI Intent Routing
+
+- When the request mentions screenshot, 截图, 页面快照, runtime screenshot, or capture the current mini-program page, default to `weapp-vite screenshot` / `wv screenshot`.
+- When the request mentions screenshot compare, 截图对比, diff, baseline, visual regression, 像素对比, or acceptance comparison, default to `weapp-vite compare` / `wv compare`.
+- Treat these commands as the primary screenshot contract for AI workflows in this project.
+- Only fall back to generic browser screenshot tools when the target is explicitly the web runtime instead of WeChat DevTools.
+
+## Weapp-vite Workflow
+
+- Keep `vite.config.ts` as the source of truth for `weapp` config, output behavior, and IDE/MCP automation.
+- Confirm `weapp.srcRoot`, routes, subpackages, and auto-import strategy before broad refactors.
+- Prefer minimal scoped verification: targeted `pnpm build`, targeted tests, then broader checks only when required.
+- If editing package source in a monorepo dependency, rebuild the touched package before validating downstream apps to avoid stale `dist`.
+- Keep CLI ownership explicit: native `weapp-vite` commands first, IDE passthrough second.
+- For Rust/native acceleration, treat JS ↔ Rust boundary crossings as a primary performance cost. Prefer batch analysis that sends source once, parses once, and returns structured results; only put fine-grained native APIs on hot paths when profiling proves a net win.
+- Keep native AST fast paths optional and explicitly enabled. They must fall back to Babel/Oxc/Vue compiler behavior on load, parse, or runtime failure, and new native coverage needs correctness tests plus real profiling.
+
+## WeChat DevTools
+
+- Before using screenshot, preview, upload, automator, or `--open`, ensure WeChat DevTools is logged in and the service port is enabled.
+- `wv open`, `wv dev -o`, and `wv build -o` use the official DevTools CLI first, then connect automator; if the IDE is open but automator is unavailable, run `wv ide doctor --json` before retrying.
+- Use `--ide-open-strategy automator` only for explicit compatibility debugging; do not treat it as the default startup path.
+- Prefer writing screenshots to workspace paths such as `.tmp/acceptance.png`.
+
+## AI Skills
+
+- Recommend installing shared skills with `npx skills add sonofmagic/skills`.
+- In Codex/Claude environments, prefer these skills first when available:
+  - `$weapp-vite-best-practices` for config, build, subpackage, route, DevTools CLI orchestration, and screenshot/compare command work.
+  - `$weapp-vite-vue-sfc-best-practices` for `.vue` SFC macros, JSON blocks, and template compatibility.
+  - `$release-and-changeset-best-practices` for issue delivery, pnpm change intents, repoctl release decisions, and PR workflow.
+  - `$docs-and-website-sync` when documentation or AI guidance must be refreshed together with code changes.
+  - Use `$weapp-vite-best-practices` for stateful HMR, pluginRoot/dist-plugin, Web runtime compatibility, and native AST profiling; use `$wevu-best-practices` for `wevu/router` navigation semantics.
+  - Use `$weapp-devtools-e2e-best-practices` for serialized DevTools runtime suites, shared automator sessions, and known host compatibility skips.
+  - `$native-to-weapp-vite-wevu-migration` when migrating native mini-program projects to `weapp-vite + native`, or further toward Vue SFC / wevu.
+
+## Native Mini-program Authoring
+
+- Keep native page/component structure consistent with the template unless there is a clear migration goal.
+- Prefer `weapp-vite generate` or `wv generate` for new app/page/component scaffolds.
+- If migrating this project, first decide whether the target is `weapp-vite + native` or a further Vue SFC / `wevu` migration, then keep each migration wave explicit.

+ 18 - 60
README.md

@@ -1,75 +1,33 @@
-# 闽南文化漳州小程序
+# weapp-vite-tailwindcss-vant-template
 
-构建"数字闽南文化生态圈",打造集文化保护、学术研究、公众教育、产业应用于一体的综合性数字平台。通过建立"1+3+N"服务体系(1个核心数据库、3大应用终端、N个特色场景),形成覆盖全市的文化数字服务网络。
+`weapp-vite` 集成 `tailwindcss`, `@vant/weapp` 的模板
 
-本项目为闽南文化生态保护区(漳州市)的小程序项目源代码。
+## 使用方式
 
-## 赞 📚
+### 开发
 
-观此闽南文化之软件,诚为一方瑰宝之窗也!其集闽南之韵、文化之魂于一体,界面雅致,内容丰赡。
+- `pnpm dev`
 
-此程序精妙绝伦,运行如流,逻辑缜密,功能周全,用户称便,诚乃软件之良工也。闽南文脉,源远流长。今构"数字文化生态圈",融保护、研习、教化、兴业于一体。诚为数智时代承传地方文粹之典范也。
+- `pnpm dev --open` 可以打包并直接启动微信开发者工具
 
-启蒙昧,续薪火,南音雅乐盈耳,古俗新韵入目。学闽南语则乡音可拾,览风土则侨心可系。以方寸之屏,承千年文脉;借数字之翼,传四海乡声。
+### 构建
 
-足不出户,便览漳州风华;指掌之间,尽赏闽南气象。此 APP 也,实为赓续根脉、联结侨胞之津梁,可谓匠心独运、功在文化者也!
+`pnpm build`
 
-## 技术栈
+### Web 预览与构建
 
-### 前端核心技术
+- `pnpm dev:web`
+- `pnpm build:web`
 
-- **框架**: 原生微信小程序(glass-easel 组件框架)
-- **语言**: TypeScript
-- **样式方案**: TailwindCSS
-- **API 请求工具**: @imengyu/js-request-transform
+### 打开微信开发者工具
 
-## 项目结构
+`pnpm open`
 
-### 核心依赖
+### 生成组件/页面
 
-- `@imengyu/imengyu-utils`: 通用工具库
-- `@imengyu/js-request-transform`: API 请求转换工具
-- `tailwindcss`: 原子化 CSS 框架(开发依赖)
+`pnpm g path/to/your/component`
 
-### 目录组织
+## 文档地址
 
-```
-miniprogram/
-├── pages/               # 页面
-│   ├── home/            # 首页
-│   ├── inherit/         # 传承
-│   ├── travel/          # 乐游
-│   └── user/            # 我的
-├── tailwind/            # TailwindCSS 样式
-│   ├── tailwind.css     # 输入样式源文件
-│   └── tailwind.wxss    # 编译输出的样式
-├── app.ts               # 应用入口
-├── app.json             # 应用配置(含 tabBar)
-└── app.wxss             # 全局样式
-tailwind.config.js       # TailwindCSS 配置
-postcss.config.js        # PostCSS 配置
-tsconfig.json            # TypeScript 配置
-```
-
-## 开发与构建
-
-### 环境准备
-
-使用微信开发者工具打开本项目根目录,开发者工具会自动编译 TypeScript 并预览运行。
-
-### TailwindCSS 构建
-
-```bash
-# 构建一次 TailwindCSS 样式
-npm run tailwind:build
-
-# 监听文件变化并自动构建 TailwindCSS 样式
-npm run tailwind:watch
-```
-
-### 类型检查
-
-```bash
-# 执行 TypeScript 类型检查
-npm run type-check
-```
+0. `weapp-vite`: https://vite.weapp.dev/
+1. `weapp-tailwindcss`: https://tw.icebreaker.top/

+ 9 - 0
eslint.config.js

@@ -0,0 +1,9 @@
+import { icebreaker } from '@icebreakers/eslint-config'
+
+export default icebreaker({
+  miniProgram: true,
+  tailwindcss: {
+    entryPoint: './src/app.css',
+  },
+  ignores: ['CHANGELOG.md', 'README.md', '.turbo/**', 'dist/**', '.weapp-vite/**'],
+})

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <link rel="icon" href="data:," />
+    <title>weapp-vite Web</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/@weapp-vite/web/entry"></script>
+  </body>
+</html>

+ 0 - 5
miniprogram/app.ts

@@ -1,5 +0,0 @@
-// app.ts
-App<IAppOption>({
-  globalData: {},
-  onLaunch() {},
-})

+ 0 - 6
miniprogram/app.wxss

@@ -1,6 +0,0 @@
-/**app.wxss**/
-@import "./tailwind/tailwind.wxss";
-
-page {
-  height: 100%;
-}

+ 0 - 4
miniprogram/pages/home/home.json

@@ -1,4 +0,0 @@
-{
-  "navigationBarTitleText": "首页",
-  "usingComponents": {}
-}

+ 0 - 3
miniprogram/pages/home/home.ts

@@ -1,3 +0,0 @@
-Component({
-  data: {},
-})

+ 0 - 3
miniprogram/pages/inherit/inherit.ts

@@ -1,3 +0,0 @@
-Component({
-  data: {},
-})

+ 0 - 4
miniprogram/pages/travel/travel.json

@@ -1,4 +0,0 @@
-{
-  "navigationBarTitleText": "乐游",
-  "usingComponents": {}
-}

+ 0 - 3
miniprogram/pages/travel/travel.ts

@@ -1,3 +0,0 @@
-Component({
-  data: {},
-})

+ 0 - 3
miniprogram/pages/user/user.ts

@@ -1,3 +0,0 @@
-Component({
-  data: {},
-})

+ 0 - 2
miniprogram/tailwind/tailwind.css

@@ -1,2 +0,0 @@
-@tailwind components;
-@tailwind utilities;

+ 0 - 18
miniprogram/tailwind/tailwind.wxss

@@ -1,18 +0,0 @@
-.flex {
-    display: flex
-}
-.min-h-screen {
-    min-height: 100vh
-}
-.flex-col {
-    flex-direction: column
-}
-.items-center {
-    align-items: center
-}
-.justify-center {
-    justify-content: center
-}
-.font-semibold {
-    font-weight: 600
-}

File diff suppressed because it is too large
+ 19325 - 989
package-lock.json


+ 50 - 16
package.json

@@ -1,25 +1,59 @@
 {
-  "name": "minnan-zhangzhou",
-  "version": "1.0.0",
-  "description": "闽南文化漳州小程序",
-  "scripts": {
-    "tailwind:build": "postcss miniprogram/tailwind/tailwind.css -o miniprogram/tailwind/tailwind.wxss",
-    "tailwind:watch": "postcss miniprogram/tailwind/tailwind.css -o miniprogram/tailwind/tailwind.wxss --watch",
-    "type-check": "tsc --noEmit"
+  "name": "weapp-vite-tailwindcss-vant-template",
+  "type": "module",
+  "version": "1.0.9",
+  "private": true,
+  "description": "原生微信小程序 weapp-vite + tailwindcss 模板",
+  "author": "ice breaker <1324318532@qq.com>",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/weapp-vite/weapp-vite.git",
+    "directory": "templates/weapp-vite-tailwindcss-vant-template"
+  },
+  "bugs": {
+    "url": "https://github.com/weapp-vite/weapp-vite/issues"
   },
   "keywords": [],
-  "author": "",
-  "license": "",
+  "scripts": {
+    "dev": "wv dev",
+    "dev:web": "wv dev -p web --host",
+    "dev:open": "wv dev -o",
+    "dev:ui": "wv dev --ui",
+    "dev:open:ui": "wv dev --ui -o",
+    "build": "wv build",
+    "build:web": "wv build -p web",
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix",
+    "open": "wv open",
+    "stylelint": "stylelint \"src/**/*.{css,scss,vue,wxss}\" --allow-empty-input",
+    "stylelint:fix": "stylelint \"src/**/*.{css,scss,vue,wxss}\" --fix --allow-empty-input",
+    "g": "wv generate",
+    "gen:icons": "node scripts/generate-svg-icons.mjs",
+    "postinstall": "patch-package && wv prepare"
+  },
   "dependencies": {
-    "@imengyu/imengyu-utils": "^0.1.1",
-    "@imengyu/js-request-transform": "^0.4.1"
+    "@vant/weapp": "^1.11.7",
+    "weapp-svg-icon": "^1.0.0"
   },
   "devDependencies": {
-    "autoprefixer": "^10.5.4",
-    "miniprogram-api-typings": "^2.8.3-1",
+    "@icebreakers/eslint-config": "^7.1.0",
+    "@icebreakers/stylelint-config": "^5.0.6",
+    "@iconify-json/mdi": "^1.2.3",
+    "@iconify-json/ph": "^1.2.2",
+    "@iconify-json/solar": "^1.2.10",
+    "@iconify/tailwind4": "^1.2.3",
+    "@types/node": "^26.4.0",
+    "@weapp-vite/dashboard": "^6.25.1",
+    "eslint": "^10.9.1",
+    "miniprogram-api-typings": "^5.2.3",
+    "patch-package": "^8.0.1",
     "postcss": "^8.5.26",
-    "postcss-cli": "^11.0.1",
-    "tailwindcss": "^3.4.19",
-    "typescript": "^5.9.3"
+    "sass": "^1.103.1",
+    "stylelint": "^17.14.1",
+    "tailwindcss": "^4.3.3",
+    "typescript": "^6.0.3",
+    "weapp-vite": "^6.25.1",
+    "wevu": "^6.25.1"
   }
 }

File diff suppressed because it is too large
+ 468 - 0
patches/weapp-svg-icon+1.0.0.patch


+ 2 - 5
postcss.config.js

@@ -1,6 +1,3 @@
-module.exports = {
-  plugins: {
-    tailwindcss: {},
-    autoprefixer: {},
-  },
+export default {
+  plugins: {},
 }

+ 16 - 23
project.config.json

@@ -1,11 +1,8 @@
 {
-  "description": "项目配置文件",
-  "miniprogramRoot": "miniprogram/",
+  "description": "闽南文化(漳州)小程序",
+  "miniprogramRoot": "dist/",
   "compileType": "miniprogram",
   "setting": {
-    "useCompilerPlugins": [
-      "typescript"
-    ],
     "babelSetting": {
       "ignore": [],
       "disablePlugins": [],
@@ -14,37 +11,33 @@
     "coverView": false,
     "postcss": false,
     "minified": false,
-    "enhance": false,
+    "enhance": true,
     "showShadowRootInWxmlPanel": false,
-    "packNpmRelationList": [],
+    "packNpmRelationList": [
+      {
+        "packageJsonPath": "./package.json",
+        "miniprogramNpmDistDir": "./dist"
+      }
+    ],
     "ignoreUploadUnusedFiles": true,
     "compileHotReLoad": false,
-    "skylineRenderEnable": true,
-    "es6": false,
-    "compileWorklet": false,
-    "uglifyFileName": false,
-    "uploadWithSourceMap": true,
-    "packNpmManually": false,
-    "minifyWXSS": true,
-    "minifyWXML": true,
-    "localPlugins": false,
-    "condition": false,
-    "swc": false,
-    "disableSWC": true,
-    "disableUseStrict": false
+    "skylineRenderEnable": false,
+    "packNpmManually": true,
+    "es6": true,
+    "minifyWXML": true
   },
   "simulatorType": "wechat",
   "simulatorPluginLibVersion": {},
   "condition": {},
-  "srcMiniprogramRoot": "miniprogram/",
+  "srcMiniprogramRoot": "dist/",
   "editorSetting": {
     "tabIndent": "insertSpaces",
     "tabSize": 2
   },
-  "libVersion": "trial",
+  "libVersion": "3.13.2",
   "packOptions": {
     "ignore": [],
     "include": []
   },
-  "appid": "wxa33c724c785988fa"
+  "appid": "wx005c00425fd2342c"
 }

+ 3 - 16
project.private.config.json

@@ -1,22 +1,9 @@
 {
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "projectname": "minnan-zhangzhou",
+  "projectname": "闽南文化(漳州)小程序",
   "setting": {
     "compileHotReLoad": true,
-    "urlCheck": true,
-    "coverView": false,
-    "lazyloadPlaceholderEnable": false,
-    "skylineRenderEnable": true,
-    "preloadBackgroundData": false,
-    "autoAudits": false,
-    "useApiHook": true,
-    "showShadowRootInWxmlPanel": false,
-    "useStaticServer": false,
-    "useLanDebug": false,
-    "showES6CompileOption": false,
-    "bigPackageSizeSupport": false,
-    "checkInvalidKey": true,
-    "ignoreDevUnusedFiles": true
+    "urlCheck": false
   },
-  "libVersion": "3.17.1"
+  "libVersion": "3.15.0"
 }

BIN
public/logo.png


File diff suppressed because it is too large
+ 24 - 0
refer/cultural-achievements.html


File diff suppressed because it is too large
+ 18 - 0
refer/cultural-relics.html


File diff suppressed because it is too large
+ 65 - 0
refer/explore.html


File diff suppressed because it is too large
+ 24 - 0
refer/heritage-experience.html


File diff suppressed because it is too large
+ 131 - 0
refer/heritage.html


File diff suppressed because it is too large
+ 143 - 0
refer/home.html


File diff suppressed because it is too large
+ 45 - 0
refer/profile.html


File diff suppressed because it is too large
+ 18 - 0
refer/protected-areas.html


File diff suppressed because it is too large
+ 24 - 0
refer/theory-research.html


File diff suppressed because it is too large
+ 18 - 0
refer/traditional-villages.html


File diff suppressed because it is too large
+ 52 - 0
refer/travel.html


+ 202 - 0
scripts/generate-svg-icons.mjs

@@ -0,0 +1,202 @@
+// 从 @iconify-json/ph 与 @iconify-json/solar 中抽取项目实际用到的图标,
+// 生成一个精简的 iconify 数据文件,供 weapp-svg-icon 组件使用,避免全量打包。
+import { readFile, readdir, writeFile } from 'node:fs/promises'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const root = path.resolve(__dirname, '..')
+
+const SRC_DIR = path.join(root, 'src')
+const PKG_DIR = path.join(root, 'node_modules', 'weapp-svg-icon')
+const OUTPUT = path.join(PKG_DIR, 'miniprogram_dist', 'index.js')
+
+const ICON_PREFIXES = ['ph', 'solar']
+const ICON_RE = /(?:^|[^\w])(ph|solar):([a-z0-9-]+)/g
+const SCAN_EXTENSIONS = new Set(['.ts', '.js', '.wxml', '.vue', '.json'])
+
+async function walk(dir) {
+  const entries = await readdir(dir, { withFileTypes: true })
+  const files = []
+  for (const entry of entries) {
+    if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
+    const full = path.join(dir, entry.name)
+    if (entry.isDirectory()) files.push(...(await walk(full)))
+    else files.push(full)
+  }
+  return files
+}
+
+async function collectUsedIcons() {
+  const files = await walk(SRC_DIR)
+  const used = new Set()
+  for (const file of files) {
+    if (!SCAN_EXTENSIONS.has(path.extname(file))) continue
+    const content = await readFile(file, 'utf8')
+    for (const match of content.matchAll(ICON_RE)) {
+      used.add(`${match[1]}:${match[2]}`)
+    }
+  }
+  return [...used].sort()
+}
+
+async function loadIconifyJson(packageName) {
+  const p = path.join(root, 'node_modules', '@iconify-json', packageName, 'icons.json')
+  return JSON.parse(await readFile(p, 'utf8'))
+}
+
+async function main() {
+  const used = await collectUsedIcons()
+  const byPrefix = { ph: new Set(), solar: new Set() }
+  for (const id of used) {
+    const [prefix, icon] = id.split(':')
+    byPrefix[prefix]?.add(icon)
+  }
+
+  const sets = {}
+  const svgNameSwitch = (id) =>
+    id
+      .split(/:|-|_/)
+      .filter(Boolean)
+      .map((s) => s[0].toUpperCase() + s.slice(1).toLowerCase())
+      .join('')
+  for (const prefix of ICON_PREFIXES) {
+    const source = await loadIconifyJson(prefix)
+    const icons = {}
+    for (const icon of [...byPrefix[prefix]].sort()) {
+      if (source.icons[icon]) icons[icon] = source.icons[icon]
+    }
+    sets[svgNameSwitch(prefix)] = {
+      prefix: source.prefix ?? prefix,
+      width: source.width,
+      height: source.height,
+      icons,
+    }
+  }
+
+  const template = `// Generated by scripts/generate-svg-icons.mjs — DO NOT EDIT.
+// Minimal iconify data extracted from @iconify-json/ph + @iconify-json/solar.
+var iconifyPresets = ${JSON.stringify(sets)}
+
+function svgNameSwitch(id) {
+  return id
+    .split(/:|-|_/)
+    .filter(Boolean)
+    .map(function (s) {
+      return s[0].toUpperCase() + s.slice(1).toLowerCase()
+    })
+    .join('')
+}
+
+function encodeSvg(svg) {
+  return svg
+    .replace('<svg', ~svg.indexOf('xmlns') ? '<svg' : '<svg xmlns="http://www.w3.org/2000/svg"')
+    .replace(/"/g, "'")
+    .replace(/%/g, '%25')
+    .replace(/#/g, '%23')
+    .replace(/{/g, '%7B')
+    .replace(/}/g, '%7D')
+    .replace(/</g, '%3C')
+    .replace(/>/g, '%3E')
+}
+
+function getDataUri(data) {
+  return 'url("data:image/svg+xml;utf8,' + data + '")'
+}
+
+function presetsIconify(id) {
+  var parts = id.split(':')
+  var prefix = ''
+  var icon = ''
+  if (parts.length === 2) {
+    prefix = svgNameSwitch(parts[0])
+    icon = parts[1]
+  } else if (parts.length === 1) {
+    parts = parts[0].split('-')
+    if (parts.length > 1) {
+      prefix = svgNameSwitch(parts.shift())
+      icon = parts.join('-')
+    }
+  }
+  var setData = iconifyPresets[prefix] || null
+  var width = setData && setData.width ? setData.width : 32
+  var height = setData && setData.height ? setData.height : 32
+  var iconData = setData && setData.icons && setData.icons[icon] ? setData.icons[icon] : null
+  if (!iconData) {
+    return ''
+  }
+  var svgAttributes = {
+    xmlns: 'http://www.w3.org/2000/svg',
+    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
+    width: '1em',
+    height: '1em',
+    preserveAspectRatio: 'xMidYMid meet',
+    viewBox: '0 0 ' + width + ' ' + height
+  }
+  var svgAttributesStr = Object.keys(svgAttributes)
+    .map(function (attr) {
+      return attr + '="' + svgAttributes[attr] + '"'
+    })
+    .join(' ')
+  return '<svg ' + svgAttributesStr + '>' + iconData.body + '</svg>'
+}
+
+function getLocalSVG(id) {
+  var name = svgNameSwitch(id)
+  var data = presetsIconify(id)
+  if (data) {
+    return getDataUri(encodeSvg(data))
+  }
+  data = wx.getStorageSync(name)
+  if (data) {
+    return getDataUri(data)
+  }
+  return ''
+}
+
+Component({
+  properties: {
+    name: {
+      type: String,
+      value: ''
+    },
+    color: {
+      type: String,
+      value: ''
+    },
+    size: {
+      type: String
+    }
+  },
+  data: {
+    width: '1em',
+    height: '1em',
+    iconUrl: '',
+    class: ''
+  },
+  lifetimes: {
+    attached: function attached() {
+      this.setData({
+        iconUrl: getLocalSVG(this.data.name),
+        class: this.data.color ? 'icon' : 'default-icon'
+      })
+      if (this.data.size) {
+        this.setData({
+          width: this.data.size,
+          height: this.data.size
+        })
+      }
+    }
+  }
+})
+`
+
+  await writeFile(OUTPUT, template, 'utf8')
+  console.log(`generated ${path.relative(root, OUTPUT)} with ${used.length} icons`)
+  console.log(used.join('\n'))
+}
+
+main().catch((err) => {
+  console.error(err)
+  process.exit(1)
+})

+ 8 - 0
src/app.css

@@ -0,0 +1,8 @@
+@import 'tailwindcss';
+
+@plugin "@iconify/tailwind4" {
+  prefixes: mdi;
+  icon-selector: '.i-{prefix}-{name}';
+}
+
+@source "./**/*.{wxml,js,ts,vue}";

+ 14 - 11
miniprogram/app.json

@@ -1,20 +1,19 @@
 {
+  "$schema": "https://vite.weapp.dev/app.json",
   "pages": [
     "pages/home/home",
     "pages/inherit/inherit",
+    "pages/news/news",
     "pages/travel/travel",
     "pages/user/user"
   ],
-  "window": {
-    "navigationBarTextStyle": "black",
-    "navigationBarTitleText": "闽南文化漳州",
-    "navigationBarBackgroundColor": "#ffffff"
-  },
+  "window": {},
   "tabBar": {
-    "color": "#999999",
-    "selectedColor": "#c8102e",
-    "backgroundColor": "#ffffff",
-    "borderStyle": "black",
+    "custom": true,
+    "color": "#a79d93",
+    "selectedColor": "#a83f2d",
+    "backgroundColor": "#fffaf1",
+    "borderStyle": "white",
     "list": [
       {
         "pagePath": "pages/home/home",
@@ -25,8 +24,12 @@
         "text": "传承"
       },
       {
+        "pagePath": "pages/news/news",
+        "text": "资讯"
+      },
+      {
         "pagePath": "pages/travel/travel",
-        "text": "乐游"
+        "text": "文旅"
       },
       {
         "pagePath": "pages/user/user",
@@ -34,7 +37,7 @@
       }
     ]
   },
-  "style": "v2",
   "componentFramework": "glass-easel",
+  "sitemapLocation": "sitemap.json",
   "lazyCodeLoading": "requiredComponents"
 }

+ 6 - 0
src/app.ts

@@ -0,0 +1,6 @@
+App({
+  globalData: {},
+  onLaunch() {
+
+  },
+})

+ 9 - 0
src/components/HelloWorld/HelloWorld.json

@@ -0,0 +1,9 @@
+{
+  "$schema": "https://vite.weapp.dev/component.json",
+  "component": true,
+  // 组件需要设置成 "apply-shared" 才能使用 app.wxss 里的全局样式
+  "styleIsolation": "apply-shared",
+  "usingComponents": {
+    "van-button": "@vant/weapp/button/index"
+  }
+}

+ 1 - 0
src/components/HelloWorld/HelloWorld.scss

@@ -0,0 +1 @@
+/* Tailwind CSS utilities are applied directly in the template. */

+ 63 - 0
src/components/HelloWorld/HelloWorld.ts

@@ -0,0 +1,63 @@
+import { wpi } from 'wevu/api'
+
+interface HelloWorldLink {
+  text: string
+  url: string
+  variant?: 'ghost'
+}
+
+Component({
+  properties: {
+    title: {
+      type: String,
+      value: 'Hello weapp-vite',
+    },
+    description: {
+      type: String,
+      value: '欢迎使用 weapp-vite 模板,这里演示了如何在组件内快速放置介绍信息。',
+    },
+    docs: {
+      type: String,
+      value: '',
+    },
+    links: {
+      type: Array,
+      value: [] as HelloWorldLink[],
+    },
+  },
+  lifetimes: {
+    attached() {
+      if (!this.data.links?.length && this.data.docs) {
+        this.setData({
+          links: [
+            {
+              text: '复制文档链接',
+              url: this.data.docs,
+            },
+          ],
+        })
+      }
+    },
+  },
+  methods: {
+    async copyLink(event: WechatMiniprogram.TouchEvent) {
+      const url = event.currentTarget.dataset?.url as string | undefined
+      if (!url) {
+        return
+      }
+
+      try {
+        await wpi.setClipboardData({ data: url })
+        await wpi.showToast({
+          title: '链接已复制',
+          icon: 'success',
+          duration: 1500,
+        })
+      }
+      catch (error) {
+        // eslint-disable-next-line no-console
+        console.error('复制链接失败', error)
+      }
+    },
+  },
+})

+ 19 - 0
src/components/HelloWorld/HelloWorld.wxml

@@ -0,0 +1,19 @@
+<view class="w-full max-w-[640rpx] mx-auto rounded-3xl p-8 bg-white/95 shadow-xl shadow-sky-200/40 flex flex-col gap-6">
+  <view class="text-3xl font-semibold text-sky-500">{{title}}</view>
+  <view class="text-lg leading-relaxed text-slate-700">{{description}}</view>
+  <view wx:if="{{links.length}}" class="flex flex-wrap gap-4">
+    <van-button
+      wx:for="{{links}}"
+      wx:key="url"
+      size="small"
+      type="{{item.variant === 'ghost' ? 'default' : 'primary'}}"
+      plain="{{item.variant === 'ghost'}}"
+      data-url="{{item.url}}"
+      bindtap="copyLink"
+      custom-class="px-5 py-2 rounded-full text-sm font-medium transition active:scale-95"
+    >
+      {{item.text}}
+    </van-button>
+  </view>
+  <view wx:if="{{links.length}}" class="text-xs text-slate-400">复制后可直接粘贴到浏览器访问</view>
+</view>

+ 7 - 0
src/components/banner-swiper/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {
+    "svg-icon": "weapp-svg-icon/index"
+  }
+}

+ 31 - 0
src/components/banner-swiper/index.ts

@@ -0,0 +1,31 @@
+Component({
+  properties: {
+    list: {
+      type: Array,
+      value: [],
+    },
+  },
+
+  data: {
+    current: 0,
+  },
+
+  methods: {
+    onChange(e: WechatMiniprogram.SwiperChange) {
+      this.setData({ current: e.detail.current })
+    },
+    onPrev() {
+      const { current, list } = this.data as { current: number; list: unknown[] }
+      if (!list.length) return
+      this.setData({ current: (current - 1 + list.length) % list.length })
+    },
+    onNext() {
+      const { current, list } = this.data as { current: number; list: unknown[] }
+      if (!list.length) return
+      this.setData({ current: (current + 1) % list.length })
+    },
+    onTapItem(e: WechatMiniprogram.TouchEvent) {
+      this.triggerEvent('select', { index: e.currentTarget.dataset.index })
+    },
+  },
+})

+ 37 - 0
src/components/banner-swiper/index.wxml

@@ -0,0 +1,37 @@
+<view class="banner relative overflow-hidden rounded-2xl border border-[#d1aa58] bg-[#7a2f27]">
+  <swiper
+    class="banner__swiper"
+    circular
+    autoplay
+    interval="{{4200}}"
+    duration="{{450}}"
+    current="{{current}}"
+    bindchange="onChange"
+  >
+    <swiper-item wx:for="{{list}}" wx:key="title">
+      <view class="relative w-full h-full" data-index="{{index}}" bindtap="onTapItem">
+        <image class="w-full h-full opacity-75" src="{{item.image}}" mode="aspectFill" />
+        <view class="absolute inset-0 bg-gradient-to-t from-[#52241e] via-[#52241e]/25 to-transparent"></view>
+        <view class="absolute top-3 right-3 rounded-full bg-[#f6e2aa]/90 px-2 py-1 text-[10px] font-semibold text-[#873325]">{{item.tag}}</view>
+        <view class="absolute inset-x-4 bottom-4 text-white">
+          <text class="block text-[11px] tracking-[.18em] text-[#f6d98c]">{{item.enTitle}}</text>
+          <view class="mt-1 text-xl font-bold">{{item.title}}</view>
+          <view class="mt-1 text-[11px] text-[#f7e9d2]">{{item.subtitle}}</view>
+        </view>
+      </view>
+    </swiper-item>
+  </swiper>
+  <view class="absolute left-2 top-1/2 -translate-y-1/2 w-7 h-7 rounded-full bg-black/20 text-white flex items-center justify-center" bindtap="onPrev">
+    <svg-icon name="solar:alt-arrow-left-linear" size="16px" color="#ffffff" />
+  </view>
+  <view class="absolute right-2 top-1/2 -translate-y-1/2 w-7 h-7 rounded-full bg-black/20 text-white flex items-center justify-center" bindtap="onNext">
+    <svg-icon name="solar:alt-arrow-right-linear" size="16px" color="#ffffff" />
+  </view>
+  <view class="absolute bottom-3 right-4 flex gap-1.5">
+    <view
+      wx:for="{{list}}"
+      wx:key="index"
+      class="{{current === index ? 'w-4 h-1.5 bg-[#f8dc99]' : 'w-1.5 h-1.5 bg-white/65'}} rounded-full"
+    ></view>
+  </view>
+</view>

+ 9 - 0
src/components/banner-swiper/index.wxss

@@ -0,0 +1,9 @@
+.banner {
+  height: 178px;
+  box-shadow: 0 8px 18px rgba(113, 52, 36, 0.18);
+}
+
+.banner__swiper {
+  width: 100%;
+  height: 178px;
+}

+ 7 - 0
src/components/news-item/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {
+    "svg-icon": "weapp-svg-icon/index"
+  }
+}

+ 17 - 0
src/components/news-item/index.ts

@@ -0,0 +1,17 @@
+import Toast from '@vant/weapp/toast/toast'
+
+Component({
+  properties: {
+    item: {
+      type: Object,
+      value: {},
+    },
+  },
+
+  methods: {
+    onTap() {
+      const title = (this.data.item as { title: string }).title
+      Toast(`阅读:${title}`)
+    },
+  },
+})

+ 10 - 0
src/components/news-item/index.wxml

@@ -0,0 +1,10 @@
+<view class="w-full px-3 py-3 flex gap-2.5" bindtap="onTap">
+  <view class="mt-0.5 w-7 h-7 flex-none rounded-lg flex items-center justify-center" style="background-color: {{item.iconBg}};">
+    <svg-icon name="{{item.icon}}" size="15px" color="{{item.iconColor}}" />
+  </view>
+  <view class="min-w-0 flex-1">
+    <text class="block text-[12px] text-[#51433e] truncate">{{item.title}}</text>
+    <text class="block mt-1 text-[9px] text-[#9a8c82]">{{item.subtitle}}</text>
+  </view>
+  <text class="flex-none text-[9px] text-[#a99686]">{{item.time}}</text>
+</view>

+ 1 - 0
src/components/news-item/index.wxss

@@ -0,0 +1 @@
+/* news-item */

+ 5 - 0
src/components/page-header/index.json

@@ -0,0 +1,5 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {}
+}

+ 49 - 0
src/components/page-header/index.ts

@@ -0,0 +1,49 @@
+import { svgToDataUri } from '../../utils/svgIcon'
+
+const LOGO_SVG =
+  '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">' +
+  '<path d="M13 31C28 26 36 16 42 7c5 8 10 13 18 16 8-3 13-8 18-16 6 9 14 19 29 24-12-9-19-18-25-29-6 8-13 13-22 16C51 15 44 10 38 2 32 13 25 22 13 31Z" fill="#f05a25"/>' +
+  '<path d="M7 43c18 8 34-2 46-2 7 0 10 2 7 5-3 3-8 4-8 9 0 5 8 7 16 5 11-3 18-10 31-9-17-8-28 0-40 4-8 3-13 1-13-2 0-4 8-7 8-10 0-6-13-4-23-2-9 2-14 4-17 2Z" fill="#008db8"/>' +
+  '<path d="M57 55c13-3 25-10 42-4-17-3-26 7-40 10-9 2-16 0-17-4 3 1 8 1 15-2Z" fill="#0755aa"/>' +
+  '<text fill="#0b397d" font-family="Arial, sans-serif" font-size="19" font-weight="700" letter-spacing="1" x="10" y="82">MINNAN</text>' +
+  '<rect fill="#c8312b" height="17" rx="1" width="14" x="76" y="66"></rect><text fill="white" font-family="serif" font-size="12" font-weight="700" x="78" y="80">闽</text>' +
+  '</svg>'
+
+Component({
+  properties: {
+    title: {
+      type: String,
+      value: '闽南文化(漳州)生态保护区',
+    },
+    subtitle: {
+      type: String,
+      value: '一城烟火 · 千年传承',
+    },
+  },
+
+  data: {
+    headerPaddingTop: 32,
+    rightPadding: 16,
+    logoUrl: '',
+  },
+
+  lifetimes: {
+    attached() {
+      const info = wx.getSystemInfoSync()
+      let rightPadding = 16
+      try {
+        const rect = wx.getMenuButtonBoundingClientRect()
+        if (rect && rect.left > 0) {
+          rightPadding = info.windowWidth - rect.left + 8
+        }
+      } catch (e) {
+        // ignore
+      }
+      this.setData({
+        headerPaddingTop: (info.statusBarHeight || 20) + 12,
+        rightPadding,
+        logoUrl: svgToDataUri(LOGO_SVG),
+      })
+    },
+  },
+})

+ 11 - 0
src/components/page-header/index.wxml

@@ -0,0 +1,11 @@
+<view class="page-header" style="padding-top: {{headerPaddingTop}}px;">
+  <view class="page-header__row flex items-center" style="padding-right: {{rightPadding}}px;">
+    <view class="flex items-center gap-2.5">
+      <view class="page-header__logo" style="background-image: {{logoUrl}};"></view>
+      <view class="min-w-0">
+        <text class="block whitespace-nowrap text-[16px] font-bold tracking-[-.02em] text-[#9d3829] leading-tight">{{title}}</text>
+        <text class="block text-[10px] text-[#9f8660] mt-0.5">{{subtitle}}</text>
+      </view>
+    </view>
+  </view>
+</view>

+ 15 - 0
src/components/page-header/index.wxss

@@ -0,0 +1,15 @@
+.page-header {
+  flex: none;
+  padding-left: 20px;
+  padding-bottom: 8px;
+  background-color: rgba(249, 243, 231, 0.95);
+  border-bottom: 1px solid #eadcc5;
+}
+
+.page-header__logo {
+  width: 36px;
+  height: 36px;
+  flex: none;
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+}

+ 7 - 0
src/components/recommend-card/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {
+    "svg-icon": "weapp-svg-icon/index"
+  }
+}

+ 35 - 0
src/components/recommend-card/index.ts

@@ -0,0 +1,35 @@
+import Toast from '@vant/weapp/toast/toast'
+
+Component({
+  properties: {
+    item: {
+      type: Object,
+      value: {},
+    },
+  },
+
+  data: {
+    saved: false,
+  },
+
+  methods: {
+    onOpen() {
+      const title = (this.data.item as { title: string }).title
+      Toast(`已进入:${title}`)
+    },
+    onSave() {
+      const title = (this.data.item as { title: string }).title
+      const saved = !this.data.saved
+      this.setData({ saved })
+      Toast(saved ? `已收藏:${title}` : `已取消收藏:${title}`)
+    },
+    onShare() {
+      const title = (this.data.item as { title: string }).title
+      Toast(`已生成"${title}"分享卡片`)
+    },
+    onNav() {
+      const title = (this.data.item as { title: string }).title
+      Toast(`导航已为您规划至${title}`)
+    },
+  },
+})

+ 21 - 0
src/components/recommend-card/index.wxml

@@ -0,0 +1,21 @@
+<view class="shrink-0 w-[160px] overflow-hidden rounded-2xl bg-white border border-[#eadcc5] shadow-sm">
+  <view class="h-[100px] relative" style="background-color: {{item.imageBg}};" bindtap="onOpen">
+    <image class="w-full h-full opacity-70" src="{{item.image}}" mode="aspectFill" />
+    <view class="absolute left-2 top-2 rounded-md px-1.5 py-0.5 text-[9px] text-white" style="background-color: {{item.tagBg}};">{{item.tag}}</view>
+  </view>
+  <view class="p-2.5">
+    <text class="block text-[12px] font-bold text-[#51433e] truncate">{{item.title}}</text>
+    <text class="block mt-1 text-[9px] text-[#8c7e74]">{{item.subtitle}}</text>
+    <view class="mt-2 flex justify-between items-center">
+      <view class="flex items-center justify-center" bindtap="onSave">
+        <svg-icon name="{{saved ? 'solar:bookmark-bold' : 'solar:bookmark-linear'}}" size="17px" color="#b48732" />
+      </view>
+      <view class="flex items-center justify-center" bindtap="onShare">
+        <svg-icon name="solar:share-linear" size="17px" color="#a83f2d" />
+      </view>
+      <view class="flex items-center justify-center" bindtap="onNav">
+        <svg-icon name="solar:map-point-linear" size="17px" color="#59755d" />
+      </view>
+    </view>
+  </view>
+</view>

+ 1 - 0
src/components/recommend-card/index.wxss

@@ -0,0 +1 @@
+/* recommend-card */

+ 7 - 0
src/components/section-header/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {
+    "svg-icon": "weapp-svg-icon/index"
+  }
+}

+ 26 - 0
src/components/section-header/index.ts

@@ -0,0 +1,26 @@
+Component({
+  properties: {
+    title: {
+      type: String,
+      value: '',
+    },
+    barColor: {
+      type: String,
+      value: '#b48732',
+    },
+    moreText: {
+      type: String,
+      value: '查看全部',
+    },
+    showMore: {
+      type: Boolean,
+      value: true,
+    },
+  },
+
+  methods: {
+    onMore() {
+      this.triggerEvent('more')
+    },
+  },
+})

+ 10 - 0
src/components/section-header/index.wxml

@@ -0,0 +1,10 @@
+<view class="flex items-end justify-between">
+  <view class="flex items-center gap-2">
+    <view class="w-1 h-5 rounded-full" style="background-color: {{barColor}};"></view>
+    <text class="text-[16px] font-bold text-[#983726]">{{title}}</text>
+  </view>
+  <view wx:if="{{showMore}}" class="flex items-center text-[11px] text-[#a83f2d]" bindtap="onMore">
+    <text>{{moreText}}</text>
+    <svg-icon name="solar:alt-arrow-right-linear" size="13px" color="#a83f2d" />
+  </view>
+</view>

+ 1 - 0
src/components/section-header/index.wxss

@@ -0,0 +1 @@
+/* section-header */

+ 7 - 0
src/components/volunteer-card/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {
+    "svg-icon": "weapp-svg-icon/index"
+  }
+}

+ 17 - 0
src/components/volunteer-card/index.ts

@@ -0,0 +1,17 @@
+import Toast from '@vant/weapp/toast/toast'
+
+Component({
+  properties: {
+    item: {
+      type: Object,
+      value: {},
+    },
+  },
+
+  methods: {
+    onTap() {
+      const title = (this.data.item as { title: string }).title
+      Toast(`已打开:${title}`)
+    },
+  },
+})

+ 15 - 0
src/components/volunteer-card/index.wxml

@@ -0,0 +1,15 @@
+<view class="w-full rounded-2xl border border-[#eadcc5] bg-white/90 p-3 shadow-sm" bindtap="onTap">
+  <view class="flex items-center gap-2.5">
+    <view class="w-9 h-9 flex-none rounded-xl flex items-center justify-center" style="background-color: {{item.iconBg}};">
+      <svg-icon name="{{item.icon}}" size="19px" color="{{item.iconColor}}" />
+    </view>
+    <view class="min-w-0 flex-1">
+      <text class="block text-[12px] text-[#51433e] truncate">{{item.title}}</text>
+    </view>
+    <view class="rounded-md px-1.5 py-1 text-[9px]" style="background-color: {{item.statusBg}}; color: {{item.statusColor}};">{{item.status}}</view>
+  </view>
+  <view class="mt-2 flex items-center justify-between pl-[46px] text-[9px] text-[#a99686]">
+    <text>{{item.meta}}</text>
+    <text class="text-[#a83f2d]">{{item.actionText}} ›</text>
+  </view>
+</view>

+ 1 - 0
src/components/volunteer-card/index.wxss

@@ -0,0 +1 @@
+/* volunteer-card */

+ 6 - 0
src/custom-tab-bar/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "svg-icon": "weapp-svg-icon/index"
+  }
+}

+ 32 - 0
src/custom-tab-bar/index.ts

@@ -0,0 +1,32 @@
+interface TabItem {
+  pagePath: string
+  text: string
+  icon: string
+  inactiveIcon: string
+}
+
+Component({
+  data: {
+    selected: 0,
+    color: '#a79d93',
+    activeColor: '#a83f2d',
+    list: [
+      { pagePath: '/pages/home/home', text: '首页', icon: 'ph:house-fill', inactiveIcon: 'ph:house-duotone' },
+      { pagePath: '/pages/inherit/inherit', text: '传承', icon: 'ph:hand-heart-fill', inactiveIcon: 'ph:hand-heart-duotone' },
+      { pagePath: '/pages/news/news', text: '资讯', icon: 'ph:compass-fill', inactiveIcon: 'ph:compass-duotone' },
+      { pagePath: '/pages/travel/travel', text: '文旅', icon: 'ph:map-trifold-fill', inactiveIcon: 'ph:map-trifold-duotone' },
+      { pagePath: '/pages/user/user', text: '我的', icon: 'ph:user-circle-fill', inactiveIcon: 'ph:user-circle-duotone' },
+    ] as TabItem[],
+  },
+
+  methods: {
+    onTap(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const list = this.data.list as TabItem[]
+      const item = list[index]
+      if (item && index !== this.data.selected) {
+        wx.switchTab({ url: item.pagePath })
+      }
+    },
+  },
+})

+ 16 - 0
src/custom-tab-bar/index.wxml

@@ -0,0 +1,16 @@
+<view class="tab-bar">
+  <view
+    wx:for="{{list}}"
+    wx:key="pagePath"
+    class="tab-bar__item"
+    data-index="{{index}}"
+    bindtap="onTap"
+  >
+    <svg-icon
+      name="{{selected === index ? item.icon : item.inactiveIcon}}"
+      size="21px"
+      color="{{selected === index ? activeColor : color}}"
+    />
+    <text class="tab-bar__label {{selected === index ? 'tab-bar__label--active' : ''}}">{{item.text}}</text>
+  </view>
+</view>

+ 29 - 0
src/custom-tab-bar/index.wxss

@@ -0,0 +1,29 @@
+.tab-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 12px;
+  padding-bottom: calc(8px + env(safe-area-inset-bottom));
+  padding-bottom: calc(8px + constant(safe-area-inset-bottom));
+  background-color: #fffaf1;
+  border-top: 1px solid #e9dbc5;
+}
+
+.tab-bar__item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 2px;
+  width: 48px;
+}
+
+.tab-bar__label {
+  font-size: 10px;
+  line-height: 1;
+  color: #a79d93;
+}
+
+.tab-bar__label--active {
+  color: #a83f2d;
+  font-weight: 600;
+}

+ 13 - 0
src/pages/home/home.json

@@ -0,0 +1,13 @@
+{
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "page-header": "/components/page-header/index",
+    "banner-swiper": "/components/banner-swiper/index",
+    "section-header": "/components/section-header/index",
+    "recommend-card": "/components/recommend-card/index",
+    "news-item": "/components/news-item/index",
+    "volunteer-card": "/components/volunteer-card/index",
+    "svg-icon": "weapp-svg-icon/index",
+    "van-toast": "@vant/weapp/toast/index"
+  }
+}

+ 194 - 0
src/pages/home/home.ts

@@ -0,0 +1,194 @@
+import Toast from '@vant/weapp/toast/toast'
+
+interface BannerItem {
+  image: string
+  tag: string
+  topic: string
+  enTitle: string
+  title: string
+  subtitle: string
+}
+
+interface QuickEntry {
+  icon: string
+  iconBg: string
+  iconColor: string
+  iconBorder: string
+  text: string
+}
+
+Component({
+  data: {
+    banners: [
+      {
+        image: 'https://modao.cc/agent-py/media/generated_images/2026-08-06/8b37d6e9e1d74b9fb48c953f0e199c71.jpg',
+        tag: '古城实景',
+        topic: '古城慢游专题',
+        enTitle: 'Z H A N G Z H O U',
+        title: '红砖古厝里的夏日烟火',
+        subtitle: '在千年古城,遇见闽南生活美学',
+      },
+      {
+        image: 'https://modao.cc/agent-py/media/generated_images/2026-08-06/e6fa0fac0a834dcc9389dccae0c8308d.jpg',
+        tag: '国家级非遗',
+        topic: '布袋木偶非遗专题',
+        enTitle: 'LIVING HERITAGE',
+        title: '掌中乾坤 · 布袋木偶戏',
+        subtitle: '一方戏台,演尽闽南百年故事',
+      },
+      {
+        image: 'https://modao.cc/agent-py/media/generated_images/2026-08-06/e00e4cebe5254522aeb935928765429f.jpg',
+        tag: '本周推荐',
+        topic: '非遗市集专题',
+        enTitle: 'CULTURAL MARKET',
+        title: '一场热闹的非遗市集',
+        subtitle: '手作、食味与匠心,在此相逢',
+      },
+    ] as BannerItem[],
+
+    quickEntries: [
+      { icon: 'ph:mask-happy-duotone', iconBg: '#f9e7df', iconColor: '#a83f2d', iconBorder: '#edcabd', text: '非遗项目' },
+      { icon: 'ph:person-simple-tai-chi-duotone', iconBg: '#fff1d8', iconColor: '#a87a27', iconBorder: '#ead4a3', text: '非遗传承人' },
+      { icon: 'ph:shield-check-duotone', iconBg: '#e8f0e7', iconColor: '#5f795d', iconBorder: '#cbdcc8', text: '重点区域' },
+      { icon: 'ph:building-duotone', iconBg: '#f9e8e1', iconColor: '#a83f2d', iconBorder: '#ead0c8', text: '文物古迹' },
+      { icon: 'ph:map-pin-area-duotone', iconBg: '#edf2e8', iconColor: '#5f795d', iconBorder: '#cbdcc8', text: '文化地图' },
+      { icon: 'ph:hand-heart-duotone', iconBg: '#fff1d8', iconColor: '#a87a27', iconBorder: '#ead4a3', text: '传承体验' },
+      { icon: 'ph:books-duotone', iconBg: '#f2ebe3', iconColor: '#8b4935', iconBorder: '#dfcbb8', text: '理论研究' },
+      { icon: 'ph:trophy-duotone', iconBg: '#e8f0e7', iconColor: '#5f795d', iconBorder: '#cbdcc8', text: '特色成果' },
+    ] as QuickEntry[],
+
+    recommends: [
+      {
+        image: 'https://modao.cc/agent-py/media/generated_images/2026-08-06/57cb448b9cad417eaff26bd1ea34fc10.jpg',
+        imageBg: '#762d25',
+        tag: '非遗体验',
+        tagBg: 'rgba(159,55,40,0.85)',
+        title: '木偶戏沉浸体验',
+        subtitle: '古城剧场 · 90分钟',
+      },
+      {
+        image: 'https://modao.cc/agent-py/media/user_assets/2026-08-20/4c62b95d09954e9dbfb8eefffa11317c.png',
+        imageBg: '#5b756a',
+        tag: '打卡点',
+        tagBg: 'rgba(89,117,93,0.9)',
+        title: '云洞岩摩崖石刻',
+        subtitle: '龙文区 · 山野清凉',
+      },
+      {
+        image: 'https://modao.cc/agent-py/media/generated_images/2026-08-06/c442391b73bf46f99a6d09d414c04306.jpg',
+        imageBg: '#a96638',
+        tag: '亲子研学',
+        tagBg: 'rgba(167,103,55,0.9)',
+        title: '土楼营造技艺研学',
+        subtitle: '南靖县 · 周末成团',
+      },
+    ],
+
+    news: [
+      {
+        icon: 'ph:microphone-stage-duotone',
+        iconBg: '#f9e5df',
+        iconColor: '#a83f2d',
+        title: '漳州古城周末非遗展演安排公布',
+        subtitle: '精彩戏曲、南音与传统技艺轮番上演',
+        time: '今天',
+      },
+      {
+        icon: 'ph:ticket-duotone',
+        iconBg: '#fff0d8',
+        iconColor: '#a87a27',
+        title: '暑期文旅惠民消费券即将发放',
+        subtitle: '覆盖景区、文创、研学等多个场景',
+        time: '08-05',
+      },
+      {
+        icon: 'ph:calendar-heart-duotone',
+        iconBg: '#e8f0e7',
+        iconColor: '#5f795d',
+        title: '中秋博饼民俗活动报名通道开启',
+        subtitle: '感受闽南传统团圆仪式与欢乐',
+        time: '08-03',
+      },
+      {
+        icon: 'ph:scroll-duotone',
+        iconBg: '#f2ebe3',
+        iconColor: '#78675a',
+        title: '闽南文化生态保护区建设政策解读',
+        subtitle: '守护文化根脉,激活传承新活力',
+        time: '08-01',
+      },
+    ],
+
+    volunteers: [
+      {
+        icon: 'ph:megaphone-duotone',
+        iconBg: '#f9e5df',
+        iconColor: '#a83f2d',
+        title: '古城文化讲解志愿队',
+        status: '招募中',
+        statusBg: '#e8f0e7',
+        statusColor: '#5f795d',
+        meta: '每周末 · 漳州古城',
+        actionText: '查看详情',
+      },
+      {
+        icon: 'ph:hand-heart-duotone',
+        iconBg: '#fff0d8',
+        iconColor: '#a87a27',
+        title: '非遗传承助力团',
+        status: '长期开放',
+        statusBg: '#fff0d8',
+        statusColor: '#a87a27',
+        meta: '灵活参与 · 线上报名',
+        actionText: '立即参与',
+      },
+      {
+        icon: 'ph:camera-duotone',
+        iconBg: '#e8f0e7',
+        iconColor: '#5f795d',
+        title: '乡村文化记录计划',
+        status: '即将开始',
+        statusBg: '#f2ebe3',
+        statusColor: '#78675a',
+        meta: '每月主题 · 南靖县',
+        actionText: '预约报名',
+      },
+    ],
+  },
+
+  pageLifetimes: {
+    show() {
+      if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+        this.getTabBar().setData({ selected: 0 })
+      }
+    },
+  },
+
+  methods: {
+    onBannerTap(e: WechatMiniprogram.CustomEvent) {
+      const index = e.detail.index as number
+      const banners = this.data.banners as BannerItem[]
+      const banner = banners[index]
+      if (banner) {
+        Toast(`已进入:${banner.topic}`)
+      }
+    },
+    onQuickTap(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const quickEntries = this.data.quickEntries as QuickEntry[]
+      const item = quickEntries[index]
+      if (item) {
+        Toast(`${item.text}功能建设中`)
+      }
+    },
+    onMoreRecommend() {
+      Toast('更多精选内容正在加载')
+    },
+    onMoreNews() {
+      Toast('已切换到资讯列表')
+    },
+    onMoreVolunteer() {
+      Toast('更多志愿组织正在招募')
+    },
+  },
+})

+ 58 - 0
src/pages/home/home.wxml

@@ -0,0 +1,58 @@
+<view class="h-screen flex flex-col paper-pattern">
+  <page-header />
+
+  <scroll-view class="flex-1 min-h-0" scroll-y>
+    <view class="tabbar-safe-bottom">
+      <view class="px-4 pt-4">
+        <banner-swiper list="{{banners}}" bind:select="onBannerTap" />
+      </view>
+
+      <view class="mx-4 mt-4 rounded-2xl bg-white/85 border border-[#eadcc5] px-2 py-3 shadow-sm">
+        <view class="grid grid-cols-4 gap-x-1 gap-y-3 px-1">
+          <view
+            wx:for="{{quickEntries}}"
+            wx:key="text"
+            class="flex w-full flex-col items-center gap-1.5"
+            data-index="{{index}}"
+            bindtap="onQuickTap"
+          >
+            <view
+              class="w-11 h-11 rounded-2xl flex items-center justify-center border"
+              style="background-color: {{item.iconBg}}; border-color: {{item.iconBorder}};"
+            >
+              <svg-icon name="{{item.icon}}" size="23px" color="{{item.iconColor}}" />
+            </view>
+            <text class="text-[10px] font-medium text-[#4d4540]">{{item.text}}</text>
+          </view>
+        </view>
+      </view>
+
+      <view class="mt-5">
+        <view class="px-4">
+          <section-header title="精选推荐" barColor="#b48732" moreText="更多" bind:more="onMoreRecommend" />
+        </view>
+        <scroll-view class="mt-3" scroll-x>
+          <view class="inline-flex gap-3 pl-4 pr-4">
+            <recommend-card wx:for="{{recommends}}" wx:key="title" item="{{item}}" />
+          </view>
+        </scroll-view>
+      </view>
+
+      <view class="mt-5 px-4">
+        <section-header title="最新资讯" barColor="#a83f2d" moreText="查看全部" bind:more="onMoreNews" />
+        <view class="mt-3 overflow-hidden rounded-2xl border border-[#eadcc5] bg-white/90 divide-y divide-[#f0e6d5]">
+          <news-item wx:for="{{news}}" wx:key="title" item="{{item}}" />
+        </view>
+      </view>
+
+      <view class="mt-5 px-4">
+        <section-header title="志愿者组织" barColor="#b48732" moreText="查看全部" bind:more="onMoreVolunteer" />
+        <view class="mt-3 space-y-2.5">
+          <volunteer-card wx:for="{{volunteers}}" wx:key="title" item="{{item}}" />
+        </view>
+      </view>
+    </view>
+  </scroll-view>
+
+  <van-toast id="van-toast" />
+</view>

+ 1 - 0
src/pages/home/home.wxss

@@ -0,0 +1 @@
+/* home */

miniprogram/pages/inherit/inherit.json → src/pages/inherit/inherit.json


+ 11 - 0
src/pages/inherit/inherit.ts

@@ -0,0 +1,11 @@
+Component({
+  data: {},
+
+  pageLifetimes: {
+    show() {
+      if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+        this.getTabBar().setData({ selected: 1 })
+      }
+    },
+  },
+})

miniprogram/pages/inherit/inherit.wxml → src/pages/inherit/inherit.wxml


miniprogram/pages/inherit/inherit.wxss → src/pages/inherit/inherit.wxss


+ 4 - 0
src/pages/news/news.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "资讯",
+  "usingComponents": {}
+}

+ 11 - 0
src/pages/news/news.ts

@@ -0,0 +1,11 @@
+Component({
+  data: {},
+
+  pageLifetimes: {
+    show() {
+      if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+        this.getTabBar().setData({ selected: 2 })
+      }
+    },
+  },
+})

+ 1 - 1
miniprogram/pages/home/home.wxml

@@ -1,4 +1,4 @@
 <view class="min-h-screen flex flex-col items-center justify-center">
-  <text class="font-semibold">首页</text>
+  <text class="font-semibold">资讯</text>
   <text class="placeholder-desc">内容占位符</text>
 </view>

miniprogram/pages/home/home.wxss → src/pages/news/news.wxss


+ 4 - 0
src/pages/travel/travel.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "文旅",
+  "usingComponents": {}
+}

+ 11 - 0
src/pages/travel/travel.ts

@@ -0,0 +1,11 @@
+Component({
+  data: {},
+
+  pageLifetimes: {
+    show() {
+      if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+        this.getTabBar().setData({ selected: 3 })
+      }
+    },
+  },
+})

miniprogram/pages/travel/travel.wxml → src/pages/travel/travel.wxml


miniprogram/pages/travel/travel.wxss → src/pages/travel/travel.wxss


miniprogram/pages/user/user.json → src/pages/user/user.json


+ 11 - 0
src/pages/user/user.ts

@@ -0,0 +1,11 @@
+Component({
+  data: {},
+
+  pageLifetimes: {
+    show() {
+      if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+        this.getTabBar().setData({ selected: 4 })
+      }
+    },
+  },
+})

miniprogram/pages/user/user.wxml → src/pages/user/user.wxml


miniprogram/pages/user/user.wxss → src/pages/user/user.wxss


+ 10 - 0
src/sitemap.json

@@ -0,0 +1,10 @@
+{
+  "$schema": "https://vite.weapp.dev/sitemap.json",
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [
+    {
+      "action": "allow",
+      "page": "*"
+    }
+  ]
+}

+ 5 - 0
src/theme.json

@@ -0,0 +1,5 @@
+{
+  "$schema": "https://vite.weapp.dev/theme.json",
+  "light": {},
+  "dark": {}
+}

+ 10 - 0
src/utils/svgIcon.ts

@@ -0,0 +1,10 @@
+// 通用 svg 工具:把内联 svg 字符串转成 data URI。
+// 图标统一由 weapp-svg-icon 组件(基于 @iconify-json 图标包)渲染,
+// 此处仅保留品牌 logo 等少量非图标集的本地 svg 转换能力。
+
+export function svgToDataUri(svg: string): string {
+  const normalized = svg.includes('xmlns')
+    ? svg
+    : svg.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"')
+  return `url("data:image/svg+xml;charset=utf-8,${encodeURIComponent(normalized)}")`
+}

+ 3 - 0
src/utils/util.ts

@@ -0,0 +1,3 @@
+export function hello() {
+  return 'world'
+}

+ 5 - 0
stylelint.config.js

@@ -0,0 +1,5 @@
+import { icebreaker } from '@icebreakers/stylelint-config'
+
+export default icebreaker({
+  miniProgram: true,
+})

+ 0 - 14
tailwind.config.js

@@ -1,14 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-module.exports = {
-  content: [
-    './miniprogram/**/*.{wxml,wxss,ts,js}',
-    '!./miniprogram/tailwind/tailwind.wxss',
-  ],
-  corePlugins: {
-    preflight: false,
-  },
-  theme: {
-    extend: {},
-  },
-  plugins: [],
-}

+ 0 - 0
tsconfig.json


Some files were not shown because too many files changed in this diff