Explorar el Código

完成5个基础页面

快乐的梦鱼 hace 1 mes
padre
commit
fa9d68c1e0
Se han modificado 70 ficheros con 2312 adiciones y 158 borrados
  1. 3 1
      .npmrc
  2. 7 0
      AGENTS.md
  3. 47 3
      README.md
  4. 2 15
      patches/weapp-svg-icon+1.0.0.patch
  5. 4 0
      src/app.css
  6. 3 1
      src/app.json
  7. 0 9
      src/components/HelloWorld/HelloWorld.json
  8. 0 1
      src/components/HelloWorld/HelloWorld.scss
  9. 0 63
      src/components/HelloWorld/HelloWorld.ts
  10. 0 19
      src/components/HelloWorld/HelloWorld.wxml
  11. 7 0
      src/components/favorites-panel/index.json
  12. 51 0
      src/components/favorites-panel/index.ts
  13. 26 0
      src/components/favorites-panel/index.wxml
  14. 1 0
      src/components/favorites-panel/index.wxss
  15. 7 0
      src/components/feature-card/index.json
  16. 21 0
      src/components/feature-card/index.ts
  17. 17 0
      src/components/feature-card/index.wxml
  18. 1 0
      src/components/feature-card/index.wxss
  19. 7 0
      src/components/heritage-card/index.json
  20. 24 0
      src/components/heritage-card/index.ts
  21. 16 0
      src/components/heritage-card/index.wxml
  22. 25 0
      src/components/heritage-card/index.wxss
  23. 7 0
      src/components/heritage-header/index.json
  24. 31 0
      src/components/heritage-header/index.ts
  25. 27 0
      src/components/heritage-header/index.wxml
  26. 57 0
      src/components/heritage-header/index.wxss
  27. 7 0
      src/components/menu-item/index.json
  28. 62 0
      src/components/menu-item/index.ts
  29. 21 0
      src/components/menu-item/index.wxml
  30. 1 0
      src/components/menu-item/index.wxss
  31. 7 0
      src/components/news-header/index.json
  32. 28 0
      src/components/news-header/index.ts
  33. 26 0
      src/components/news-header/index.wxml
  34. 58 0
      src/components/news-header/index.wxss
  35. 4 0
      src/components/news-item/index.ts
  36. 2 2
      src/components/news-item/index.wxml
  37. 7 0
      src/components/profile-header/index.json
  38. 34 0
      src/components/profile-header/index.ts
  39. 16 0
      src/components/profile-header/index.wxml
  40. 31 0
      src/components/profile-header/index.wxss
  41. 7 0
      src/components/route-card/index.json
  42. 27 0
      src/components/route-card/index.ts
  43. 17 0
      src/components/route-card/index.wxml
  44. 1 0
      src/components/route-card/index.wxss
  45. 0 1
      src/components/section-header/index.ts
  46. 2 2
      src/components/section-header/index.wxml
  47. 7 0
      src/components/travel-header/index.json
  48. 25 0
      src/components/travel-header/index.ts
  49. 21 0
      src/components/travel-header/index.wxml
  50. 42 0
      src/components/travel-header/index.wxss
  51. 9 3
      src/custom-tab-bar/index.wxss
  52. 3 1
      src/pages/home/home.wxml
  53. 1 0
      src/pages/home/home.wxss
  54. 7 2
      src/pages/inherit/inherit.json
  55. 362 1
      src/pages/inherit/inherit.ts
  56. 134 3
      src/pages/inherit/inherit.wxml
  57. 8 3
      src/pages/inherit/inherit.wxss
  58. 10 2
      src/pages/news/news.json
  59. 229 1
      src/pages/news/news.ts
  60. 104 3
      src/pages/news/news.wxml
  61. 15 3
      src/pages/news/news.wxss
  62. 9 2
      src/pages/travel/travel.json
  63. 240 1
      src/pages/travel/travel.ts
  64. 133 3
      src/pages/travel/travel.wxml
  65. 15 3
      src/pages/travel/travel.wxss
  66. 9 2
      src/pages/user/user.json
  67. 87 1
      src/pages/user/user.ts
  68. 82 3
      src/pages/user/user.wxml
  69. 8 4
      src/pages/user/user.wxss
  70. 3 0
      weapp-vite.config.ts

+ 3 - 1
.npmrc

@@ -1,2 +1,4 @@
 # 默认镜像源
-# registry=https://registry.npmmirror.com
+# registry=https://registry.npmmirror.com
+# 允许安装通过远程 tarball URL 依赖(patch-package 需要在临时目录安装依赖以生成补丁)
+allow-remote=all

+ 7 - 0
AGENTS.md

@@ -34,6 +34,13 @@
 - 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.
 
+## SVG Icons
+
+- Icons render via the `weapp-svg-icon` npm component, referenced as `weapp-svg-icon/index` in `usingComponents`.
+- Icon data is tree-shaken from `@iconify-json/ph` and `@iconify-json/solar` into a minimal set by `scripts/generate-svg-icons.mjs`.
+- After adding or changing icons in source (new `ph:*` / `solar:*` names), run `npm run gen:icons` to regenerate the minimal data, then `npx patch-package weapp-svg-icon` to refresh `patches/weapp-svg-icon+1.0.0.patch`.
+- IMPORTANT: `wv build` caches compiled npm mini-program packages by `package.json` dependency hash (see `node_modules/weapp-vite/.cache/`). Regenerating only `miniprogram_dist/index.js` does NOT invalidate this cache, so new icons silently stay missing. After `gen:icons`, clear the cache before building, e.g. remove `node_modules/weapp-vite/.cache/-.json`, so the stale `dist/miniprogram_npm/weapp-svg-icon/` is rebuilt.
+
 ## WeChat DevTools
 
 - Before using screenshot, preview, upload, automator, or `--open`, ensure WeChat DevTools is logged in and the service port is enabled.

+ 47 - 3
README.md

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

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2 - 15
patches/weapp-svg-icon+1.0.0.patch


+ 4 - 0
src/app.css

@@ -6,3 +6,7 @@
 }
 
 @source "./**/*.{wxml,js,ts,vue}";
+
+.tabbar-placeholder {
+  height: calc(58px + env(safe-area-inset-bottom, 0px));
+}

+ 3 - 1
src/app.json

@@ -7,7 +7,9 @@
     "pages/travel/travel",
     "pages/user/user"
   ],
-  "window": {},
+  "window": {
+    "navigationBarTextStyle": "black"
+  },
   "tabBar": {
     "custom": true,
     "color": "#a79d93",

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

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

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

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

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

@@ -1,63 +0,0 @@
-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)
-      }
-    },
-  },
-})

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

@@ -1,19 +0,0 @@
-<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/favorites-panel/index.json

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

+ 51 - 0
src/components/favorites-panel/index.ts

@@ -0,0 +1,51 @@
+interface FavTab {
+  key: string
+  text: string
+}
+
+interface FavContent {
+  text: string
+  url: string
+}
+
+Component({
+  data: {
+    tabs: [
+      { key: 'route', text: '收藏路线' },
+      { key: 'spot', text: '收藏景点' },
+      { key: 'food', text: '收藏美食' },
+    ] as FavTab[],
+
+    activeKey: 'route',
+
+    contents: {
+      route: { text: '土楼摄影线 · 古城逛吃线 · 东山滨海线', url: '/pages/travel/travel' },
+      spot: { text: '田螺坑土楼 · 漳州古城 · 南门湾沙滩', url: '/pages/news/news' },
+      food: { text: '阿芬卤面 · 古城四果汤 · 建国蚵仔煎', url: '/pages/news/news' },
+    } as Record<string, FavContent>,
+
+    activeContent: {
+      text: '土楼摄影线 · 古城逛吃线 · 东山滨海线',
+      url: '/pages/travel/travel',
+    } as FavContent,
+  },
+
+  methods: {
+    onTabTap(e: WechatMiniprogram.TouchEvent) {
+      const key = e.currentTarget.dataset.key as string
+      if (!key || key === this.data.activeKey) {
+        return
+      }
+      const contents = this.data.contents as Record<string, FavContent>
+      this.setData({
+        activeKey: key,
+        activeContent: contents[key],
+      })
+    },
+
+    onGoTap() {
+      const url = (this.data.activeContent as FavContent).url
+      wx.switchTab({ url })
+    },
+  },
+})

+ 26 - 0
src/components/favorites-panel/index.wxml

@@ -0,0 +1,26 @@
+<view class="rounded-2xl border border-[#e0c592] bg-white/90 p-3 shadow-sm">
+  <view class="flex items-center justify-between">
+    <text class="text-[15px] font-bold text-[#873428]">我的收藏</text>
+    <text class="text-[9px] text-[#a7834c]">一键查看心仪目的地</text>
+  </view>
+
+  <view class="mt-3 grid grid-cols-3 gap-2">
+    <view
+      wx:for="{{tabs}}"
+      wx:key="key"
+      class="rounded-xl py-2 text-[10px] text-center {{activeKey === item.key ? 'bg-[#a83f2d] text-white' : 'bg-[#f5e5c9] text-[#765347]'}}"
+      data-key="{{item.key}}"
+      bindtap="onTabTap"
+    >
+      {{item.text}}
+    </view>
+  </view>
+
+  <view class="mt-3 rounded-xl bg-[#fbf3e5] p-3">
+    <text class="block text-[10px] text-[#765f53]">{{activeContent.text}}</text>
+    <view class="mt-2 inline-flex items-center gap-1 text-[#a83f2d]" bindtap="onGoTap">
+      <text>前往对应页面</text>
+      <svg-icon name="ph:arrow-right-bold" size="11px" color="#a83f2d" />
+    </view>
+  </view>
+</view>

+ 1 - 0
src/components/favorites-panel/index.wxss

@@ -0,0 +1 @@
+/* favorites-panel */

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

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

+ 21 - 0
src/components/feature-card/index.ts

@@ -0,0 +1,21 @@
+import Toast from '@vant/weapp/toast/toast'
+
+Component({
+  properties: {
+    item: {
+      type: Object,
+      value: {},
+    },
+  },
+
+  methods: {
+    onTap() {
+      const name = (this.data.item as { name: string }).name
+      Toast(`拼图碎片已点亮:${name}`)
+    },
+    onNav() {
+      const name = (this.data.item as { name: string }).name
+      Toast(`导航路线规划完成:${name}`)
+    },
+  },
+})

+ 17 - 0
src/components/feature-card/index.wxml

@@ -0,0 +1,17 @@
+<view class="shrink-0 w-[220px] overflow-hidden rounded-xl bg-white border border-[#ddc18c] shadow-sm" bindtap="onTap">
+  <view class="relative h-[104px] bg-gradient-to-br from-[#8f3528] to-[#d1a052] overflow-hidden flex items-center justify-center">
+    <svg-icon name="{{item.icon}}" size="47px" color="#ffe5a3" />
+    <view class="absolute left-2 top-2 rounded bg-[#fff4d8] px-2 py-1 text-[9px] text-[#9b3929]">{{item.tag}}</view>
+  </view>
+  <view class="p-3">
+    <view class="flex items-center justify-between gap-2">
+      <text class="min-w-0 flex-1 text-[13px] font-bold text-[#563d35] truncate">{{item.name}}</text>
+      <svg-icon name="ph:puzzle-piece-duotone" size="19px" color="#c28b32" />
+    </view>
+    <text class="block mt-1 text-[10px] text-[#917c6d]">{{item.desc}}</text>
+    <view class="mt-3 flex items-center justify-between gap-2">
+      <text class="min-w-0 flex-1 text-[9px] text-[#a83f2d] truncate">任务:{{item.task}}</text>
+      <view class="flex-none rounded bg-[#a83f2d] px-2 py-1 text-[9px] text-white" catchtap="onNav">导航</view>
+    </view>
+  </view>
+</view>

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

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

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

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

+ 24 - 0
src/components/heritage-card/index.ts

@@ -0,0 +1,24 @@
+import Toast from '@vant/weapp/toast/toast'
+
+Component({
+  properties: {
+    item: {
+      type: Object,
+      value: {},
+    },
+  },
+
+  data: {
+    imgError: false,
+  },
+
+  methods: {
+    onTap() {
+      const name = (this.data.item as { name: string }).name
+      Toast(`已选择:${name}`)
+    },
+    onImgError() {
+      this.setData({ imgError: true })
+    },
+  },
+})

+ 16 - 0
src/components/heritage-card/index.wxml

@@ -0,0 +1,16 @@
+<view class="flex-none w-[210px] overflow-hidden rounded-xl border border-[#e4cfad] bg-[#fffaf1] text-left shadow-sm" bindtap="onTap">
+  <view class="heritage-card__cover">
+    <svg-icon name="{{item.icon}}" size="38px" color="#ffe5ae" />
+    <image wx:if="{{!imgError}}" class="heritage-card__img" src="{{item.image}}" mode="aspectFill" binderror="onImgError" />
+    <view class="heritage-card__frame"></view>
+  </view>
+  <view class="p-3">
+    <text class="block truncate text-[13px] font-bold text-[#753024]">{{item.name}}</text>
+    <text class="mt-1 block truncate text-[10px] text-[#b0793c]">{{item.meta}}</text>
+    <text class="mt-2 block h-8 text-[11px] leading-4 text-[#806d5c]">{{item.desc}}</text>
+    <view class="mt-2 inline-flex items-center gap-1 text-[10px] text-[#a83f2d]">
+      <text>查看详情</text>
+      <svg-icon name="ph:arrow-right" size="11px" color="#a83f2d" />
+    </view>
+  </view>
+</view>

+ 25 - 0
src/components/heritage-card/index.wxss

@@ -0,0 +1,25 @@
+.heritage-card__cover {
+  position: relative;
+  height: 96px;
+  overflow: hidden;
+  background-image: linear-gradient(135deg, #8f3427, #c58a42);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #ffe5ae;
+}
+
+.heritage-card__img {
+  position: absolute;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.heritage-card__frame {
+  position: absolute;
+  inset: 8px;
+  border: 1px solid rgb(255 255 255 / 25%);
+  border-radius: 8px;
+  pointer-events: none;
+}

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

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

+ 31 - 0
src/components/heritage-header/index.ts

@@ -0,0 +1,31 @@
+Component({
+  properties: {
+    value: {
+      type: String,
+      value: '',
+    },
+  },
+
+  data: {
+    statusBarHeight: 20,
+  },
+
+  lifetimes: {
+    attached() {
+      const info = wx.getSystemInfoSync()
+      this.setData({ statusBarHeight: info.statusBarHeight || 20 })
+    },
+  },
+
+  methods: {
+    onInput(e: WechatMiniprogram.Input) {
+      this.triggerEvent('search', { keyword: e.detail.value })
+    },
+    onFilter() {
+      this.triggerEvent('filter')
+    },
+    onBack() {
+      this.triggerEvent('back')
+    },
+  },
+})

+ 27 - 0
src/components/heritage-header/index.wxml

@@ -0,0 +1,27 @@
+<view class="heritage-header" style="padding-top: {{statusBarHeight}}px;">
+  <view class="heritage-header__inner">
+    <view class="flex items-center gap-2.5">
+      <view class="heritage-header__back" bindtap="onBack">
+        <svg-icon name="ph:caret-left-bold" size="17px" color="#ffffff" />
+      </view>
+      <text class="text-[19px] font-bold tracking-[.2em] text-white">传承</text>
+    </view>
+
+    <view class="mt-4 flex gap-2">
+      <view class="heritage-header__search">
+        <svg-icon name="ph:magnifying-glass" size="18px" color="#8a7566" />
+        <input
+          class="heritage-header__input"
+          value="{{value}}"
+          placeholder="搜非遗名称、传承人、技艺、区县"
+          placeholder-class="heritage-header__placeholder"
+          confirm-type="search"
+          bindinput="onInput"
+        />
+      </view>
+      <view class="heritage-header__filter" bindtap="onFilter">
+        <svg-icon name="ph:sliders-horizontal-bold" size="18px" color="#ffffff" />
+      </view>
+    </view>
+  </view>
+</view>

+ 57 - 0
src/components/heritage-header/index.wxss

@@ -0,0 +1,57 @@
+.heritage-header {
+  flex: none;
+  background-color: #9d3829;
+  background-image: linear-gradient(30deg, transparent 45%, rgb(247 217 159 / 20%) 46%, transparent 47%),
+    linear-gradient(150deg, transparent 45%, rgb(247 217 159 / 20%) 46%, transparent 47%);
+  background-size: 34px 34px;
+}
+
+.heritage-header__inner {
+  padding: 10px 16px 20px;
+}
+
+.heritage-header__back {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  background-color: rgb(255 255 255 / 15%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.heritage-header__search {
+  flex: 1;
+  height: 40px;
+  background-color: #fffaf1;
+  border-radius: 12px;
+  padding: 0 12px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
+}
+
+.heritage-header__input {
+  min-width: 0;
+  flex: 1;
+  background: transparent;
+  font-size: 12px;
+  color: #51433e;
+}
+
+.heritage-header__placeholder {
+  color: #ab9b8d;
+}
+
+.heritage-header__filter {
+  flex: none;
+  width: 40px;
+  height: 40px;
+  border-radius: 12px;
+  background-color: #d7a951;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
+}

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

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

+ 62 - 0
src/components/menu-item/index.ts

@@ -0,0 +1,62 @@
+Component({
+  properties: {
+    icon: {
+      type: String,
+      value: '',
+    },
+    iconBoxClass: {
+      type: String,
+      value: 'w-9 h-9',
+    },
+    iconBg: {
+      type: String,
+      value: '#f7e6df',
+    },
+    iconColor: {
+      type: String,
+      value: '#a83f2d',
+    },
+    iconSize: {
+      type: String,
+      value: '21px',
+    },
+    title: {
+      type: String,
+      value: '',
+    },
+    subtitle: {
+      type: String,
+      value: '',
+    },
+    rightText: {
+      type: String,
+      value: '',
+    },
+    rightTextBg: {
+      type: String,
+      value: '#a83f2d',
+    },
+    rightTextColor: {
+      type: String,
+      value: '#ffffff',
+    },
+    rightTextSize: {
+      type: String,
+      value: '8px',
+    },
+    badge: {
+      type: Boolean,
+      value: false,
+    },
+    chevron: {
+      type: Boolean,
+      value: false,
+    },
+  },
+
+  methods: {
+    onTap() {
+      this.triggerEvent('select')
+    },
+  },
+})

+ 21 - 0
src/components/menu-item/index.wxml

@@ -0,0 +1,21 @@
+<view class="w-full p-3 flex items-center gap-3" bindtap="onTap">
+  <view
+    class="relative flex-none rounded-xl flex items-center justify-center {{iconBoxClass}}"
+    style="background-color: {{iconBg}};"
+  >
+    <svg-icon name="{{icon}}" size="{{iconSize}}" color="{{iconColor}}" />
+    <view wx:if="{{badge}}" class="absolute right-0 top-0 w-2.5 h-2.5 rounded-full border-2 border-white bg-[#db3b30]"></view>
+  </view>
+  <view class="min-w-0 flex-1">
+    <text class="block text-[12px] font-semibold text-[#61463c] leading-tight">{{title}}</text>
+    <text class="block mt-0.5 text-[9px] text-[#9a8375] leading-tight">{{subtitle}}</text>
+  </view>
+  <view
+    wx:if="{{rightText}}"
+    class="flex-none rounded-full px-2 py-0.5 leading-normal"
+    style="background-color: {{rightTextBg}}; color: {{rightTextColor}}; font-size: {{rightTextSize}};"
+  >
+    {{rightText}}
+  </view>
+  <svg-icon wx:elif="{{chevron}}" name="ph:caret-right-bold" size="12px" color="#b9a898" />
+</view>

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

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

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

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

+ 28 - 0
src/components/news-header/index.ts

@@ -0,0 +1,28 @@
+Component({
+  properties: {
+    value: {
+      type: String,
+      value: '',
+    },
+  },
+
+  data: {
+    statusBarHeight: 20,
+  },
+
+  lifetimes: {
+    attached() {
+      const info = wx.getSystemInfoSync()
+      this.setData({ statusBarHeight: info.statusBarHeight || 20 })
+    },
+  },
+
+  methods: {
+    onInput(e: WechatMiniprogram.Input) {
+      this.triggerEvent('search', { keyword: e.detail.value })
+    },
+    onNotification() {
+      this.triggerEvent('notification')
+    },
+  },
+})

+ 26 - 0
src/components/news-header/index.wxml

@@ -0,0 +1,26 @@
+<view class="news-header" style="padding-top: {{statusBarHeight}}px;">
+  <view class="news-header__inner">
+    <view class="flex items-center justify-between">
+      <text class="text-[21px] font-bold tracking-[.24em] text-white">资讯</text>
+    </view>
+
+    <view class="mt-4 flex gap-2">
+      <view class="news-header__search">
+        <svg-icon name="ph:magnifying-glass" size="18px" color="#8a7566" />
+        <input
+          class="news-header__input"
+          value="{{value}}"
+          placeholder="试试“古厝日落”或“亲子研学”"
+          placeholder-class="news-header__placeholder"
+          confirm-type="search"
+          bindinput="onInput"
+        />
+        <svg-icon name="ph:lightbulb-duotone" size="18px" color="#d29b3d" />
+      </view>
+      <view class="news-header__bell" bindtap="onNotification">
+        <svg-icon name="ph:bell-ringing-duotone" size="19px" color="#983726" />
+        <view class="news-header__badge"></view>
+      </view>
+    </view>
+  </view>
+</view>

+ 58 - 0
src/components/news-header/index.wxss

@@ -0,0 +1,58 @@
+.news-header {
+  flex: none;
+  background-color: #9d3829;
+  background-image: linear-gradient(35deg, transparent 45%, rgb(246 214 154 / 20%) 46%, transparent 47%),
+    linear-gradient(145deg, transparent 45%, rgb(246 214 154 / 20%) 46%, transparent 47%);
+  background-size: 32px 32px;
+}
+
+.news-header__inner {
+  padding: 10px 16px 20px;
+}
+
+.news-header__search {
+  flex: 1;
+  height: 40px;
+  background-color: #fffaf1;
+  border-radius: 20px;
+  padding: 0 12px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
+}
+
+.news-header__input {
+  min-width: 0;
+  flex: 1;
+  background: transparent;
+  font-size: 12px;
+  color: #51433e;
+}
+
+.news-header__placeholder {
+  color: #aa9787;
+}
+
+.news-header__bell {
+  position: relative;
+  flex: none;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background-color: #f5d390;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.news-header__badge {
+  position: absolute;
+  right: 0;
+  top: 0;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #e33e32;
+  border: 2px solid #9d3829;
+}

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

@@ -6,6 +6,10 @@ Component({
       type: Object,
       value: {},
     },
+    bold: {
+      type: Boolean,
+      value: false,
+    },
   },
 
   methods: {

+ 2 - 2
src/components/news-item/index.wxml

@@ -3,8 +3,8 @@
     <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 text-[12px] text-[#51433e] truncate {{bold ? 'font-bold' : ''}}">{{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>
+  <text wx:if="{{item.time}}" class="flex-none text-[9px] text-[#a99686]">{{item.time}}</text>
 </view>

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

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

+ 34 - 0
src/components/profile-header/index.ts

@@ -0,0 +1,34 @@
+Component({
+  properties: {
+    nickname: {
+      type: String,
+      value: '燕尾巷游客',
+    },
+    avatarIcon: {
+      type: String,
+      value: 'ph:user-circle-duotone',
+    },
+  },
+
+  data: {
+    headerPaddingTop: 32,
+  },
+
+  lifetimes: {
+    attached() {
+      const info = wx.getSystemInfoSync()
+      this.setData({
+        headerPaddingTop: (info.statusBarHeight || 20) + 12,
+      })
+    },
+  },
+
+  methods: {
+    onAvatarTap() {
+      this.triggerEvent('changeavatar')
+    },
+    onEditTap() {
+      this.triggerEvent('edit')
+    },
+  },
+})

+ 16 - 0
src/components/profile-header/index.wxml

@@ -0,0 +1,16 @@
+<view class="profile-header" style="padding-top: {{headerPaddingTop}}px;">
+  <view class="flex items-center gap-3 px-4 pb-5">
+    <view class="profile-header__avatar" bindtap="onAvatarTap">
+      <svg-icon name="{{avatarIcon}}" size="48px" color="#a83f2d" />
+    </view>
+    <view class="min-w-0 flex-1">
+      <view class="flex items-center gap-2">
+        <text class="truncate text-[18px] font-bold text-white">{{nickname}}</text>
+        <view class="profile-header__edit" bindtap="onEditTap">
+          <svg-icon name="ph:pencil-simple-duotone" size="12px" color="#ffffff" />
+          <text>编辑</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</view>

+ 31 - 0
src/components/profile-header/index.wxss

@@ -0,0 +1,31 @@
+.profile-header {
+  flex: none;
+  background-color: #9d3829;
+  background-image: linear-gradient(90deg, rgb(255 240 213 / 9%) 1px, transparent 1px),
+    linear-gradient(rgb(255 240 213 / 9%) 1px, transparent 1px);
+  background-size: 34px 15px;
+}
+
+.profile-header__avatar {
+  display: flex;
+  flex: none;
+  align-items: center;
+  justify-content: center;
+  width: 64px;
+  height: 64px;
+  background-color: #fff5dc;
+  border: 2px solid #f3d18b;
+  border-radius: 50%;
+  box-shadow: 0 4px 12px rgb(0 0 0 / 12%);
+}
+
+.profile-header__edit {
+  display: inline-flex;
+  gap: 2px;
+  align-items: center;
+  padding: 4px 8px;
+  font-size: 9px;
+  color: #fff;
+  background-color: rgb(255 255 255 / 15%);
+  border-radius: 999px;
+}

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

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

+ 27 - 0
src/components/route-card/index.ts

@@ -0,0 +1,27 @@
+import Toast from '@vant/weapp/toast/toast'
+
+Component({
+  properties: {
+    item: {
+      type: Object,
+      value: {},
+    },
+  },
+
+  data: {
+    saved: false,
+  },
+
+  methods: {
+    onSave() {
+      const title = (this.data.item as { title: string }).title
+      const saved = !this.data.saved
+      this.setData({ saved })
+      Toast(saved ? `已收藏:${title}` : `已取消收藏:${title}`)
+    },
+    onNav() {
+      const title = (this.data.item as { title: string }).title
+      Toast(`已规划前往“${title}”的最佳路线`)
+    },
+  },
+})

+ 17 - 0
src/components/route-card/index.wxml

@@ -0,0 +1,17 @@
+<view class="shrink-0 w-[238px] rounded-2xl border border-[#dec18b] bg-white p-4 shadow-sm">
+  <view class="flex items-center justify-between">
+    <view class="w-10 h-10 rounded-xl bg-[#f2dfba] flex items-center justify-center">
+      <svg-icon name="{{item.icon}}" size="25px" color="#9a3b2d" />
+    </view>
+    <view class="flex items-center justify-center" bindtap="onSave">
+      <svg-icon
+        name="{{saved ? 'ph:heart-fill' : 'ph:heart-duotone'}}"
+        size="21px"
+        color="{{saved ? '#a83f2d' : '#aa8a6f'}}"
+      />
+    </view>
+  </view>
+  <text class="mt-3 block text-[13px] font-bold text-[#673d33]">{{item.title}}</text>
+  <text class="mt-1 block h-8 text-[9px] leading-4 text-[#8d7768]">{{item.desc}}</text>
+  <view class="mt-3 w-full rounded-lg bg-[#a83f2d] py-2 text-center text-[10px] text-white" bindtap="onNav">一键导航</view>
+</view>

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

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

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

@@ -17,7 +17,6 @@ Component({
       value: true,
     },
   },
-
   methods: {
     onMore() {
       this.triggerEvent('more')

+ 2 - 2
src/components/section-header/index.wxml

@@ -1,10 +1,10 @@
-<view class="flex items-end justify-between">
+<view class="flex items-end justify-between" data-component-id="{{title}}">
   <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>
+    <text>{{displayMoreText}}</text>
     <svg-icon name="solar:alt-arrow-right-linear" size="13px" color="#a83f2d" />
   </view>
 </view>

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

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

+ 25 - 0
src/components/travel-header/index.ts

@@ -0,0 +1,25 @@
+Component({
+  data: {
+    statusBarHeight: 20,
+    keyword: '',
+  },
+
+  lifetimes: {
+    attached() {
+      const info = wx.getSystemInfoSync()
+      this.setData({ statusBarHeight: info.statusBarHeight || 20 })
+    },
+  },
+
+  methods: {
+    onInput(e: WechatMiniprogram.Input) {
+      this.setData({ keyword: e.detail.value })
+    },
+    onSubmit() {
+      this.triggerEvent('search', { keyword: this.data.keyword })
+    },
+    onConfirm() {
+      this.triggerEvent('search', { keyword: this.data.keyword })
+    },
+  },
+})

+ 21 - 0
src/components/travel-header/index.wxml

@@ -0,0 +1,21 @@
+<view class="travel-header" style="padding-top: {{statusBarHeight}}px;">
+  <view class="travel-header__inner">
+    <text class="text-[22px] font-bold tracking-[.18em] text-white">趣游漳州</text>
+
+    <view class="travel-header__search">
+      <svg-icon name="ph:magnifying-glass" size="18px" color="#8a372d" />
+      <input
+        class="travel-header__input"
+        value="{{keyword}}"
+        placeholder="逛古城、拍土楼、吃卤面…"
+        placeholder-class="travel-header__placeholder"
+        confirm-type="search"
+        bindinput="onInput"
+        bindconfirm="onConfirm"
+      />
+      <view class="travel-header__submit" bindtap="onSubmit">
+        <svg-icon name="ph:arrow-right-bold" size="14px" color="#8a372d" />
+      </view>
+    </view>
+  </view>
+</view>

+ 42 - 0
src/components/travel-header/index.wxss

@@ -0,0 +1,42 @@
+.travel-header {
+  flex: none;
+  background-color: #a73529;
+  background-image: linear-gradient(90deg, rgb(255 240 213 / 9%) 1px, transparent 1px),
+    linear-gradient(rgb(255 240 213 / 9%) 1px, transparent 1px);
+  background-size: 34px 15px;
+  box-shadow: 0 2px 6px rgb(0 0 0 / 12%);
+}
+
+.travel-header__inner {
+  padding: 10px 16px 16px;
+}
+
+.travel-header__search {
+  margin-top: 12px;
+  display: flex;
+  align-items: center;
+  height: 40px;
+  background-color: #fff;
+  border-radius: 12px;
+  padding: 0 12px;
+  gap: 8px;
+}
+
+.travel-header__input {
+  min-width: 0;
+  flex: 1;
+  background: transparent;
+  font-size: 11px;
+  color: #8a372d;
+}
+
+.travel-header__placeholder {
+  color: #b69083;
+}
+
+.travel-header__submit {
+  flex: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}

+ 9 - 3
src/custom-tab-bar/index.wxss

@@ -1,12 +1,18 @@
 .tab-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
   display: flex;
+  box-sizing: border-box;
   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));
+  padding: 10px 40rpx;
+  padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
   background-color: #fffaf1;
   border-top: 1px solid #e9dbc5;
+  z-index: 9999;
+  width: 100%;
 }
 
 .tab-bar__item {

+ 3 - 1
src/pages/home/home.wxml

@@ -47,10 +47,12 @@
 
       <view class="mt-5 px-4">
         <section-header title="志愿者组织" barColor="#b48732" moreText="查看全部" bind:more="onMoreVolunteer" />
-        <view class="mt-3 space-y-2.5">
+        <view class="flex flex-col mt-3 gap-2.5">
           <volunteer-card wx:for="{{volunteers}}" wx:key="title" item="{{item}}" />
         </view>
       </view>
+
+      <view class="tabbar-placeholder"></view>
     </view>
   </scroll-view>
 

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

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

+ 7 - 2
src/pages/inherit/inherit.json

@@ -1,4 +1,9 @@
 {
-  "navigationBarTitleText": "传承",
-  "usingComponents": {}
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "heritage-header": "/components/heritage-header/index",
+    "heritage-card": "/components/heritage-card/index",
+    "svg-icon": "weapp-svg-icon/index",
+    "van-toast": "@vant/weapp/toast/index"
+  }
 }

+ 362 - 1
src/pages/inherit/inherit.ts

@@ -1,5 +1,264 @@
+import Toast from '@vant/weapp/toast/toast'
+
+interface Stat {
+  label: string
+  value: string
+  unit: string
+  icon?: string
+}
+
+interface HeritageItem {
+  name: string
+  meta: string
+  desc: string
+  icon: string
+  image: string
+  level: string
+  district: string
+  category: string
+}
+
+interface TagOption {
+  name: string
+  active: boolean
+}
+
+const MEDIA = 'https://modao.cc/agent-py/media'
+
+const PROJECTS: HeritageItem[] = [
+  {
+    name: '漳州布袋木偶戏',
+    meta: '国家级 · 芗城',
+    desc: '掌中乾坤,一口道尽千古事',
+    icon: 'ph:mask-happy-duotone',
+    image: `${MEDIA}/generated_images/2026-08-07/a0d729e3db234da0b8ce54c7076e4fed.jpg`,
+    level: '国家级',
+    district: '芗城',
+    category: '传统戏剧',
+  },
+  {
+    name: '漳浦剪纸',
+    meta: '国家级 · 漳浦',
+    desc: '剪刻乡土记忆与吉祥纹样',
+    icon: 'ph:scissors-duotone',
+    image: `${MEDIA}/generated_images/2026-08-07/236e78587aac4527ae84a006f6397167.jpg`,
+    level: '国家级',
+    district: '漳浦',
+    category: '传统美术',
+  },
+  {
+    name: '东山歌册',
+    meta: '省级 · 东山',
+    desc: '方言吟唱里的海岛生活',
+    icon: 'ph:music-notes-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/49d50567367248718d0d833c46ada65c.jpg`,
+    level: '省级',
+    district: '东山',
+    category: '民间文学',
+  },
+  {
+    name: '南靖土楼营造技艺',
+    meta: '市级 · 南靖',
+    desc: '夯土筑圆楼,聚族而居',
+    icon: 'ph:building-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/2a2356e6bdb74d3abff8576c1339926f.jpg`,
+    level: '市级',
+    district: '南靖',
+    category: '传统技艺',
+  },
+]
+
+const INHERITORS: HeritageItem[] = [
+  {
+    name: '陈黎晖',
+    meta: '布袋木偶戏 · 芗城',
+    desc: '四十载掌上演绎漳州故事',
+    icon: 'ph:user-focus-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/69916b535e7d4045993ee45b945a67d2.jpg`,
+    level: '国家级',
+    district: '芗城',
+    category: '传统戏剧',
+  },
+  {
+    name: '林桃',
+    meta: '剪纸 · 漳浦',
+    desc: '以剪代笔,留住花鸟人情',
+    icon: 'ph:user-focus-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/ca5ca067074747e8b76d98546b37ea6d.jpg`,
+    level: '省级',
+    district: '漳浦',
+    category: '传统美术',
+  },
+  {
+    name: '沈水仙',
+    meta: '歌册 · 东山',
+    desc: '乡音传唱海岛古韵',
+    icon: 'ph:user-focus-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/33251f5102fe4de18a2cc548b85035fc.jpg`,
+    level: '市级',
+    district: '东山',
+    category: '民间文学',
+  },
+  {
+    name: '简天佑',
+    meta: '竹编 · 平和',
+    desc: '一篾一挑编出山野风物',
+    icon: 'ph:user-focus-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/6f7e281e05064529899f0cd60dc9dbf4.jpg`,
+    level: '县级',
+    district: '平和',
+    category: '传统技艺',
+  },
+]
+
+const UNITS: HeritageItem[] = [
+  {
+    name: '漳州市木偶剧团',
+    meta: '国家级保护单位 · 芗城',
+    desc: '布袋木偶戏系统保护与展演',
+    icon: 'ph:columns-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/40f6b69d9e9a4bf7be8bb81fffa1cb7b.jpg`,
+    level: '国家级',
+    district: '芗城',
+    category: '传统戏剧',
+  },
+  {
+    name: '南靖县文化馆',
+    meta: '市级保护单位 · 南靖',
+    desc: '土楼民俗资料整理与活化',
+    icon: 'ph:bank-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/42d41037be194bdca22ced6a181d08c7.jpg`,
+    level: '市级',
+    district: '南靖',
+    category: '民俗',
+  },
+  {
+    name: '东山县文化馆',
+    meta: '省级保护单位 · 东山',
+    desc: '海洋民俗与歌册传承基地',
+    icon: 'ph:lighthouse-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/636da2f41d614f8797833a11f2967743.jpg`,
+    level: '省级',
+    district: '东山',
+    category: '民间文学',
+  },
+  {
+    name: '诏安县美术馆',
+    meta: '县级保护单位 · 诏安',
+    desc: '书画艺术与民间美术守护',
+    icon: 'ph:frame-corners-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/50b938f366134e749616ad065f1b4b32.jpg`,
+    level: '县级',
+    district: '诏安',
+    category: '传统美术',
+  },
+]
+
+const CENTERS: HeritageItem[] = [
+  {
+    name: '芗城木偶传习所',
+    meta: '芗城 · 预约开放',
+    desc: '木偶操演、唱腔与道具体验',
+    icon: 'ph:storefront-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/4f7a53eab8c8493ab98704d4d2d452fd.jpg`,
+    level: '国家级',
+    district: '芗城',
+    category: '传统戏剧',
+  },
+  {
+    name: '南靖土楼营造传习中心',
+    meta: '南靖 · 周二至周日',
+    desc: '夯筑工具与传统工序展示',
+    icon: 'ph:house-line-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/5b8c846df27049b1ba38df648280df8d.jpg`,
+    level: '市级',
+    district: '南靖',
+    category: '传统技艺',
+  },
+  {
+    name: '云霄竹塔传习所',
+    meta: '云霄 · 公益课堂',
+    desc: '竹艺制作与节庆文化传习',
+    icon: 'ph:plant-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/dd3693b6cae1420999e57a56aa10e735.jpg`,
+    level: '区级',
+    district: '云霄',
+    category: '传统技艺',
+  },
+  {
+    name: '诏安木版年画传习中心',
+    meta: '诏安 · 可现场体验',
+    desc: '刻版、拓印与年俗课堂',
+    icon: 'ph:paint-brush-broad-duotone',
+    image: `${MEDIA}/generated_images/2026-08-06/9b460a47a8f34e1dafbf29ecd5260a68.jpg`,
+    level: '县级',
+    district: '诏安',
+    category: '传统美术',
+  },
+]
+
+function matchItems(
+  items: HeritageItem[],
+  query: string,
+  category: string,
+  levels: string[],
+  district: string,
+): HeritageItem[] {
+  const q = query.trim().toLowerCase()
+  return items.filter((item) => {
+    const matchQuery = !q || `${item.name}${item.meta}${item.desc}`.toLowerCase().includes(q)
+    const matchCategory = !category || item.category === category
+    const matchLevel = levels.length === 0 || levels.includes(item.level)
+    const matchDistrict = district === '全部' || item.district === district
+    return matchQuery && matchCategory && matchLevel && matchDistrict
+  })
+}
+
 Component({
-  data: {},
+  data: {
+    keyword: '',
+
+    stats: [
+      { label: '非遗项目总数', value: '1,286', unit: '项' },
+      { label: '传承人总量', value: '2,560', unit: '人' },
+      { label: '文保单位数量', value: '486', unit: '处', icon: 'ph:columns-duotone' },
+      { label: '传习工坊数量', value: '128', unit: '家' },
+    ] as Stat[],
+
+    categories: [
+      '民间文学',
+      '传统音乐',
+      '传统舞蹈',
+      '传统戏剧',
+      '曲艺',
+      '传统体育游艺与杂技',
+      '传统美术',
+      '传统技艺',
+      '传统医药',
+      '民俗',
+    ],
+    selectedCategory: '',
+
+    levels: [
+      { name: '全部', active: true },
+      { name: '国家级', active: false },
+      { name: '省级', active: false },
+      { name: '市级', active: false },
+      { name: '区级', active: false },
+      { name: '县级', active: false },
+    ] as TagOption[],
+
+    districts: ['全部', '芗城', '龙文', '南靖', '东山', '云霄', '诏安', '漳浦', '平和', '华安', '长泰'],
+    selectedDistrict: '全部',
+
+    projects: PROJECTS as HeritageItem[],
+    inheritors: INHERITORS as HeritageItem[],
+    units: UNITS as HeritageItem[],
+    centers: CENTERS as HeritageItem[],
+
+    summary: '当前展示全部非遗内容 · 共 16 条',
+    hasResult: true,
+  },
 
   pageLifetimes: {
     show() {
@@ -8,4 +267,106 @@ Component({
       }
     },
   },
+
+  methods: {
+    refresh() {
+      const { keyword, selectedCategory, selectedDistrict } = this.data as {
+        keyword: string
+        selectedCategory: string
+        selectedDistrict: string
+      }
+      const selectedLevels = (this.data.levels as TagOption[])
+        .filter(l => l.active && l.name !== '全部')
+        .map(l => l.name)
+
+      const projects = matchItems(PROJECTS, keyword, selectedCategory, selectedLevels, selectedDistrict)
+      const inheritors = matchItems(INHERITORS, keyword, selectedCategory, selectedLevels, selectedDistrict)
+      const units = matchItems(UNITS, keyword, selectedCategory, selectedLevels, selectedDistrict)
+      const centers = matchItems(CENTERS, keyword, selectedCategory, selectedLevels, selectedDistrict)
+      const total = projects.length + inheritors.length + units.length + centers.length
+
+      const active = [
+        selectedCategory,
+        ...selectedLevels,
+        selectedDistrict === '全部' ? '' : selectedDistrict,
+      ].filter(Boolean)
+      const summary = active.length
+        ? `已筛选:${active.join('、')} · 共 ${total} 条内容`
+        : `当前展示全部非遗内容 · 共 ${total} 条`
+
+      this.setData({
+        projects,
+        inheritors,
+        units,
+        centers,
+        summary,
+        hasResult: total > 0,
+      })
+    },
+
+    onSearch(e: WechatMiniprogram.CustomEvent) {
+      this.setData({ keyword: e.detail.keyword as string })
+      this.refresh()
+    },
+
+    onSelectCategory(e: WechatMiniprogram.TouchEvent) {
+      const category = e.currentTarget.dataset.name as string
+      const selectedCategory = this.data.selectedCategory === category ? '' : category
+      this.setData({ selectedCategory })
+      this.refresh()
+    },
+
+    onToggleLevel(e: WechatMiniprogram.TouchEvent) {
+      const name = e.currentTarget.dataset.name as string
+      let levels = this.data.levels as TagOption[]
+      if (name === '全部') {
+        levels = levels.map(l => ({ ...l, active: l.name === '全部' }))
+      }
+      else {
+        levels = levels.map((l) => {
+          if (l.name === '全部') {
+            return { ...l, active: false }
+          }
+          if (l.name === name) {
+            return { ...l, active: !l.active }
+          }
+          return l
+        })
+        const hasSpecific = levels.some(l => l.name !== '全部' && l.active)
+        if (!hasSpecific) {
+          levels = levels.map(l => ({ ...l, active: l.name === '全部' }))
+        }
+      }
+      this.setData({ levels })
+      this.refresh()
+    },
+
+    onSelectDistrict(e: WechatMiniprogram.TouchEvent) {
+      const district = e.currentTarget.dataset.name as string
+      if (!district) {
+        return
+      }
+      this.setData({ selectedDistrict: district })
+      this.refresh()
+    },
+
+    onReset() {
+      const levels = (this.data.levels as TagOption[]).map(l => ({ ...l, active: l.name === '全部' }))
+      this.setData({
+        keyword: '',
+        selectedCategory: '',
+        levels,
+        selectedDistrict: '全部',
+      })
+      this.refresh()
+    },
+
+    onFilter() {
+      Toast('可使用下方门类、级别与区县进行筛选')
+    },
+
+    onBack() {
+      wx.switchTab({ url: '/pages/home/home' })
+    },
+  },
 })

+ 134 - 3
src/pages/inherit/inherit.wxml

@@ -1,4 +1,135 @@
-<view class="min-h-screen flex flex-col items-center justify-center">
-  <text class="font-semibold">传承</text>
-  <text class="placeholder-desc">内容占位符</text>
+<view class="h-screen flex flex-col bg-[#f8efe2]">
+  <heritage-header value="{{keyword}}" bind:search="onSearch" bind:filter="onFilter" bind:back="onBack" />
+
+  <scroll-view class="flex-1 min-h-0" scroll-y>
+    <view class="bg-[#fffaf1] border-b border-[#eadcc5] px-4 pt-3 pb-3">
+      <view class="mb-2">
+        <text class="text-[13px] font-bold text-[#983726]">非遗数据</text>
+      </view>
+      <view class="grid grid-cols-2 gap-2">
+        <view
+          wx:for="{{stats}}"
+          wx:key="label"
+          class="border border-[#e5cfa9] bg-[#fff7e9] px-3 py-2"
+        >
+          <view class="flex items-center justify-between text-[#a83f2d]">
+            <text class="text-[10px]">{{item.label}}</text>
+            <svg-icon wx:if="{{item.icon}}" name="{{item.icon}}" size="16px" color="#a83f2d" />
+          </view>
+          <view class="mt-1 flex items-baseline">
+            <text class="text-[18px] leading-none font-bold text-[#803020]">{{item.value}}</text>
+            <text class="ml-1 text-[10px] text-[#803020]">{{item.unit}}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="bg-[#fffaf1] border-b border-[#eadcc5] px-4 pt-3 pb-2">
+      <view class="flex items-center justify-between">
+        <text class="text-[13px] font-bold text-[#983726]">非遗门类</text>
+        <view class="text-[11px] text-[#a83f2d]" bindtap="onReset">重置</view>
+      </view>
+      <scroll-view class="hide-scroll mt-2" scroll-x>
+        <view class="inline-flex gap-2 whitespace-nowrap pb-1">
+          <view
+            wx:for="{{categories}}"
+            wx:key="*this"
+            class="rounded-full border px-3 py-1.5 text-[11px] {{selectedCategory === item ? 'bg-[#a83f2d] border-[#a83f2d] text-white' : 'bg-white border-[#e6d4bd] text-[#806d5c]'}}"
+            data-name="{{item}}"
+            bindtap="onSelectCategory"
+          >
+            {{item}}
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+
+    <view class="bg-[#fffaf1] border-b border-[#eadcc5] px-4 py-3">
+      <view class="flex items-center justify-between">
+        <text class="text-[13px] font-bold text-[#983726]">保护级别</text>
+        <text class="text-[10px] text-[#9b897a]">可多选</text>
+      </view>
+      <view class="mt-2 flex flex-wrap gap-2 text-[11px]">
+        <view
+          wx:for="{{levels}}"
+          wx:key="name"
+          class="rounded-full border px-3 py-1.5 {{item.active ? 'bg-[#a83f2d] border-[#a83f2d] text-white' : 'bg-white border-[#e6d4bd] text-[#806d5c]'}}"
+          data-name="{{item.name}}"
+          bindtap="onToggleLevel"
+        >
+          {{item.name}}
+        </view>
+      </view>
+
+      <view class="mt-3 flex items-center justify-between">
+        <text class="text-[12px] font-bold text-[#6c5145]">漳州各区县</text>
+        <text class="text-[10px] text-[#9b897a]">快捷筛选</text>
+      </view>
+      <scroll-view class="hide-scroll mt-2" scroll-x>
+        <view class="inline-flex gap-2 whitespace-nowrap pb-1">
+          <view
+            wx:for="{{districts}}"
+            wx:key="*this"
+            class="rounded-full border px-3 py-1.5 text-[11px] {{selectedDistrict === item ? 'bg-[#a83f2d] border-[#a83f2d] text-white' : 'bg-white border-[#e6d4bd] text-[#806d5c]'}}"
+            data-name="{{item}}"
+            bindtap="onSelectDistrict"
+          >
+            {{item}}
+          </view>
+        </view>
+      </scroll-view>
+
+      <text class="mt-2 block text-[10px] text-[#9b897a]">{{summary}}</text>
+    </view>
+
+    <view wx:if="{{hasResult}}">
+      <view class="px-4 pt-4">
+        <text class="text-[16px] font-bold text-[#873324]">非遗项目</text>
+        <scroll-view class="hide-scroll mt-2.5" scroll-x>
+          <view class="inline-flex gap-3 pb-2">
+            <heritage-card wx:for="{{projects}}" wx:key="name" item="{{item}}" />
+          </view>
+        </scroll-view>
+      </view>
+
+      <view class="px-4 pt-4">
+        <text class="text-[16px] font-bold text-[#873324]">非遗传承人</text>
+        <scroll-view class="hide-scroll mt-2.5" scroll-x>
+          <view class="inline-flex gap-3 pb-2">
+            <heritage-card wx:for="{{inheritors}}" wx:key="name" item="{{item}}" />
+          </view>
+        </scroll-view>
+      </view>
+
+      <view class="px-4 pt-4">
+        <text class="text-[16px] font-bold text-[#873324]">非遗保护单位</text>
+        <scroll-view class="hide-scroll mt-2.5" scroll-x>
+          <view class="inline-flex gap-3 pb-2">
+            <heritage-card wx:for="{{units}}" wx:key="name" item="{{item}}" />
+          </view>
+        </scroll-view>
+      </view>
+
+      <view class="px-4 pt-4">
+        <text class="text-[16px] font-bold text-[#873324]">非遗传习中心(所)</text>
+        <scroll-view class="hide-scroll mt-2.5" scroll-x>
+          <view class="inline-flex gap-3 pb-2">
+            <heritage-card wx:for="{{centers}}" wx:key="name" item="{{item}}" />
+          </view>
+        </scroll-view>
+      </view>
+    </view>
+
+    <view wx:else class="px-4 py-12 text-center">
+      <view class="flex justify-center">
+        <svg-icon name="ph:magnifying-glass-duotone" size="40px" color="#c8a66b" />
+      </view>
+      <text class="mt-2 block text-[13px] font-bold text-[#7f5f50]">暂未找到匹配内容</text>
+      <view class="mt-3 inline-block text-[11px] text-[#a83f2d]" bindtap="onReset">清除筛选条件</view>
+    </view>
+
+    <view class="tabbar-placeholder"></view>
+  </scroll-view>
+
+  <van-toast id="van-toast" />
 </view>

+ 8 - 3
src/pages/inherit/inherit.wxss

@@ -1,4 +1,9 @@
-.placeholder-desc {
-  margin-top: 16rpx;
-  color: #999999;
+/* inherit */
+
+.hide-scroll {
+  scrollbar-width: none;
+}
+
+.hide-scroll::-webkit-scrollbar {
+  display: none;
 }

+ 10 - 2
src/pages/news/news.json

@@ -1,4 +1,12 @@
 {
-  "navigationBarTitleText": "资讯",
-  "usingComponents": {}
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "news-header": "/components/news-header/index",
+    "news-item": "/components/news-item/index",
+    "feature-card": "/components/feature-card/index",
+    "section-header": "/components/section-header/index",
+    "svg-icon": "weapp-svg-icon/index",
+    "van-toast": "@vant/weapp/toast/index",
+    "van-dialog": "@vant/weapp/dialog/index"
+  }
 }

+ 229 - 1
src/pages/news/news.ts

@@ -1,5 +1,155 @@
+import Dialog from '@vant/weapp/dialog/dialog'
+import Toast from '@vant/weapp/toast/toast'
+
+interface NewsItem {
+  icon: string
+  iconBg: string
+  iconColor: string
+  title: string
+  subtitle: string
+}
+
+interface NewsTab {
+  key: string
+  text: string
+  icon: string
+}
+
+interface FeatureItem {
+  tag: string
+  name: string
+  desc: string
+  task: string
+  icon: string
+}
+
+interface MechanismItem {
+  icon: string
+  title: string
+  desc: string
+}
+
+interface FeedbackItem {
+  icon: string
+  title: string
+  sub: string
+}
+
+interface CategoryMeta {
+  icon: string
+  iconBg: string
+  iconColor: string
+}
+
+const CATEGORY_META: Record<string, CategoryMeta> = {
+  宣传传播: { icon: 'ph:newspaper-clipping-duotone', iconBg: '#f9e5df', iconColor: '#a83f2d' },
+  非遗教育: { icon: 'ph:student-duotone', iconBg: '#fff0d8', iconColor: '#a87a27' },
+  建设动态: { icon: 'ph:house-line-duotone', iconBg: '#e8f0e7', iconColor: '#5f795d' },
+  政策法规: { icon: 'ph:scroll-duotone', iconBg: '#f2ebe3', iconColor: '#78675a' },
+}
+
+const NEWS_MAP: Record<string, [string, string][]> = {
+  宣传传播: [
+    ['专题展示:漳州非遗名录', '一览闽南文化生态保护区代表性项目'],
+    ['品牌活动持续上新', '古城灯会、南音雅集等品牌活动预告'],
+    ['闽南文化交流开启新篇', '跨区域文化交流与合作成果'],
+    ['宣传报道:守护漳州非遗', '记录传承人和保护工作一线'],
+    ['非遗宣传物料上线', '主题海报与公共文化宣传手册'],
+    ['视觉形象焕新发布', '统一保护区文化视觉识别'],
+    ['保护区标识牌更新', '古厝街巷新增文化保护指引'],
+  ],
+  非遗教育: [
+    ['“非遗在校园”工作活动开展', '传统技艺走进课堂与校园社团'],
+    ['非遗校本教材/通识读本发布', '让闽南文化成为可读的乡土教材'],
+    ['专业教育合作项目启动', '推动非遗传承与专业人才培养'],
+    ['师资队伍培训完成', '提升非遗教学与实践指导能力'],
+  ],
+  建设动态: [
+    ['古城骑楼修缮进入收尾阶段', '燕尾脊与红砖立面焕新'],
+    ['九龙江文化廊道新增三处驿站', '水岸慢行线路持续完善'],
+    ['传统村落数字档案完成更新', '新增二百余处古厝记录'],
+  ],
+  政策法规: [
+    ['文化生态保护区专项规划公示', '公众意见征集至本月底'],
+    ['非遗工坊扶持申报正式启动', '支持活态传承与创新转化'],
+    ['传统建筑保护导则发布', '明确修缮材料与工艺标准'],
+  ],
+}
+
+function buildNews(tab: string): NewsItem[] {
+  const meta = CATEGORY_META[tab] || CATEGORY_META['宣传传播']
+  return (NEWS_MAP[tab] || []).map(([title, subtitle], index) => ({
+    icon: index === 0 ? 'ph:buildings-duotone' : meta.icon,
+    iconBg: meta.iconBg,
+    iconColor: meta.iconColor,
+    title,
+    subtitle,
+  }))
+}
+
 Component({
-  data: {},
+  data: {
+    tabs: [
+      { key: '宣传传播', text: '宣传传播', icon: 'ph:megaphone-duotone' },
+      { key: '非遗教育', text: '非遗教育', icon: 'ph:student-duotone' },
+      { key: '建设动态', text: '建设动态', icon: 'ph:house-line-duotone' },
+      { key: '政策法规', text: '政策法规', icon: 'ph:scroll-duotone' },
+    ] as NewsTab[],
+
+    activeTab: '宣传传播',
+    keyword: '',
+    news: buildNews('宣传传播') as NewsItem[],
+
+    festivalTitle: '古城七夕灯游会',
+    festivalCountdown: '还剩 11 天',
+
+    features: [
+      {
+        tag: '拍照出片',
+        name: '漳州古城香港路',
+        desc: '骑楼红砖与百年商铺,一步一景。',
+        task: '找到一扇燕尾脊门楼',
+        icon: 'ph:buildings-duotone',
+      },
+      {
+        tag: '亲子遛娃',
+        name: '东山南门湾',
+        desc: '沿海堤慢走,等一场橘色日落。',
+        task: '收集三种海浪声音',
+        icon: 'ph:map-pin-area-duotone',
+      },
+      {
+        tag: '小众秘境',
+        name: '埭美古村',
+        desc: '水上红砖古厝群,安静又从容。',
+        task: '辨认五种红砖纹样',
+        icon: 'ph:buildings-duotone',
+      },
+    ] as FeatureItem[],
+
+    mechanisms: [
+      {
+        icon: 'ph:buildings-duotone',
+        title: '机构建设',
+        desc: '完善保护区工作架构,明确职责边界,推动保护、传承与利用协同开展。',
+      },
+      {
+        icon: 'ph:clipboard-text-duotone',
+        title: '统筹管理',
+        desc: '建立项目清单与年度计划,统筹资源调度,持续跟进重点保护任务进度。',
+      },
+      {
+        icon: 'ph:users-three-duotone',
+        title: '专职人员',
+        desc: '配备熟悉地方文化的专职队伍,做好日常巡查、资料整理与公众服务。',
+      },
+    ] as MechanismItem[],
+
+    feedback: [
+      { icon: 'ph:star-duotone', title: '我要打分', sub: '' },
+      { icon: 'ph:chat-circle-dots-duotone', title: '提个小建议', sub: '含进度查询' },
+    ] as FeedbackItem[],
+  },
 
   pageLifetimes: {
     show() {
@@ -8,4 +158,82 @@ Component({
       }
     },
   },
+
+  methods: {
+    refreshNews() {
+      const activeTab = this.data.activeTab as string
+      const keyword = (this.data.keyword as string).trim()
+      let list = buildNews(activeTab)
+      if (keyword) {
+        list = list.filter(n => n.title.includes(keyword) || n.subtitle.includes(keyword))
+      }
+      this.setData({ news: list })
+    },
+
+    onSearch(e: WechatMiniprogram.CustomEvent) {
+      this.setData({ keyword: e.detail.keyword as string })
+      this.refreshNews()
+    },
+
+    onTabTap(e: WechatMiniprogram.TouchEvent) {
+      const key = e.currentTarget.dataset.key as string
+      if (!key || key === this.data.activeTab) {
+        return
+      }
+      this.setData({ activeTab: key, keyword: '' })
+      this.refreshNews()
+    },
+
+    onClearSearch() {
+      this.setData({ keyword: '' })
+      this.refreshNews()
+    },
+
+    onNotification() {
+      Dialog.alert({
+        title: '元宵限定推送',
+        message: '古城千盏花灯今晚亮起,猜灯谜集章可兑换限定剪纸书签。',
+        confirmButtonText: '收下灯会消息',
+      })
+    },
+
+    onMoreFeature() {
+      Toast('已为你展开更多特色成果')
+    },
+
+    onMechanism(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const item = (this.data.mechanisms as MechanismItem[])[index]
+      if (!item) {
+        return
+      }
+      Dialog.alert({
+        title: item.title,
+        message: item.desc,
+        confirmButtonText: '知道了',
+      })
+    },
+
+    onFeedback(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const item = (this.data.feedback as FeedbackItem[])[index]
+      if (!item) {
+        return
+      }
+      if (item.title === '我要打分') {
+        Dialog.alert({
+          title: '满意度问卷',
+          message: '感谢你的参与!完成问卷即可点亮“古厝守护者”徽章。',
+          confirmButtonText: '收入徽章册',
+        })
+      }
+      else {
+        Dialog.alert({
+          title: '建议反馈',
+          message: '你的建议已记录,可在“我的”页查看处理进度。',
+          confirmButtonText: '知道了',
+        })
+      }
+    },
+  },
 })

+ 104 - 3
src/pages/news/news.wxml

@@ -1,4 +1,105 @@
-<view class="min-h-screen flex flex-col items-center justify-center">
-  <text class="font-semibold">资讯</text>
-  <text class="placeholder-desc">内容占位符</text>
+<view class="h-screen flex flex-col paper">
+  <news-header value="{{keyword}}" bind:search="onSearch" bind:notification="onNotification" />
+
+  <scroll-view class="flex-1 min-h-0" scroll-y>
+    <view class="px-4 pt-4">
+      <scroll-view class="hide-scroll" scroll-x>
+        <view class="inline-flex gap-2 whitespace-nowrap">
+          <view
+            wx:for="{{tabs}}"
+            wx:key="key"
+            class="inline-flex items-center gap-1.5 rounded-full border px-3 py-2 text-[11px] {{activeTab === item.key ? 'bg-[#a83f2d] text-white border-[#a83f2d]' : 'bg-white text-[#765f52] border-[#e4d0b2]'}}"
+            data-key="{{item.key}}"
+            bindtap="onTabTap"
+          >
+            <svg-icon name="{{item.icon}}" size="14px" color="{{activeTab === item.key ? '#ffffff' : '#765f52'}}" />
+            <text>{{item.text}}</text>
+          </view>
+        </view>
+      </scroll-view>
+
+      <view class="mt-3 flex items-center justify-between rounded-lg border border-[#e6c98f] bg-[#9d3829] px-3 py-2 text-white">
+        <view class="flex items-center gap-2 text-[11px]">
+          <svg-icon name="ph:lightbulb-duotone" size="19px" color="#f5d390" />
+          <text>{{festivalTitle}}</text>
+        </view>
+        <text class="text-[12px] font-bold text-[#f8dda2]">{{festivalCountdown}}</text>
+      </view>
+
+      <view class="mt-2 overflow-hidden rounded-2xl border border-[#eadcc5] bg-white divide-y divide-[#f0e6d6]">
+        <news-item wx:for="{{news}}" wx:key="title" item="{{item}}" bold="{{true}}" />
+        <view wx:if="{{!news.length}}" class="px-5 py-14 text-center">
+          <view class="mx-auto w-28 h-24 rounded-lg bg-[#f2dfc1] flex items-center justify-center text-[#a83f2d]">
+            <svg-icon name="ph:map-pin-line-duotone" size="48px" color="#a83f2d" />
+          </view>
+          <text class="block mt-4 text-[15px] font-bold text-[#78473b]">这条巷子暂时没有线索</text>
+          <text class="block mt-1 text-[11px] text-[#9a8273]">换个关键词吧,好料总在下一转角。</text>
+          <view class="mt-4 inline-block rounded bg-[#a83f2d] px-4 py-2 text-xs text-white" bindtap="onClearSearch">看看全部</view>
+        </view>
+      </view>
+    </view>
+
+    <view class="mx-4 my-5 flex items-center gap-2 text-[#b68635]">
+      <view class="h-px flex-1 bg-[#dbbd83]"></view>
+      <svg-icon name="ph:boat-duotone" size="25px" color="#b68635" />
+      <svg-icon name="ph:lightbulb-duotone" size="20px" color="#b68635" />
+      <view class="h-px flex-1 bg-[#dbbd83]"></view>
+    </view>
+
+    <view class="px-4">
+      <section-header title="特色推荐" barColor="#8f3426" moreText="查看更多" bind:more="onMoreFeature" />
+    </view>
+    <scroll-view class="mt-3" scroll-x>
+      <view class="inline-flex gap-3 pl-4 pr-4">
+        <feature-card wx:for="{{features}}" wx:key="name" item="{{item}}" />
+      </view>
+    </scroll-view>
+
+    <view class="mx-4 my-5 flex items-center gap-2 text-[#a83f2d]">
+      <view class="h-px flex-1 bg-[#dec496]"></view>
+      <svg-icon name="ph:buildings-duotone" size="25px" color="#a83f2d" />
+      <view class="h-px flex-1 bg-[#dec496]"></view>
+    </view>
+
+    <view class="mx-4 mt-2 rounded-xl border border-[#d8ba7d] bg-[#fffaf0] p-4 relative overflow-hidden">
+      <text class="block text-[16px] font-bold text-[#803426]">工作机制</text>
+      <text class="block mt-1 text-[10px] text-[#8f7564]">把文化保护落到机构、管理与人才的每一步。</text>
+      <view class="mt-3 grid grid-cols-3 gap-2">
+        <view
+          wx:for="{{mechanisms}}"
+          wx:key="title"
+          class="flex flex-col items-center rounded-lg border border-[#e0c58e] bg-[#fdf1d9] px-1 py-3 text-[#8d3829]"
+          data-index="{{index}}"
+          bindtap="onMechanism"
+        >
+          <svg-icon name="{{item.icon}}" size="22px" color="#8d3829" />
+          <text class="mt-1 block text-[10px] font-bold">{{item.title}}</text>
+          <text class="mt-1 block text-[8px] text-[#9b806e]">查看详情</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="mx-4 mt-2 rounded-xl border border-[#d8ba7d] bg-[#f5e4c6] p-4 relative overflow-hidden">
+      <text class="block text-[16px] font-bold text-[#803426]">意见反馈</text>
+      <text class="block mt-1 text-[10px] text-[#8f7564]">一盏茶的时间,一起把保护区变得更好。</text>
+      <view class="mt-4 grid grid-cols-2 gap-3">
+        <view
+          wx:for="{{feedback}}"
+          wx:key="title"
+          class="flex flex-col items-center rounded-lg border border-[#caa459] bg-[#fff9ed] py-3 text-[#8d3829]"
+          data-index="{{index}}"
+          bindtap="onFeedback"
+        >
+          <svg-icon name="{{item.icon}}" size="22px" color="#8d3829" />
+          <text class="mt-1 block text-[11px] font-bold">{{item.title}}</text>
+          <text wx:if="{{item.sub}}" class="mt-0.5 block text-[9px] text-[#9b806e]">{{item.sub}}</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="tabbar-placeholder"></view>
+  </scroll-view>
+
+  <van-toast id="van-toast" />
+  <van-dialog id="van-dialog" />
 </view>

+ 15 - 3
src/pages/news/news.wxss

@@ -1,4 +1,16 @@
-.placeholder-desc {
-  margin-top: 16rpx;
-  color: #999999;
+/* news */
+
+.paper {
+  background-color: #faf3e5;
+  background-image: radial-gradient(circle at 16% 8%, rgb(159 55 40 / 5.5%) 0 1px, transparent 1px),
+    radial-gradient(circle at 76% 28%, rgb(181 135 48 / 5%) 0 1px, transparent 1px);
+  background-size: 20px 20px, 27px 27px;
+}
+
+.hide-scroll {
+  scrollbar-width: none;
+}
+
+.hide-scroll::-webkit-scrollbar {
+  display: none;
 }

+ 9 - 2
src/pages/travel/travel.json

@@ -1,4 +1,11 @@
 {
-  "navigationBarTitleText": "文旅",
-  "usingComponents": {}
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "travel-header": "/components/travel-header/index",
+    "route-card": "/components/route-card/index",
+    "section-header": "/components/section-header/index",
+    "svg-icon": "weapp-svg-icon/index",
+    "van-toast": "@vant/weapp/toast/index",
+    "van-dialog": "@vant/weapp/dialog/index"
+  }
 }

+ 240 - 1
src/pages/travel/travel.ts

@@ -1,5 +1,151 @@
+import Dialog from '@vant/weapp/dialog/dialog'
+import Toast from '@vant/weapp/toast/toast'
+
+interface Layer {
+  name: string
+}
+
+interface Mode {
+  name: string
+}
+
+interface Spot {
+  icon: string
+  size: string
+  label: string
+  color: string
+  left: string
+  top: string
+  type: 'spot' | 'nav'
+  name: string
+  hours?: string
+  ticket?: string
+  stamp?: string
+}
+
+interface RouteItem {
+  icon: string
+  title: string
+  desc: string
+}
+
+interface HeritageItem {
+  icon: string
+  title: string
+  desc: string
+  action: string
+}
+
+interface StayItem {
+  title: string
+  desc: string
+}
+
 Component({
-  data: {},
+  data: {
+    layers: [
+      { name: '古城人文' },
+      { name: '世遗土楼' },
+      { name: '东山滨海' },
+      { name: '闽南美食' },
+    ] as Layer[],
+
+    activeLayer: '古城人文',
+
+    modes: [
+      { name: '步行' },
+      { name: '自驾' },
+    ] as Mode[],
+
+    activeMode: '步行',
+
+    spots: [
+      {
+        icon: 'ph:buildings-fill',
+        size: '29px',
+        label: '土楼',
+        color: '#a83f2d',
+        left: '15%',
+        top: '26%',
+        type: 'spot',
+        name: '土楼',
+        hours: '09:00-17:30',
+        ticket: '免费',
+        stamp: '土楼手绘章',
+      },
+      {
+        icon: 'ph:bank-fill',
+        size: '29px',
+        label: '文庙',
+        color: '#a83f2d',
+        left: '51%',
+        top: '27%',
+        type: 'spot',
+        name: '漳州文庙',
+        hours: '08:30-18:00',
+        ticket: '免费',
+        stamp: '文脉印记',
+      },
+      {
+        icon: 'ph:boat-fill',
+        size: '30px',
+        label: '漳船',
+        color: '#287b8e',
+        left: '68%',
+        top: '57%',
+        type: 'nav',
+        name: '漳州古城水岸',
+      },
+      {
+        icon: 'ph:bowl-food-fill',
+        size: '28px',
+        label: '卤面',
+        color: '#a65b2d',
+        left: '32%',
+        top: '64%',
+        type: 'nav',
+        name: '古城美食街',
+      },
+      {
+        icon: 'ph:leaf-fill',
+        size: '28px',
+        label: '木偶戏',
+        color: '#4c8c61',
+        left: '52%',
+        top: '64%',
+        type: 'nav',
+        name: '木偶戏馆',
+      },
+    ] as Spot[],
+
+    routes: [
+      { icon: 'ph:buildings-duotone', title: '古城逛吃一日线', desc: '文庙 + 牌坊 + 木偶戏 + 小吃老街' },
+      { icon: 'ph:camera-duotone', title: '土楼风光摄影线', desc: '田螺坑四菜一汤 + 云水谣古榕' },
+      { icon: 'ph:waves-duotone', title: '东山滨海治愈线', desc: '苏峰山环岛 + 南门湾 + 赶海沙滩' },
+      { icon: 'ph:student-duotone', title: '亲子民俗研学线', desc: '香蕉海 + 埭美古村 + 手工体验' },
+    ] as RouteItem[],
+
+    heritages: [
+      {
+        icon: 'ph:buildings-duotone',
+        title: '特色村镇/街区',
+        desc: '古城骑楼、牌坊街与红砖古厝,边走边看闽南生活。',
+        action: '查看街区亮点',
+      },
+      {
+        icon: 'ph:house-line-duotone',
+        title: '非遗工坊',
+        desc: '汇集各地非遗工坊资源,呈现传统技艺生产、展示与体验空间。',
+        action: '探索非遗工坊',
+      },
+    ] as HeritageItem[],
+
+    stays: [
+      { title: '古城古厝民宿', desc: '红砖墙风格·看图选房' },
+      { title: '土楼山间小院', desc: '推窗见山·看图选房' },
+      { title: '东山海景客栈', desc: '枕着海浪眠·看图选房' },
+    ] as StayItem[],
+  },
 
   pageLifetimes: {
     show() {
@@ -8,4 +154,97 @@ Component({
       }
     },
   },
+
+  methods: {
+    onSearch(e: WechatMiniprogram.CustomEvent) {
+      const keyword = ((e.detail.keyword as string) || '').trim()
+      if (!keyword) {
+        Toast('试试搜索:古城、土楼、卤面、木偶或海景房')
+        return
+      }
+      const types = ['景点', '美食', '民宿', '活动']
+      const type = types[Math.floor(Math.random() * types.length)]
+      Toast(`为你找到 8 个与“${keyword}”相关的${type}结果`)
+    },
+
+    onLocate() {
+      Toast('定位成功:漳州古城中山公园,已更新附近点位')
+    },
+
+    onLayerTap(e: WechatMiniprogram.TouchEvent) {
+      const name = e.currentTarget.dataset.name as string
+      if (!name) {
+        return
+      }
+      this.setData({ activeLayer: name })
+      Toast(`已切换:${name}`)
+    },
+
+    onModeTap(e: WechatMiniprogram.TouchEvent) {
+      const name = e.currentTarget.dataset.name as string
+      if (!name) {
+        return
+      }
+      this.setData({ activeMode: name })
+      Toast(`路线模式已切换为${name}`)
+    },
+
+    onSpotTap(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const spot = (this.data.spots as Spot[])[index]
+      if (!spot) {
+        return
+      }
+      if (spot.type === 'nav') {
+        Toast(`已规划前往“${spot.name}”的最佳路线`)
+        return
+      }
+      Dialog.confirm({
+        title: spot.name,
+        message: `开放时间:${spot.hours}\n门票:${spot.ticket}\n\n抵达后自动领取:${spot.stamp}`,
+        confirmButtonText: '一键导航',
+        cancelButtonText: '预约入口',
+      }).then(() => {
+        Toast(`已规划前往“${spot.name}”的最佳路线`)
+      }).catch(() => {
+        Toast(`已打开${spot.name}预约入口`)
+      })
+    },
+
+    onMapNav() {
+      Toast(`已规划前往“${this.data.activeLayer}”点位的最佳路线`)
+    },
+
+    onMoreHeritage() {
+      Toast('非遗特色已为你展开')
+    },
+
+    onMoreStay() {
+      Toast('更多非遗特色住宿正在加载')
+    },
+
+    onHeritageAction(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const item = (this.data.heritages as HeritageItem[])[index]
+      if (!item) {
+        return
+      }
+      Toast(`已打开${item.title}详情`)
+    },
+
+    onStayBook(e: WechatMiniprogram.TouchEvent) {
+      const index = Number(e.currentTarget.dataset.index)
+      const item = (this.data.stays as StayItem[])[index]
+      if (!item) {
+        return
+      }
+      Dialog.alert({
+        title: '特色住宿',
+        message: `${item.title}\n可查看庭院、房型与窗景照片,当前周末尚有余房。\n\n到店前 24 小时可免费取消`,
+        confirmButtonText: '选择房型并预订',
+      }).then(() => {
+        Toast('预订信息已提交')
+      })
+    },
+  },
 })

+ 133 - 3
src/pages/travel/travel.wxml

@@ -1,4 +1,134 @@
-<view class="min-h-screen flex flex-col items-center justify-center">
-  <text class="font-semibold">乐游</text>
-  <text class="placeholder-desc">内容占位符</text>
+<view class="h-screen flex flex-col bg-[#f9f3e7]">
+  <travel-header bind:search="onSearch" />
+
+  <scroll-view class="flex-1 min-h-0" scroll-y>
+    <view class="pb-5">
+      <view class="mt-5 px-4">
+        <view class="flex items-end justify-between">
+          <text class="text-[16px] font-bold text-[#843428]">文化地图</text>
+          <view class="flex items-center gap-1 text-[10px] text-[#a83f2d]" bindtap="onLocate">
+            <svg-icon name="ph:navigation-arrow" size="14px" color="#a83f2d" />
+            <text>自动定位</text>
+          </view>
+        </view>
+
+        <scroll-view class="hide-scroll mt-3" scroll-x>
+          <view class="inline-flex gap-2 whitespace-nowrap">
+            <view
+              wx:for="{{layers}}"
+              wx:key="name"
+              class="shrink-0 rounded-full px-3 py-1.5 text-[9px] {{activeLayer === item.name ? 'bg-[#a83f2d] text-white' : 'border border-[#dcc598] text-[#765c4c]'}}"
+              data-name="{{item.name}}"
+              bindtap="onLayerTap"
+            >
+              {{item.name}}
+            </view>
+          </view>
+        </scroll-view>
+
+        <view class="map-paper relative mt-3 h-[250px] overflow-hidden rounded-2xl border border-[#d7a94e] p-3">
+          <view class="flex items-center justify-between">
+            <text class="rounded-md bg-white/90 px-2 py-1 text-[10px] text-[#765c4c]">{{activeLayer}}图层</text>
+            <view class="flex overflow-hidden rounded-md border border-[#c08a43] bg-white/90 text-[9px]">
+              <view
+                wx:for="{{modes}}"
+                wx:key="name"
+                class="px-2 py-1.5 {{activeMode === item.name ? 'bg-[#a83f2d] text-white' : 'text-[#765c4c]'}}"
+                data-name="{{item.name}}"
+                bindtap="onModeTap"
+              >
+                {{item.name}}
+              </view>
+            </view>
+          </view>
+
+          <view
+            wx:for="{{spots}}"
+            wx:key="label"
+            class="absolute flex flex-col items-center"
+            style="left: {{item.left}}; top: {{item.top}};"
+            data-index="{{index}}"
+            bindtap="onSpotTap"
+          >
+            <svg-icon name="{{item.icon}}" size="{{item.size}}" color="{{item.color}}" />
+            <text class="mt-1 rounded bg-white/90 px-1 text-[9px]">{{item.label}}</text>
+          </view>
+
+          <view
+            class="absolute bottom-3 right-3 flex items-center gap-1 rounded-full bg-[#a83f2d] px-3 py-1.5 text-[9px] text-white shadow"
+            bindtap="onMapNav"
+          >
+            <svg-icon name="ph:map-pin-line" size="12px" color="#ffffff" />
+            <text>导航</text>
+          </view>
+        </view>
+      </view>
+
+      <view class="mt-6">
+        <view class="flex items-center justify-between px-4">
+          <text class="text-[16px] font-bold text-[#843428]">主题路线</text>
+          <text class="text-[9px] text-[#a47c45]">向左滑动,查看更多路线</text>
+        </view>
+        <scroll-view class="hide-scroll mt-3" scroll-x>
+          <view class="inline-flex gap-3 px-4">
+            <route-card wx:for="{{routes}}" wx:key="title" item="{{item}}" />
+          </view>
+        </scroll-view>
+      </view>
+
+      <view class="mt-6 px-4">
+        <section-header title="非遗特色" barColor="#843428" moreText="查看更多" bind:more="onMoreHeritage" />
+        <view class="mt-3 space-y-3">
+          <view
+            wx:for="{{heritages}}"
+            wx:key="title"
+            class="rounded-2xl border border-[#dec18b] bg-white p-3 shadow-sm"
+          >
+            <view class="flex items-center gap-3">
+              <view class="h-11 w-11 shrink-0 rounded-xl bg-[#f2dfba] flex items-center justify-center">
+                <svg-icon name="{{item.icon}}" size="25px" color="#9a3b2d" />
+              </view>
+              <view class="min-w-0 flex-1">
+                <text class="block text-[13px] font-bold text-[#673d33]">{{item.title}}</text>
+                <text class="mt-1 block text-[9px] leading-4 text-[#8d7768]">{{item.desc}}</text>
+              </view>
+            </view>
+            <view
+              class="mt-3 w-full rounded-lg bg-[#f4e4c7] py-2 text-center text-[10px] text-[#9a3b2d]"
+              data-index="{{index}}"
+              bindtap="onHeritageAction"
+            >
+              {{item.action}}
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <view class="mt-6 px-4">
+        <section-header title="非遗特色住宿" barColor="#843428" moreText="查看更多" bind:more="onMoreStay" />
+        <view class="mt-3 space-y-2">
+          <view
+            wx:for="{{stays}}"
+            wx:key="title"
+            class="rounded-2xl border border-[#dec18b] bg-[#f4e4c7] p-3 shadow-sm"
+          >
+            <text class="block text-[12px] font-bold text-[#673d33]">{{item.title}}</text>
+            <text class="mt-1 block text-[9px] leading-4 text-[#8d7768]">{{item.desc}}</text>
+            <view
+              class="mt-2 inline-block text-[10px] text-[#a83f2d]"
+              data-index="{{index}}"
+              bindtap="onStayBook"
+            >
+              在线预订
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <view class="tabbar-placeholder"></view>
+    </view>
+  </scroll-view>
+
+  <van-toast id="van-toast" />
+  <van-dialog id="van-dialog" />
 </view>

+ 15 - 3
src/pages/travel/travel.wxss

@@ -1,4 +1,16 @@
-.placeholder-desc {
-  margin-top: 16rpx;
-  color: #999999;
+/* travel */
+
+.map-paper {
+  background-color: #f5e8c9;
+  background-image: radial-gradient(circle at 20% 25%, #b9d2ad 0 25px, transparent 26px),
+    radial-gradient(circle at 74% 66%, #b8d6df 0 39px, transparent 40px),
+    linear-gradient(28deg, transparent 47%, rgb(175 107 60 / 18%) 48% 50%, transparent 51%);
+}
+
+.hide-scroll {
+  scrollbar-width: none;
+}
+
+.hide-scroll::-webkit-scrollbar {
+  display: none;
 }

+ 9 - 2
src/pages/user/user.json

@@ -1,4 +1,11 @@
 {
-  "navigationBarTitleText": "我的",
-  "usingComponents": {}
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "profile-header": "/components/profile-header/index",
+    "favorites-panel": "/components/favorites-panel/index",
+    "menu-item": "/components/menu-item/index",
+    "svg-icon": "weapp-svg-icon/index",
+    "van-toast": "@vant/weapp/toast/index",
+    "van-dialog": "@vant/weapp/dialog/index"
+  }
 }

+ 87 - 1
src/pages/user/user.ts

@@ -1,5 +1,28 @@
+import Dialog from '@vant/weapp/dialog/dialog'
+import Toast from '@vant/weapp/toast/toast'
+
+interface SettingItem {
+  icon: string
+  title: string
+  subtitle: string
+}
+
 Component({
-  data: {},
+  data: {
+    nickname: '燕尾巷游客',
+    avatarIcon: 'ph:user-circle-duotone',
+    avatarIcons: ['ph:user-circle-duotone', 'ph:smiley-duotone', 'ph:flower-lotus-duotone'],
+    avatarIndex: 0,
+
+    settings: [
+      { icon: 'ph:pen-nib-duotone', title: '我的投稿', subtitle: '记录我与闽南文化的故事' },
+      { icon: 'ph:user-plus-duotone', title: '邀请好友', subtitle: '分享闽南文化之旅' },
+      { icon: 'ph:headset-duotone', title: '在线客服', subtitle: '获取实时人工服务' },
+      { icon: 'ph:shield-check-duotone', title: '隐私设置', subtitle: '管理授权、位置与消息权限' },
+      { icon: 'ph:broom-duotone', title: '清除缓存', subtitle: '当前缓存 18.6 MB' },
+      { icon: 'ph:info-duotone', title: '关于小程序', subtitle: '漳州文旅公共服务平台' },
+    ] as SettingItem[],
+  },
 
   pageLifetimes: {
     show() {
@@ -8,4 +31,67 @@ Component({
       }
     },
   },
+
+  methods: {
+    onChangeAvatar() {
+      const icons = this.data.avatarIcons as string[]
+      const next = ((this.data.avatarIndex as number) + 1) % icons.length
+      this.setData({ avatarIcon: icons[next], avatarIndex: next })
+      Toast('头像已更换')
+    },
+
+    onEditProfile() {
+      Dialog.alert({
+        title: '编辑资料',
+        message: '昵称编辑功能建设中,敬请期待。',
+        confirmButtonText: '我知道了',
+      })
+    },
+
+    onShowMessages() {
+      Dialog.alert({
+        title: '消息通知中心',
+        message: '08-08 木偶戏预约提醒\n08-07 古城七夕灯游会预告\n08-06 文旅惠民消费券政策公告',
+        confirmButtonText: '我知道了',
+      })
+    },
+
+    onFeedbackProgress() {
+      Dialog.alert({
+        title: '意见反馈进度',
+        message: '关于景区导览标识的建议已受理,当前进度:相关部门处理中,预计 2 个工作日内答复。',
+        confirmButtonText: '我知道了',
+      })
+    },
+
+    onGeneratePoster() {
+      Dialog.alert({
+        title: '打卡纪念海报',
+        message: '我在漳州集齐山海烟火\n7 枚印章 · 5 枚徽章 · 1,280 积分\n\n海报已生成,可长按保存并分享朋友圈。',
+        confirmButtonText: '我知道了',
+      })
+    },
+
+    onSettingTap(e: WechatMiniprogram.TouchEvent) {
+      const title = e.currentTarget.dataset.title as string
+      const map: Record<string, { toast?: string, dialog?: string }> = {
+        我的投稿: { dialog: '投稿入口已为你打开,欢迎分享你与闽南文化的故事。' },
+        邀请好友: { dialog: '邀请链接已生成,快把这份闽南文化之旅分享给好友吧。' },
+        在线客服: { dialog: '在线客服已收到你的咨询,请稍候为你接入服务。' },
+        隐私设置: { dialog: '位置权限:仅使用期间;消息通知:已开启;个性化推荐:已关闭。' },
+        清除缓存: { toast: '缓存已清除,本次释放 18.6 MB。' },
+        关于小程序: { dialog: '漳州文旅公共服务平台,为游客提供文化、出游、预约与便民服务。' },
+      }
+      const item = map[title]
+      if (!item) {
+        return
+      }
+      if (item.dialog) {
+        Dialog.alert({ title, message: item.dialog, confirmButtonText: '我知道了' })
+      }
+      else if (item.toast) {
+        Toast(item.toast)
+      }
+    },
+  },
 })

+ 82 - 3
src/pages/user/user.wxml

@@ -1,4 +1,83 @@
-<view class="min-h-screen flex flex-col items-center justify-center">
-  <text class="font-semibold">我的</text>
-  <text class="placeholder-desc">内容占位符</text>
+<view class="h-screen flex flex-col paper">
+  <profile-header
+    nickname="{{nickname}}"
+    avatar-icon="{{avatarIcon}}"
+    bind:changeavatar="onChangeAvatar"
+    bind:edit="onEditProfile"
+  />
+
+  <scroll-view class="flex-1 min-h-0" scroll-y>
+    <view class="px-4 pt-4">
+      <favorites-panel />
+    </view>
+
+    <view class="px-4 pt-5">
+      <view class="rounded-2xl bg-[#9b382c] p-5 text-center text-white" bindtap="onGeneratePoster">
+        <view class="flex justify-center">
+          <svg-icon name="ph:flower-lotus-duotone" size="45px" color="#f4d383" />
+        </view>
+        <text class="block mt-2 text-[16px] font-bold">我在漳州集齐山海烟火</text>
+        <text class="block mt-1 text-[9px] text-[#f6d99d]">7 枚印章 · 5 枚徽章 · 1,280 积分</text>
+        <view class="mt-3 inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1.5 text-[10px]">
+          <text>生成打卡纪念海报</text>
+          <svg-icon name="ph:arrow-right-bold" size="11px" color="#ffffff" />
+        </view>
+      </view>
+    </view>
+
+    <view class="px-4 pt-5">
+      <text class="text-[15px] font-bold text-[#873428]">反馈与消息</text>
+      <view class="mt-3 overflow-hidden rounded-2xl border border-[#e5d2af] bg-white divide-y divide-[#efe5d3]">
+        <menu-item
+          icon="ph:bell-ringing-duotone"
+          icon-bg="#f7e2dc"
+          icon-color="#a83f2d"
+          title="消息通知中心"
+          subtitle="活动预告、政策公告、预约提醒"
+          right-text="3 未读"
+          right-text-bg="#a83f2d"
+          right-text-color="#ffffff"
+          badge="{{true}}"
+          bind:select="onShowMessages"
+        />
+        <menu-item
+          icon="ph:chat-circle-dots-duotone"
+          icon-bg="#f5e8c9"
+          icon-color="#b08031"
+          title="我的意见反馈"
+          subtitle="历史投诉与建议 · 查看处理进度"
+          right-text="处理中"
+          right-text-bg="transparent"
+          right-text-color="#b17d32"
+          right-text-size="9px"
+          bind:select="onFeedbackProgress"
+        />
+      </view>
+    </view>
+
+    <view class="px-4 pt-5">
+      <text class="text-[15px] font-bold text-[#873428]">便民设置</text>
+      <view class="mt-3 overflow-hidden rounded-2xl border border-[#e5d2af] bg-white divide-y divide-[#efe5d3]">
+        <menu-item
+          wx:for="{{settings}}"
+          wx:key="title"
+          icon="{{item.icon}}"
+          icon-box-class="w-8 h-8"
+          icon-bg="#f7e6df"
+          icon-color="#a83f2d"
+          icon-size="19px"
+          title="{{item.title}}"
+          subtitle="{{item.subtitle}}"
+          chevron="{{true}}"
+          data-title="{{item.title}}"
+          bind:select="onSettingTap"
+        />
+      </view>
+    </view>
+
+    <view class="tabbar-placeholder"></view>
+  </scroll-view>
+
+  <van-toast id="van-toast" />
+  <van-dialog id="van-dialog" />
 </view>

+ 8 - 4
src/pages/user/user.wxss

@@ -1,4 +1,8 @@
-.placeholder-desc {
-  margin-top: 16rpx;
-  color: #999999;
-}
+/* user */
+
+.paper {
+  background-color: #faf3e5;
+  background-image: radial-gradient(circle at 16% 8%, rgb(159 55 40 / 5.5%) 0 1px, transparent 1px),
+    radial-gradient(circle at 76% 28%, rgb(181 135 48 / 5%) 0 1px, transparent 1px);
+  background-size: 20px 20px, 27px 27px;
+}

+ 3 - 0
weapp-vite.config.ts

@@ -37,6 +37,9 @@ export default defineConfig({
       //   page: 'index',
       // },
     },
+    hmr: {
+      runtime: "classic"
+    }
   },
   css: {
     preprocessorOptions: {