From fe5a5b6c0f87c6f7f1d9f2c259ce6a741013ebb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Thu, 30 Jul 2026 14:11:51 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=AB=AF=202.?= =?UTF-8?q?=20=E8=A7=86=E9=A2=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/Code-Standards.mdc | 9 + .cursor/rules/wot-ui.mdc | 12 + .env.development | 2 + .env.production | 2 + design-system/年糕崽崽/MASTER.md | 203 +++++++++ design-system/年糕崽崽/pages/mine.md | 52 +++ pnpm-workspace.yaml | 5 + src/api/index.ts | 411 ++++++++++++++++++ .../jetbrains-mono-latin-400-normal.woff | Bin 0 -> 27496 bytes .../playfair-display-latin-400-italic.woff | Bin 0 -> 27268 bytes .../playfair-display-latin-400-normal.woff | Bin 0 -> 26900 bytes .../playfair-display-latin-600-normal.woff | Bin 0 -> 28356 bytes .../playfair-display-latin-700-normal.woff | Bin 0 -> 28296 bytes src/components/c-end/AmbientBackground.vue | 284 ++++++++++++ src/components/c-end/ArtMenuGroup.vue | 64 +++ src/components/c-end/ArtMenuItem.vue | 136 ++++++ src/components/c-end/ArtSearchBar.vue | 94 ++++ src/components/c-end/PostCard.vue | 97 +++++ src/components/c-end/ProfileHero.vue | 111 +++++ src/components/layout/AppFloatingTabbar.vue | 64 +++ src/components/layout/AppNavbar.vue | 100 +++++ src/components/layout/AppPageShell.vue | 65 +++ src/composables/useAppMode.ts | 75 ++++ src/composables/useAppTabbar.ts | 101 +++++ src/composables/useAuth.ts | 117 +++++ src/composables/usePublicSettings.ts | 53 +++ src/composables/useSearchHistory.ts | 50 +++ src/composables/useTheme.ts | 145 ++++++ src/config/menus.ts | 107 +++++ src/env.d.ts | 10 + src/pages/explore/index.vue | 86 ++++ src/pages/home/index.vue | 244 +++++++++++ src/pages/mine/index.vue | 263 +++++++++++ src/resolvers/wot-ui-resolver.ts | 17 + src/static/default-avatar.svg | 1 + src/styles/art-fonts.scss | 48 ++ src/styles/art-tokens.scss | 322 ++++++++++++++ .../admin/components/AdminBarChart.vue | 161 +++++++ .../admin/components/AdminDetailDrawer.vue | 111 +++++ .../admin/components/AdminFilterBar.vue | 111 +++++ .../admin/components/AdminFormField.vue | 29 ++ .../admin/components/AdminLineChart.vue | 226 ++++++++++ .../admin/components/AdminMediaPicker.vue | 113 +++++ .../admin/components/AdminStatusBadge.vue | 60 +++ .../admin/composables/useAdminGuard.ts | 20 + .../admin/config/resourceSchemas.ts | 380 ++++++++++++++++ .../admin/config/settingsSchema.ts | 62 +++ src/subPackages/admin/pages/about/index.vue | 185 ++++++++ .../admin/pages/analytics/index.vue | 161 +++++++ .../admin/pages/attachments/index.vue | 361 +++++++++++++++ src/subPackages/admin/pages/columns/form.vue | 195 +++++++++ .../admin/pages/dashboard/index.vue | 228 ++++++++++ src/subPackages/admin/pages/explore/index.vue | 132 ++++++ .../admin/pages/inquiries/list.vue | 150 +++++++ .../admin/pages/oss-configs/index.vue | 206 +++++++++ src/subPackages/admin/pages/posts/form.vue | 354 +++++++++++++++ src/subPackages/admin/pages/posts/list.vue | 187 ++++++++ src/subPackages/admin/pages/profile/index.vue | 199 +++++++++ src/subPackages/admin/pages/resource/form.vue | 251 +++++++++++ src/subPackages/admin/pages/resource/list.vue | 404 +++++++++++++++++ .../admin/pages/settings/index.vue | 162 +++++++ src/subPackages/admin/pages/snippets/form.vue | 162 +++++++ src/subPackages/admin/pages/users/form.vue | 144 ++++++ src/subPackages/admin/pages/users/list.vue | 147 +++++++ .../admin/pages/video-albums/form.vue | 187 ++++++++ src/subPackages/admin/pages/videos/form.vue | 174 ++++++++ src/subPackages/admin/pages/videos/list.vue | 92 ++++ src/subPackages/admin/pages/works/form.vue | 211 +++++++++ src/subPackages/admin/pages/works/list.vue | 126 ++++++ .../content/components/CodeBlock.vue | 115 +++++ .../content/components/ContentPageHeader.vue | 34 ++ .../content/components/StaffLoginForm.vue | 88 ++++ src/subPackages/content/lib/codeHighlight.ts | 24 + .../content/lib/markdownRenderer.ts | 120 +++++ src/subPackages/content/pages/about/index.vue | 214 +++++++++ src/subPackages/content/pages/blog/detail.vue | 368 ++++++++++++++++ src/subPackages/content/pages/blog/index.vue | 239 ++++++++++ src/subPackages/content/pages/blog/ppt.vue | 60 +++ .../content/pages/columns/detail.vue | 104 +++++ .../content/pages/columns/index.vue | 67 +++ src/subPackages/content/pages/login/index.vue | 133 ++++++ .../content/pages/services/index.vue | 396 +++++++++++++++++ .../content/pages/snippets/index.vue | 94 ++++ src/subPackages/content/pages/theme/index.vue | 70 +++ .../content/pages/videos/album.vue | 112 +++++ .../content/pages/videos/detail.vue | 176 ++++++++ .../content/pages/videos/index.vue | 217 +++++++++ .../content/pages/works/detail.vue | 225 ++++++++++ src/subPackages/content/pages/works/index.vue | 164 +++++++ src/utils/content.ts | 62 +++ src/utils/markdownRenderer.ts | 103 +++++ src/utils/request.ts | 184 ++++++++ src/utils/unsupported.ts | 20 + 93 files changed, 12430 insertions(+) create mode 100644 .cursor/rules/Code-Standards.mdc create mode 100644 .cursor/rules/wot-ui.mdc create mode 100644 .env.development create mode 100644 .env.production create mode 100644 design-system/年糕崽崽/MASTER.md create mode 100644 design-system/年糕崽崽/pages/mine.md create mode 100644 pnpm-workspace.yaml create mode 100644 src/api/index.ts create mode 100644 src/assets/fonts/jetbrains-mono-latin-400-normal.woff create mode 100644 src/assets/fonts/playfair-display-latin-400-italic.woff create mode 100644 src/assets/fonts/playfair-display-latin-400-normal.woff create mode 100644 src/assets/fonts/playfair-display-latin-600-normal.woff create mode 100644 src/assets/fonts/playfair-display-latin-700-normal.woff create mode 100644 src/components/c-end/AmbientBackground.vue create mode 100644 src/components/c-end/ArtMenuGroup.vue create mode 100644 src/components/c-end/ArtMenuItem.vue create mode 100644 src/components/c-end/ArtSearchBar.vue create mode 100644 src/components/c-end/PostCard.vue create mode 100644 src/components/c-end/ProfileHero.vue create mode 100644 src/components/layout/AppFloatingTabbar.vue create mode 100644 src/components/layout/AppNavbar.vue create mode 100644 src/components/layout/AppPageShell.vue create mode 100644 src/composables/useAppMode.ts create mode 100644 src/composables/useAppTabbar.ts create mode 100644 src/composables/useAuth.ts create mode 100644 src/composables/usePublicSettings.ts create mode 100644 src/composables/useSearchHistory.ts create mode 100644 src/composables/useTheme.ts create mode 100644 src/config/menus.ts create mode 100644 src/env.d.ts create mode 100644 src/pages/explore/index.vue create mode 100644 src/pages/home/index.vue create mode 100644 src/pages/mine/index.vue create mode 100644 src/resolvers/wot-ui-resolver.ts create mode 100644 src/static/default-avatar.svg create mode 100644 src/styles/art-fonts.scss create mode 100644 src/styles/art-tokens.scss create mode 100644 src/subPackages/admin/components/AdminBarChart.vue create mode 100644 src/subPackages/admin/components/AdminDetailDrawer.vue create mode 100644 src/subPackages/admin/components/AdminFilterBar.vue create mode 100644 src/subPackages/admin/components/AdminFormField.vue create mode 100644 src/subPackages/admin/components/AdminLineChart.vue create mode 100644 src/subPackages/admin/components/AdminMediaPicker.vue create mode 100644 src/subPackages/admin/components/AdminStatusBadge.vue create mode 100644 src/subPackages/admin/composables/useAdminGuard.ts create mode 100644 src/subPackages/admin/config/resourceSchemas.ts create mode 100644 src/subPackages/admin/config/settingsSchema.ts create mode 100644 src/subPackages/admin/pages/about/index.vue create mode 100644 src/subPackages/admin/pages/analytics/index.vue create mode 100644 src/subPackages/admin/pages/attachments/index.vue create mode 100644 src/subPackages/admin/pages/columns/form.vue create mode 100644 src/subPackages/admin/pages/dashboard/index.vue create mode 100644 src/subPackages/admin/pages/explore/index.vue create mode 100644 src/subPackages/admin/pages/inquiries/list.vue create mode 100644 src/subPackages/admin/pages/oss-configs/index.vue create mode 100644 src/subPackages/admin/pages/posts/form.vue create mode 100644 src/subPackages/admin/pages/posts/list.vue create mode 100644 src/subPackages/admin/pages/profile/index.vue create mode 100644 src/subPackages/admin/pages/resource/form.vue create mode 100644 src/subPackages/admin/pages/resource/list.vue create mode 100644 src/subPackages/admin/pages/settings/index.vue create mode 100644 src/subPackages/admin/pages/snippets/form.vue create mode 100644 src/subPackages/admin/pages/users/form.vue create mode 100644 src/subPackages/admin/pages/users/list.vue create mode 100644 src/subPackages/admin/pages/video-albums/form.vue create mode 100644 src/subPackages/admin/pages/videos/form.vue create mode 100644 src/subPackages/admin/pages/videos/list.vue create mode 100644 src/subPackages/admin/pages/works/form.vue create mode 100644 src/subPackages/admin/pages/works/list.vue create mode 100644 src/subPackages/content/components/CodeBlock.vue create mode 100644 src/subPackages/content/components/ContentPageHeader.vue create mode 100644 src/subPackages/content/components/StaffLoginForm.vue create mode 100644 src/subPackages/content/lib/codeHighlight.ts create mode 100644 src/subPackages/content/lib/markdownRenderer.ts create mode 100644 src/subPackages/content/pages/about/index.vue create mode 100644 src/subPackages/content/pages/blog/detail.vue create mode 100644 src/subPackages/content/pages/blog/index.vue create mode 100644 src/subPackages/content/pages/blog/ppt.vue create mode 100644 src/subPackages/content/pages/columns/detail.vue create mode 100644 src/subPackages/content/pages/columns/index.vue create mode 100644 src/subPackages/content/pages/login/index.vue create mode 100644 src/subPackages/content/pages/services/index.vue create mode 100644 src/subPackages/content/pages/snippets/index.vue create mode 100644 src/subPackages/content/pages/theme/index.vue create mode 100644 src/subPackages/content/pages/videos/album.vue create mode 100644 src/subPackages/content/pages/videos/detail.vue create mode 100644 src/subPackages/content/pages/videos/index.vue create mode 100644 src/subPackages/content/pages/works/detail.vue create mode 100644 src/subPackages/content/pages/works/index.vue create mode 100644 src/utils/content.ts create mode 100644 src/utils/markdownRenderer.ts create mode 100644 src/utils/request.ts create mode 100644 src/utils/unsupported.ts diff --git a/.cursor/rules/Code-Standards.mdc b/.cursor/rules/Code-Standards.mdc new file mode 100644 index 0000000..66a5cb3 --- /dev/null +++ b/.cursor/rules/Code-Standards.mdc @@ -0,0 +1,9 @@ +--- +description: +alwaysApply: true +--- + +1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则 +2. 注意不要生成太多的空行,上一部分代码和下一部分代码中间的空行不要大于2行 +3. 有封装好的方法、组件需要复用,不要重复造轮子 +4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面 diff --git a/.cursor/rules/wot-ui.mdc b/.cursor/rules/wot-ui.mdc new file mode 100644 index 0000000..245bf75 --- /dev/null +++ b/.cursor/rules/wot-ui.mdc @@ -0,0 +1,12 @@ +--- +description: 小程序端优先使用 wot-ui 组件 +alwaysApply: true +--- + +# Wot UI 使用规范(blog-wot-uniapp) + +1. UI 优先使用 `@wot-ui/ui` 已有组件(`wd-*`),例如底部菜单用 `wd-action-sheet`,对话框用 `wd-dialog`,表单用 `wd-input` / `wd-button`。 +2. 现有组件能力不足时:先二次封装(在 wot 外包一层交互与 art 样式,如 `ArtMenuItem`),再自研通用组件,放到主包 `components/` 或分包 `components/`。 +3. 禁止为同一交互再造一套原生弹层/列表(例如自写底部菜单替代 `wd-action-sheet`)。 +4. 抽屉类仍遵守工作区规则:小程序端用 `page-container`,防止用户意外退出页面。 +5. C 端视觉用 art token / `themeVars` 覆盖 wot 主题色与深色背景,而不是放弃 wot 组件。 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..cb5e7f2 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_API_BASE=http://127.0.0.1:8081/api +VITE_UPLOAD_BASE=http://127.0.0.1:8081 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..e82e1ef --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +VITE_API_BASE=https://blog.nailaoyun.cn/api +VITE_UPLOAD_BASE=https://blog.nailaoyun.cn diff --git a/design-system/年糕崽崽/MASTER.md b/design-system/年糕崽崽/MASTER.md new file mode 100644 index 0000000..3dde419 --- /dev/null +++ b/design-system/年糕崽崽/MASTER.md @@ -0,0 +1,203 @@ +# Design System Master File + +> **LOGIC:** When building a specific page, first check `design-system/pages/[page-name].md`. +> If that file exists, its rules **override** this Master file. +> If not, strictly follow the rules below. + +--- + +**Project:** 年糕崽崽 +**Generated:** 2026-07-27 14:56:05 +**Category:** Digital Products/Downloads + +--- + +## Global Rules + +### Color Palette + +| Role | Hex | CSS Variable | +|------|-----|--------------| +| Primary | `#6366F1` | `--color-primary` | +| Secondary | `#818CF8` | `--color-secondary` | +| CTA/Accent | `#22C55E` | `--color-cta` | +| Background | `#EEF2FF` | `--color-background` | +| Text | `#312E81` | `--color-text` | + +**Color Notes:** Digital indigo + buy green + +### Typography + +- **Heading Font:** Archivo +- **Body Font:** Space Grotesk +- **Mood:** minimal, portfolio, designer, creative, clean, artistic +- **Google Fonts:** [Archivo + Space Grotesk](https://fonts.google.com/share?selection.family=Archivo:wght@300;400;500;600;700|Space+Grotesk:wght@300;400;500;600;700) + +**CSS Import:** +```css +@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap'); +``` + +### Spacing Variables + +| Token | Value | Usage | +|-------|-------|-------| +| `--space-xs` | `4px` / `0.25rem` | Tight gaps | +| `--space-sm` | `8px` / `0.5rem` | Icon gaps, inline spacing | +| `--space-md` | `16px` / `1rem` | Standard padding | +| `--space-lg` | `24px` / `1.5rem` | Section padding | +| `--space-xl` | `32px` / `2rem` | Large gaps | +| `--space-2xl` | `48px` / `3rem` | Section margins | +| `--space-3xl` | `64px` / `4rem` | Hero padding | + +### Shadow Depths + +| Level | Value | Usage | +|-------|-------|-------| +| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.05)` | Subtle lift | +| `--shadow-md` | `0 4px 6px rgba(0,0,0,0.1)` | Cards, buttons | +| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns | +| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured cards | + +--- + +## Component Specs + +### Buttons + +```css +/* Primary Button */ +.btn-primary { + background: #22C55E; + color: white; + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + transition: all 200ms ease; + cursor: pointer; +} + +.btn-primary:hover { + opacity: 0.9; + transform: translateY(-1px); +} + +/* Secondary Button */ +.btn-secondary { + background: transparent; + color: #6366F1; + border: 2px solid #6366F1; + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + transition: all 200ms ease; + cursor: pointer; +} +``` + +### Cards + +```css +.card { + background: #EEF2FF; + border-radius: 12px; + padding: 24px; + box-shadow: var(--shadow-md); + transition: all 200ms ease; + cursor: pointer; +} + +.card:hover { + box-shadow: var(--shadow-lg); + transform: translateY(-2px); +} +``` + +### Inputs + +```css +.input { + padding: 12px 16px; + border: 1px solid #E2E8F0; + border-radius: 8px; + font-size: 16px; + transition: border-color 200ms ease; +} + +.input:focus { + border-color: #6366F1; + outline: none; + box-shadow: 0 0 0 3px #6366F120; +} +``` + +### Modals + +```css +.modal-overlay { + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); +} + +.modal { + background: white; + border-radius: 16px; + padding: 32px; + box-shadow: var(--shadow-xl); + max-width: 500px; + width: 90%; +} +``` + +--- + +## Style Guidelines + +**Style:** Vibrant & Block-based + +**Keywords:** Bold, energetic, playful, block layout, geometric shapes, high color contrast, duotone, modern, energetic + +**Best For:** Startups, creative agencies, gaming, social media, youth-focused, entertainment, consumer + +**Key Effects:** Large sections (48px+ gaps), animated patterns, bold hover (color shift), scroll-snap, large type (32px+), 200-300ms + +### Page Pattern + +**Pattern Name:** Portfolio Grid + +- **Conversion Strategy:** hover overlay info, lightbox view, Visuals first. Filter by category. Fast loading essential. +- **CTA Placement:** Project Card Hover + Footer Contact +- **Section Order:** 1. Hero (Name/Role), 2. Project Grid (Masonry), 3. About/Philosophy, 4. Contact + +--- + +## Anti-Patterns (Do NOT Use) + +- ❌ No preview +- ❌ Slow delivery + +### Additional Forbidden Patterns + +- ❌ **Emojis as icons** — Use SVG icons (Heroicons, Lucide, Simple Icons) +- ❌ **Missing cursor:pointer** — All clickable elements must have cursor:pointer +- ❌ **Layout-shifting hovers** — Avoid scale transforms that shift layout +- ❌ **Low contrast text** — Maintain 4.5:1 minimum contrast ratio +- ❌ **Instant state changes** — Always use transitions (150-300ms) +- ❌ **Invisible focus states** — Focus states must be visible for a11y + +--- + +## Pre-Delivery Checklist + +Before delivering any UI code, verify: + +- [ ] No emojis used as icons (use SVG instead) +- [ ] All icons from consistent icon set (Heroicons/Lucide) +- [ ] `cursor-pointer` on all clickable elements +- [ ] Hover states with smooth transitions (150-300ms) +- [ ] Light mode: text contrast 4.5:1 minimum +- [ ] Focus states visible for keyboard navigation +- [ ] `prefers-reduced-motion` respected +- [ ] Responsive: 375px, 768px, 1024px, 1440px +- [ ] No content hidden behind fixed navbars +- [ ] No horizontal scroll on mobile diff --git a/design-system/年糕崽崽/pages/mine.md b/design-system/年糕崽崽/pages/mine.md new file mode 100644 index 0000000..2ee5f68 --- /dev/null +++ b/design-system/年糕崽崽/pages/mine.md @@ -0,0 +1,52 @@ +# Mine Page Overrides + +> **PROJECT:** 年糕崽崽 +> **Generated:** 2026-07-27 14:56:05 +> **Page Type:** Blog / Article + +> ⚠️ **IMPORTANT:** Rules in this file **override** the Master file (`design-system/MASTER.md`). +> Only deviations from the Master are documented here. For all other rules, refer to the Master. + +--- + +## Page-Specific Rules + +### Layout Overrides + +- **Max Width:** 1200px (standard) +- **Layout:** Full-width sections, centered content +- **Sections:** 1. Hero (Name/Role), 2. Project Grid (Masonry), 3. About/Philosophy, 4. Contact + +### Spacing Overrides + +- No overrides — use Master spacing + +### Typography Overrides + +- No overrides — use Master typography + +### Color Overrides + +- **Strategy:** Neutral background (let work shine). Text: Black/White. Accent: Minimal. + +### Component Overrides + +- Avoid: Default keyboard for all inputs +- Avoid: Desktop-first causing mobile issues +- Avoid: Enable by default everywhere + +--- + +## Page-Specific Components + +- No unique components for this page + +--- + +## Recommendations + +- Effects: Press deformation (scale + squish), bounce-back (cubic-bezier), material response, haptic-like feedback, spring physics +- Forms: Use inputmode attribute +- Responsive: Start with mobile styles then add breakpoints +- Touch: Disable where not needed +- CTA Placement: Project Card Hover + Footer Contact diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..ee14c69 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + '@parcel/watcher': set this to true or false + core-js: set this to true or false + esbuild: set this to true or false + vue-demi: set this to true or false diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..7c59d59 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,411 @@ +import { get, post, put, del, patch, uploadFile } from '@/utils/request' + +export interface User { + id: number + username: string + email: string + avatar?: string + bio?: string + role: string + isActive?: number + phone?: string + wechat?: string + wechatQrcode?: string + [key: string]: unknown +} + +export interface LoginResponse { + token: string + user: User + expire: number +} + +export interface PaginationResponse { + list: T[] + total: number + page: number + pageSize: number +} + +export interface Post { + id: number | string + title: string + content?: string + summary?: string + excerpt?: string + cover?: string + date?: string + categoryId?: number + categoryName?: string + columnId?: number + columnName?: string + userName?: string + userAvatar?: string + userId?: number + readCount?: number + isPublished?: number | boolean + tags?: { id: number, name: string }[] + [key: string]: unknown +} + +export interface WorkLinks { + live?: string + demo?: string + github?: string + [key: string]: unknown +} + +export interface WorkTechGroup { + category?: string + items?: string[] + [key: string]: unknown +} + +export interface Work { + id: string + title: string + category?: string + year?: string + heroImg?: string + heroVideo?: string + /** API 字段名为 desc */ + desc?: string + description?: string + videoUrl?: string + videoId?: number | string + techStack?: WorkTechGroup[] + gallery?: string[] + links?: WorkLinks | string + next?: string + [key: string]: unknown +} + +export interface Column { + id: number | string + title?: string + name?: string + description?: string + cover?: string + postCount?: number + isActive?: number | boolean + sortOrder?: number + [key: string]: unknown +} + +export interface Snippet { + id: number | string + title: string + code?: string + language?: string + description?: string + codeTypeId?: number + postIds?: number[] + [key: string]: unknown +} + +export interface VideoItem { + id: string + title: string + cover?: string + poster?: string + videoUrl?: string + url?: string + description?: string + categoryId?: number + categoryName?: string + albumIds?: number[] + isPublished?: number | boolean + duration?: number + createdAt?: string + [key: string]: unknown +} + +export interface VideoAlbumNavContext { + albumId: number + albumName: string + prevVideos: VideoItem[] + nextVideos: VideoItem[] +} + +export interface VideoDetailItem extends VideoItem { + albumContext?: VideoAlbumNavContext +} + +export interface VideoAlbum { + id: number + name: string + description?: string + cover?: string + categoryId?: number + categoryName?: string + videoCount?: number + videoIds?: number[] + isActive?: number | boolean + sortOrder?: number + latestVideoUpdatedAt?: string + previewVideos?: VideoItem[] + [key: string]: unknown +} + +export interface AboutExperience { + year?: string + role?: string + company?: string +} + +export interface AboutProfile { + id?: number + name?: string + avatar?: string + location?: string + bio?: string + email?: string + wechat?: string + isPrimary?: number | boolean + techStack?: string[] + experiences?: AboutExperience[] + role?: string + [key: string]: unknown +} + +export interface Attachment { + id: number + originalName?: string + fileName?: string + fileUrl?: string + url?: string + fileType?: string + fileSize?: number + categoryId?: number + [key: string]: unknown +} + +export interface Inquiry { + id: number + name?: string + company?: string + contactMethod?: string + contactValue?: string + budget?: string + description?: string + status?: number + createdAt?: string + [key: string]: unknown +} + +// ---- Auth ---- +export const loginApi = (username: string, password: string) => + post('/admin/login', { username, password }) + +export const wxLoginApi = (code: string) => + post('/auth/wx-login', { code }) + +/** 游客合并到管理员:需携带游客 JWT */ +export const mergeAccountApi = (username: string, password: string) => + post('/auth/merge-account', { username, password }, true) + +export const getCurrentUser = () => get('/admin/me', undefined, true) + +/** 更新当前登录用户资料 */ +export const updateMe = (data: Partial) => put('/admin/me', data, true) + +export const updatePassword = (oldPassword: string, newPassword: string) => + put('/admin/me/password', { oldPassword, newPassword }, true) + +// ---- Public ---- +export const getPublicSettings = () => get>('/settings') + +export const getPosts = (params: Record = {}) => + get | Post[]>('/posts', params) + +export const getPost = (id: string | number) => get(`/posts/${id}`) + +export const getRecommendedPosts = (id: string | number, limit = 3) => + get(`/posts/${id}/recommendations`, { limit }) + +export const getHotSearches = (limit = 10) => get<{ keyword: string, count: number }[]>('/search/hot', { limit }) + +export const getColumns = () => get('/columns') +export const getColumn = (id: string | number) => get(`/columns/${id}`) +export const getColumnPosts = (id: string | number) => get(`/columns/${id}/posts`) + +export const getWorks = () => get('/works') +export const getWork = (id: string) => get(`/works/${id}`) + +export const getSnippets = () => get('/snippets') +export const getSnippet = (id: string | number) => get(`/snippets/${id}`) + +export const getVideoCategories = () => get('/video-categories') +export const getVideoAlbums = (params: Record = {}) => + get('/video-albums', params) +export const getVideoAlbum = (id: string | number) => get(`/video-albums/${id}`) +export const getVideos = (params: Record = {}) => get('/videos', params) +export const getVideo = (id: string) => get(`/videos/${id}`) +export const getAlbumVideos = (id: string | number) => get(`/video-albums/${id}/videos`) + +export const getAbout = () => get('/about') +export const getTestimonials = () => get('/testimonials') +export const getPartners = () => get('/partners') +export const getEmailSuffixes = () => get('/email-suffixes') +export const submitInquiry = (data: Record) => post('/inquiries', data) + +export const getCategories = () => get<{ id: number, name: string }[]>('/categories') +export const getTags = () => get<{ id: number, name: string }[]>('/tags') + +/** 仪表盘趋势点(后端 date + value;兼容旧 count 字段) */ +export interface TrendPoint { + date: string + value?: number + count?: number + yoy?: number + mom?: number +} + +/** 读取趋势点数值:优先 value,兼容 SQL/旧接口的 count */ +export function trendPointValue(p?: TrendPoint | null): number { + if (!p) return 0 + return Number(p.value ?? p.count ?? 0) +} + +/** 热门文章排行项:后端访问统计返回 count,兼容文章实体上的 readCount */ +export interface DashboardTopPost { + title?: string + article_id?: number + count?: number + readCount?: number + [key: string]: unknown +} + +/** 概览结构化统计,对应 GetDashboardStats */ +export interface DashboardStats { + posts?: number + works?: number + inquiryCount?: number + postsTrend?: TrendPoint[] + uvTrend?: TrendPoint[] + topPosts?: DashboardTopPost[] + operationTrend?: TrendPoint[] + userRegions?: { region?: string, name?: string, count?: number, value?: number }[] +} + +// ---- Admin dashboard ---- +export const getDashboardStats = (params: Record = {}) => + get('/admin/dashboard/stats', params, true) +export const getRecentActivities = () => get('/admin/dashboard/activities', undefined, true) +export const getOperationLogs = (page = 1, pageSize = 10, filters: Record = {}) => + get>('/admin/operation-logs', { page, pageSize, ...filters }, true) +export const getAccessLogs = (page = 1, pageSize = 10, filters: Record = {}) => + get>('/admin/access-logs', { page, pageSize, ...filters }, true) + +// ---- Admin posts extras ---- +/** 切换文章发布状态 */ +export const patchPostStatus = (id: string | number, isPublished: number) => + patch(`/admin/posts/${id}/status`, { isPublished }, true) + +export const patchPostRelations = (id: string | number, data: Record) => + patch(`/admin/posts/${id}/relations`, data, true) + +export const getPostHistory = (id: string | number) => + get(`/admin/posts/${id}/history`, undefined, true) + +/** 后台文章详情(含未发布),编辑表单专用;勿用公开 getPost */ +export const adminGetPost = (id: string | number) => + get(`/admin/posts/${id}`, undefined, true) + +export const restorePostVersion = (id: string | number, version: string | number) => + post(`/admin/posts/${id}/history/${version}/restore`, {}, true) + +export const getPostAccessLogs = (id: string | number, params: Record = {}) => + get>(`/admin/posts/${id}/access-logs`, params, true) + +// ---- Admin inquiries ---- +export const updateInquiryStatus = (id: string | number, status: number) => + put(`/admin/inquiries/${id}/status`, { status }, true) + +// ---- Admin columns posts ---- +export const addPostToColumn = (columnId: string | number, postId: string | number) => + post(`/admin/columns/${columnId}/posts`, { postId }, true) + +export const removePostFromColumn = (columnId: string | number, postId: string | number) => + del(`/admin/columns/${columnId}/posts/${postId}`, true) + +export const getAdminColumnPosts = (columnId: string | number) => + get(`/admin/columns/${columnId}/posts`, undefined, true).catch(() => getColumnPosts(columnId)) + +// ---- Admin video albums ---- +export const getAlbumVideoIds = (id: string | number) => + get(`/admin/video-albums/${id}/video-ids`, undefined, true) + +// ---- Admin attachments ---- +export const uploadAttachment = (filePath: string, formData: Record = {}) => + uploadFile('/admin/attachments/upload', filePath, formData) + +// ---- Admin settings ---- +export const getAdminSettings = () => get('/admin/settings', undefined, true) +export const batchUpdateSettings = (values: Record) => + put('/admin/settings', values, true) + +// ---- Admin OSS ---- +export const testOssConfig = (id: string | number) => + post<{ storageType: string, name: string }>(`/admin/oss-configs/${id}/test`, {}, true) + +// ---- Admin generic CRUD helpers ---- +const RESOURCE_ALIAS: Record = { + logs: 'operation-logs', + 'access-logs': 'access-logs', + about: 'about', + settings: 'settings', +} + +function adminPath(resource: string, id?: string | number) { + const name = RESOURCE_ALIAS[resource] || resource + return id != null ? `/admin/${name}/${id}` : `/admin/${name}` +} + +export function adminList(resource: string, params: Record = {}) { + return get(adminPath(resource), params, true) +} + +export function adminGet(resource: string, id: string | number) { + return get(adminPath(resource, id), undefined, true) +} + +export function adminCreate(resource: string, data: unknown) { + return post(adminPath(resource), data, true) +} + +export function adminUpdate(resource: string, id: string | number, data: unknown) { + return put(adminPath(resource, id), data, true) +} + +export function adminDelete(resource: string, id: string | number) { + return del(adminPath(resource, id), true) +} + +/** 统一把列表接口结果规范成数组(兼容 list 包装与裸数组) */ +export function normalizeAdminList>(data: unknown): T[] { + if (Array.isArray(data)) return data as T[] + if (data && typeof data === 'object' && Array.isArray((data as { list?: unknown[] }).list)) { + return (data as { list: T[] }).list + } + return [] +} + +/** 统一分页结构 */ +export function normalizePagination>( + data: unknown, + page = 1, + pageSize = 20, +): PaginationResponse { + if (data && typeof data === 'object' && Array.isArray((data as PaginationResponse).list)) { + const p = data as PaginationResponse + return { + list: p.list, + total: Number(p.total ?? p.list.length), + page: Number(p.page ?? page), + pageSize: Number(p.pageSize ?? pageSize), + } + } + const list = normalizeAdminList(data) + return { list, total: list.length, page, pageSize } +} diff --git a/src/assets/fonts/jetbrains-mono-latin-400-normal.woff b/src/assets/fonts/jetbrains-mono-latin-400-normal.woff new file mode 100644 index 0000000000000000000000000000000000000000..41fa3d6c55d0571084b84a942fe4d43cf78a72ef GIT binary patch literal 27496 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 SPVD^p?;o%K18)r%=>P!EI0OX% literal 0 HcmV?d00001 diff --git a/src/assets/fonts/playfair-display-latin-400-italic.woff b/src/assets/fonts/playfair-display-latin-400-italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..c5f92cb1ee4ec4fb92db40033f2362efae6f8ece GIT binary patch literal 27268 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 E0Gj%0k^lez literal 0 HcmV?d00001 diff --git a/src/assets/fonts/playfair-display-latin-600-normal.woff b/src/assets/fonts/playfair-display-latin-600-normal.woff new file mode 100644 index 0000000000000000000000000000000000000000..61aebd25b828c730547ebae5a594deaf009154aa GIT binary patch literal 28356 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%0D0R=BpOfwoW>!kJyiB1S@o{xg>!sH1#u8tC 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 ge5;9O^T`E4+ItC7>(04&-8kpKVy literal 0 HcmV?d00001 diff --git a/src/assets/fonts/playfair-display-latin-700-normal.woff b/src/assets/fonts/playfair-display-latin-700-normal.woff new file mode 100644 index 0000000000000000000000000000000000000000..44f1c6723933f2e6fae8cdad64296b85b626ef30 GIT binary patch literal 28296 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 W2$4S^Cf4(U$?7~)7$` +/** + * C 端氛围层:暗色星空 + 偶发流星;浅色金色光斑漂移。 + * 粒子数约为 PC StarBackground 的 1/3,挂在 AppPageShell 底层。 + * 使用旧版 canvas 上下文以保证各端兼容;不可点击(pointer-events: none)。 + */ +import { getCurrentInstance, nextTick, onBeforeUnmount, onMounted, watch } from 'vue' +import { onHide, onShow } from '@dcloudio/uni-app' +import { useTheme } from '@/composables/useTheme' + +const { isDark } = useTheme() +const canvasId = `ambient-${Math.random().toString(36).slice(2, 9)}` + +interface Star { + x: number + y: number + r: number + a: number + out: boolean + sp: number +} +interface Meteor { + x: number + y: number + len: number + sp: number + a: number +} +interface Orb { + bx: number + by: number + r: number + ampX: number + ampY: number + phX: number + phY: number + sp: number + color: string +} +interface Dust { + x: number + y: number + r: number + a: number + out: boolean + sp: number + base: number +} + +let ctx: UniApp.CanvasContext | null = null +let width = 0 +let height = 0 +let running = false +let timer: ReturnType | null = null +let time = 0 +const stars: Star[] = [] +const meteors: Meteor[] = [] +const orbs: Orb[] = [] +const dust: Dust[] = [] + +const clearPools = () => { + stars.length = 0 + meteors.length = 0 + orbs.length = 0 + dust.length = 0 +} + +const initDark = () => { + clearPools() + for (let i = 0; i < 55; i++) { + stars.push({ + x: Math.random() * width, + y: Math.random() * height, + r: Math.random() * 1.4 + 0.3, + a: Math.random(), + out: Math.random() > 0.5, + sp: Math.random() * 0.02 + 0.005, + }) + } +} + +const initLight = () => { + clearPools() + const palette = [ + 'rgba(212,179,131,0.18)', + 'rgba(196,150,90,0.14)', + 'rgba(232,200,150,0.16)', + ] + const places = [ + { bx: 0.18, by: 0.22 }, + { bx: 0.8, by: 0.38 }, + { bx: 0.42, by: 0.78 }, + ] + places.forEach((p, i) => { + orbs.push({ + bx: p.bx * width, + by: p.by * height, + r: Math.min(width, height) * (0.22 + Math.random() * 0.1), + ampX: 28 + Math.random() * 40, + ampY: 22 + Math.random() * 36, + phX: Math.random() * Math.PI * 2, + phY: Math.random() * Math.PI * 2, + sp: 0.0003 + Math.random() * 0.0002, + color: palette[i % palette.length], + }) + }) + for (let i = 0; i < 28; i++) { + const base = Math.random() * 0.3 + 0.12 + dust.push({ + x: Math.random() * width, + y: Math.random() * height, + r: Math.random() * 1.6 + 0.4, + a: base, + out: Math.random() > 0.5, + sp: Math.random() * 0.008 + 0.003, + base, + }) + } +} + +const initTheme = () => { + if (isDark.value) initDark() + else initLight() +} + +const draw = () => { + if (!ctx || !running) return + time += 1 + ctx.clearRect(0, 0, width, height) + + if (isDark.value) { + stars.forEach((s) => { + if (s.out) { + s.a -= s.sp + if (s.a <= 0.1) s.out = false + } + else { + s.a += s.sp + if (s.a >= 1) s.out = true + } + ctx!.beginPath() + ctx!.arc(s.x, s.y, s.r, 0, Math.PI * 2) + ctx!.setFillStyle(`rgba(255,255,255,${Math.max(0.08, s.a)})`) + ctx!.fill() + }) + if (Math.random() < 0.008) { + meteors.push({ + x: Math.random() * width, + y: 0, + len: Math.random() * 60 + 24, + sp: Math.random() * 8 + 5, + a: 1, + }) + } + for (let i = meteors.length - 1; i >= 0; i--) { + const m = meteors[i] + const ex = m.x - m.len * 0.7 + const ey = m.y - m.len * 0.7 + ctx.beginPath() + ctx.moveTo(m.x, m.y) + ctx.lineTo(ex, ey) + ctx.setStrokeStyle(`rgba(255,255,255,${m.a})`) + ctx.setLineWidth(1.2) + ctx.stroke() + m.x += m.sp * 0.7 + m.y += m.sp * 0.7 + m.a -= 0.012 + if (m.a <= 0 || m.y > height) meteors.splice(i, 1) + } + } + else { + orbs.forEach((o) => { + const x = o.bx + Math.sin(time * o.sp + o.phX) * o.ampX + const y = o.by + Math.cos(time * o.sp + o.phY) * o.ampY + const g = ctx!.createCircularGradient(x, y, o.r) + g.addColorStop(0, o.color) + g.addColorStop(1, 'rgba(212,179,131,0)') + ctx!.beginPath() + ctx!.arc(x, y, o.r, 0, Math.PI * 2) + ctx!.setFillStyle(g) + ctx!.fill() + }) + dust.forEach((d) => { + if (d.out) { + d.a -= d.sp + if (d.a <= 0.05) d.out = false + } + else { + d.a += d.sp + if (d.a >= d.base) d.out = true + } + ctx!.beginPath() + ctx!.arc(d.x, d.y, d.r, 0, Math.PI * 2) + ctx!.setFillStyle(`rgba(196,150,90,${d.a})`) + ctx!.fill() + }) + } + + ctx.draw() + timer = setTimeout(() => { + if (running) draw() + }, 48) +} + +const stop = () => { + running = false + if (timer) { + clearTimeout(timer) + timer = null + } +} + +const start = () => { + stop() + running = true + draw() +} + +const setup = () => { + const inst = getCurrentInstance() + uni.getSystemInfo({ + success: (info) => { + width = info.windowWidth + height = info.windowHeight + nextTick(() => { + ctx = uni.createCanvasContext(canvasId, inst?.proxy as never) + initTheme() + start() + }) + }, + }) +} + +onMounted(setup) +onShow(() => { + if (ctx) start() +}) +onHide(stop) +onBeforeUnmount(stop) + +watch(isDark, () => { + initTheme() +}) + + + + + diff --git a/src/components/c-end/ArtMenuGroup.vue b/src/components/c-end/ArtMenuGroup.vue new file mode 100644 index 0000000..5367cfd --- /dev/null +++ b/src/components/c-end/ArtMenuGroup.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/c-end/ArtMenuItem.vue b/src/components/c-end/ArtMenuItem.vue new file mode 100644 index 0000000..13e9922 --- /dev/null +++ b/src/components/c-end/ArtMenuItem.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/c-end/ArtSearchBar.vue b/src/components/c-end/ArtSearchBar.vue new file mode 100644 index 0000000..2c4378c --- /dev/null +++ b/src/components/c-end/ArtSearchBar.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/components/c-end/PostCard.vue b/src/components/c-end/PostCard.vue new file mode 100644 index 0000000..1f11de1 --- /dev/null +++ b/src/components/c-end/PostCard.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/components/c-end/ProfileHero.vue b/src/components/c-end/ProfileHero.vue new file mode 100644 index 0000000..9180f64 --- /dev/null +++ b/src/components/c-end/ProfileHero.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/components/layout/AppFloatingTabbar.vue b/src/components/layout/AppFloatingTabbar.vue new file mode 100644 index 0000000..7a85df9 --- /dev/null +++ b/src/components/layout/AppFloatingTabbar.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/components/layout/AppNavbar.vue b/src/components/layout/AppNavbar.vue new file mode 100644 index 0000000..a73b332 --- /dev/null +++ b/src/components/layout/AppNavbar.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/components/layout/AppPageShell.vue b/src/components/layout/AppPageShell.vue new file mode 100644 index 0000000..200226d --- /dev/null +++ b/src/components/layout/AppPageShell.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/composables/useAppMode.ts b/src/composables/useAppMode.ts new file mode 100644 index 0000000..4fd203c --- /dev/null +++ b/src/composables/useAppMode.ts @@ -0,0 +1,75 @@ +import { computed, ref } from 'vue' +import type { AppMode, LoginType } from '@/config/menus' +import { isStaffRole } from '@/config/menus' +import { useAuth } from './useAuth' + +const MODE_KEY = 'appMode' +const LOGIN_TYPE_KEY = 'loginType' + +const appMode = ref((uni.getStorageSync(MODE_KEY) as AppMode) || 'user') +const loginType = ref((uni.getStorageSync(LOGIN_TYPE_KEY) as LoginType) || 'guest') + +export function useAppMode() { + const { getUser, isAuthenticated } = useAuth() + + const isAdminMode = computed(() => appMode.value === 'admin') + const isUserMode = computed(() => appMode.value === 'user') + + const canEnterAdmin = computed(() => { + if (!isAuthenticated()) return false + const user = getUser() as { role?: string } + return isStaffRole(user?.role) + }) + + const setLoginType = (type: LoginType) => { + loginType.value = type + uni.setStorageSync(LOGIN_TYPE_KEY, type) + } + + const setAppMode = (mode: AppMode) => { + appMode.value = mode + uni.setStorageSync(MODE_KEY, mode) + } + + const enterAdminMode = () => { + if (!canEnterAdmin.value) { + uni.showToast({ title: '无后台权限', icon: 'none' }) + return false + } + setAppMode('admin') + // 延迟 import,避免与 useTheme 循环依赖;切换模式后立刻同步页底色 + import('./useTheme').then(({ useTheme }) => useTheme().apply()).catch(() => {}) + uni.reLaunch({ url: '/subPackages/admin/pages/dashboard/index' }) + return true + } + + const enterUserMode = () => { + setAppMode('user') + import('./useTheme').then(({ useTheme }) => useTheme().apply()).catch(() => {}) + uni.reLaunch({ url: '/pages/home/index' }) + } + + /** 启动时校验:admin 模式但无权限则回退 */ + const hydrateAppMode = () => { + if (appMode.value === 'admin' && !canEnterAdmin.value) { + setAppMode('user') + if (!isAuthenticated()) setLoginType('guest') + } + if (isAuthenticated() && canEnterAdmin.value) { + setLoginType('staff') + } + } + + return { + appMode, + loginType, + isAdminMode, + isUserMode, + canEnterAdmin, + setLoginType, + setAppMode, + enterAdminMode, + enterUserMode, + hydrateAppMode, + } +} diff --git a/src/composables/useAppTabbar.ts b/src/composables/useAppTabbar.ts new file mode 100644 index 0000000..c8dd524 --- /dev/null +++ b/src/composables/useAppTabbar.ts @@ -0,0 +1,101 @@ +import { computed } from 'vue' +import { useAppMode } from './useAppMode' +import { useTheme } from './useTheme' + +export interface TabItem { + name: string + title: string + icon: string + path: string +} + +const USER_TABS: TabItem[] = [ + { name: 'home', title: '首页', icon: 'home', path: '/pages/home/index' }, + // wot-ui 无 app,正确图标名为 apps + { name: 'explore', title: '功能入口', icon: 'apps', path: '/pages/explore/index' }, + { name: 'mine', title: '我的', icon: 'user', path: '/pages/mine/index' }, +] + +const ADMIN_TABS: TabItem[] = [ + { name: 'dashboard', title: '工作台', icon: 'dashboard', path: '/subPackages/admin/pages/dashboard/index' }, + { name: 'explore', title: '功能入口', icon: 'apps', path: '/subPackages/admin/pages/explore/index' }, + { name: 'mine', title: '我的', icon: 'user', path: '/pages/mine/index' }, +] + +export function useAppTabbar() { + const { appMode } = useAppMode() + const { apply } = useTheme() + + const tabItems = computed(() => (appMode.value === 'admin' ? ADMIN_TABS : USER_TABS)) + + const resolveActive = (routePath?: string): string => { + const path = routePath || getCurrentRoutePath() + const items = tabItems.value + const hit = items.find(t => path.includes(t.path.replace(/^\//, '')) || path.endsWith(t.name)) + if (hit) return hit.name + if (path.includes('/mine')) return 'mine' + if (path.includes('/explore')) return 'explore' + if (path.includes('/dashboard') || path.includes('/home')) return items[0].name + return items[0].name + } + + /** + * Tab 切换:先同步底色再 redirectTo,避免 reLaunch 清栈 + 浅色 page 闪屏。 + * 已在当前页则 no-op。 + */ + const navigateTab = (name: string) => { + const item = tabItems.value.find(t => t.name === name) + if (!item) return + const cur = getCurrentRoutePath() + const target = item.path + if (cur === target || cur.endsWith(target.replace(/^\//, ''))) return + apply() + uni.redirectTo({ url: target }) + } + + const shouldShowTabbar = (routePath?: string): boolean => { + const path = routePath || getCurrentRoutePath() + const hidePatterns = [ + '/blog/detail', + '/blog/ppt', + '/columns/detail', + '/works/detail', + '/videos/detail', + '/videos/album', + '/resource/form', + '/resource/list', + '/analytics', + '/columns/form', + '/posts/', + '/works/list', + '/works/form', + '/videos/list', + '/videos/form', + '/video-albums/', + '/snippets/form', + '/attachments/', + '/inquiries/', + '/about/index', + '/settings/', + '/oss-configs/', + '/users/', + '/logs/', + ] + return !hidePatterns.some(p => path.includes(p)) + } + + return { + tabItems, + resolveActive, + navigateTab, + shouldShowTabbar, + USER_TABS, + ADMIN_TABS, + } +} + +function getCurrentRoutePath(): string { + const pages = getCurrentPages() + const cur = pages[pages.length - 1] as { route?: string } | undefined + return cur?.route ? `/${cur.route}` : '' +} diff --git a/src/composables/useAuth.ts b/src/composables/useAuth.ts new file mode 100644 index 0000000..fcc5f66 --- /dev/null +++ b/src/composables/useAuth.ts @@ -0,0 +1,117 @@ +import { ref } from 'vue' +import { isStaffRole } from '@/config/menus' + +const TOKEN_KEY = 'token' +const USER_KEY = 'user' +const EXPIRE_KEY = 'tokenExpireAt' + +const sessionExpired = ref(false) +let sessionExpiredHandled = false +let expiryTimer: ReturnType | null = null + +export interface AuthUser { + id: number + username: string + email?: string + avatar?: string + role?: string + [key: string]: unknown +} + +export function useAuth() { + const getToken = () => uni.getStorageSync(TOKEN_KEY) as string || '' + + const getTokenExpireAt = (): number => { + const raw = uni.getStorageSync(EXPIRE_KEY) + return raw ? Number(raw) : 0 + } + + const isAuthenticated = (): boolean => { + const token = getToken() + if (!token) return false + const expireAt = getTokenExpireAt() + if (expireAt > 0 && Date.now() >= expireAt) return false + return true + } + + const getUser = (): AuthUser => { + try { + const raw = uni.getStorageSync(USER_KEY) + if (!raw) return {} as AuthUser + return typeof raw === 'string' ? JSON.parse(raw) : raw + } + catch { + return {} as AuthUser + } + } + + const setUser = (user: AuthUser) => { + uni.setStorageSync(USER_KEY, JSON.stringify(user)) + } + + const clearExpiryTimer = () => { + if (expiryTimer) { + clearTimeout(expiryTimer) + expiryTimer = null + } + } + + const handleSessionExpired = () => { + if (sessionExpiredHandled) return + sessionExpiredHandled = true + sessionExpired.value = true + } + + const scheduleExpiryCheck = () => { + clearExpiryTimer() + const expireAt = getTokenExpireAt() + if (!expireAt) return + const delay = expireAt - Date.now() + if (delay <= 0) { + handleSessionExpired() + return + } + expiryTimer = setTimeout(() => handleSessionExpired(), delay) + } + + const login = (token: string, user: AuthUser, expireUnixSeconds: number) => { + uni.setStorageSync(TOKEN_KEY, token) + uni.setStorageSync(USER_KEY, JSON.stringify(user)) + uni.setStorageSync(EXPIRE_KEY, String(expireUnixSeconds * 1000)) + sessionExpiredHandled = false + sessionExpired.value = false + scheduleExpiryCheck() + + // sync loginType + uni.setStorageSync('loginType', isStaffRole(user.role) ? 'staff' : 'guest') + } + + const logout = () => { + clearExpiryTimer() + uni.removeStorageSync(TOKEN_KEY) + uni.removeStorageSync(USER_KEY) + uni.removeStorageSync(EXPIRE_KEY) + uni.setStorageSync('loginType', 'guest') + uni.setStorageSync('appMode', 'user') + sessionExpiredHandled = false + sessionExpired.value = false + } + + const confirmSessionExpired = () => { + logout() + uni.reLaunch({ url: '/pages/mine/index' }) + } + + return { + sessionExpired, + getToken, + getUser, + setUser, + isAuthenticated, + login, + logout, + handleSessionExpired, + confirmSessionExpired, + scheduleExpiryCheck, + } +} diff --git a/src/composables/usePublicSettings.ts b/src/composables/usePublicSettings.ts new file mode 100644 index 0000000..af2689c --- /dev/null +++ b/src/composables/usePublicSettings.ts @@ -0,0 +1,53 @@ +import { ref } from 'vue' +import { get } from '@/utils/request' +import { PUBLIC_MENUS } from '@/config/menus' + +export type PublicSettings = Record + +const cache = ref({}) +const loaded = ref(false) + +export function usePublicSettings() { + const load = async (force = false) => { + if (loaded.value && !force) return cache.value + try { + const data = await get('/settings') + cache.value = data || {} + loaded.value = true + } + catch { + cache.value = {} + } + return cache.value + } + + const getStringSetting = (key: string, fallback = '') => cache.value[key] || fallback + + const getVisibleMenus = (): string[] => { + const raw = getStringSetting('visible_menus') + const defaults = ['home', 'blog', 'columns', 'works', 'videos', 'snippets', 'about', 'services'] + if (!raw) return defaults + try { + const parsed = JSON.parse(raw) + if (Array.isArray(parsed)) return parsed + } + catch { + return raw.split(',').map(s => s.trim()).filter(Boolean) + } + return defaults + } + + const getExploreMenus = () => { + const visible = getVisibleMenus() + return PUBLIC_MENUS.filter(m => visible.includes(m.key)) + } + + return { + cache, + loaded, + load, + getStringSetting, + getVisibleMenus, + getExploreMenus, + } +} diff --git a/src/composables/useSearchHistory.ts b/src/composables/useSearchHistory.ts new file mode 100644 index 0000000..4198097 --- /dev/null +++ b/src/composables/useSearchHistory.ts @@ -0,0 +1,50 @@ +/** + * 思考页搜索历史:对齐 PC blog_search_history,最多 10 条。 + * 小程序用 uni.setStorageSync,与 localStorage key 保持一致便于跨端对齐。 + */ +export const SEARCH_HISTORY_KEY = 'blog_search_history' +const MAX_HISTORY = 10 + +export function useSearchHistory() { + /** 读取历史列表 */ + const readHistory = (): string[] => { + try { + const raw = uni.getStorageSync(SEARCH_HISTORY_KEY) + if (!raw) return [] + const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw + return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string') : [] + } + catch { + return [] + } + } + + const writeHistory = (items: string[]) => { + uni.setStorageSync(SEARCH_HISTORY_KEY, JSON.stringify(items.slice(0, MAX_HISTORY))) + } + + const getHistory = () => readHistory() + + /** 写入关键词(去重置顶) */ + const addHistory = (keyword: string) => { + const trimmed = keyword.trim() + if (!trimmed) return + const next = [trimmed, ...readHistory().filter(item => item !== trimmed)].slice(0, MAX_HISTORY) + writeHistory(next) + } + + const removeHistory = (keyword: string) => { + writeHistory(readHistory().filter(item => item !== keyword)) + } + + const clearHistory = () => { + uni.removeStorageSync(SEARCH_HISTORY_KEY) + } + + return { + getHistory, + addHistory, + removeHistory, + clearHistory, + } +} diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..bfc20cc --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,145 @@ +/** + * 主题与 wot ConfigProvider 变量。 + * 后台模式强制按暗色注入,避免用户浅色偏好导致表单字色/底色错乱。 + */ +import { computed, ref } from 'vue' +import { useAppMode } from './useAppMode' + +export type ThemePref = 'system' | 'light' | 'dark' + +const THEME_KEY = 'theme-pref' +const themePref = ref((uni.getStorageSync(THEME_KEY) as ThemePref) || 'system') +const isDark = ref(false) + +function resolveDark(pref: ThemePref): boolean { + if (pref === 'dark') return true + if (pref === 'light') return false + try { + // 优先新 API,避免 wx.getSystemInfoSync 弃用警告 + const base = typeof uni.getAppBaseInfo === 'function' ? uni.getAppBaseInfo() : null + if (base && 'theme' in base) { + return (base as { theme?: string }).theme === 'dark' + } + const info = uni.getSystemInfoSync() + return (info as UniApp.GetSystemInfoResult & { theme?: string }).theme === 'dark' + } + catch { + return false + } +} + +/** + * 同步窗口底色到 art 主题色。 + * theme.json 的 @bgColor 只跟系统深色;App 内选手动深色时必须靠此 API,避免 page 露白。 + */ +function syncPageBackground(dark: boolean, admin = false) { + const bg = admin ? '#0a0a0a' : dark ? '#050505' : '#faf8f4' + try { + uni.setBackgroundColor({ + backgroundColor: bg, + backgroundColorTop: bg, + backgroundColorBottom: bg, + }) + } + catch { /* H5 may ignore */ } + try { + uni.setNavigationBarColor({ + frontColor: dark || admin ? '#ffffff' : '#000000', + backgroundColor: bg, + }) + } + catch { /* custom nav may ignore */ } + uni.setStorageSync('theme-bg-sync', `${dark || admin ? 1 : 0}-${Date.now()}`) +} + +export function useTheme() { + const { appMode } = useAppMode() + + /** 用户偏好暗色,或当前处于后台模式 */ + const effectiveDark = computed(() => isDark.value || appMode.value === 'admin') + + const apply = () => { + isDark.value = resolveDark(themePref.value) + syncPageBackground(isDark.value, appMode.value === 'admin') + } + + const setThemePref = (pref: ThemePref) => { + themePref.value = pref + uni.setStorageSync(THEME_KEY, pref) + apply() + } + + const themeLabel = computed(() => { + if (themePref.value === 'system') return '跟随系统' + if (themePref.value === 'light') return '浅色' + return '深色' + }) + + const wotTheme = computed(() => (effectiveDark.value ? 'dark' : 'light')) + + /** 注入 wot ConfigProvider:主题色 + 导航/弹层/表单/搜索深色,避免白底割裂 */ + const themeVars = computed(() => { + const dark = effectiveDark.value + const admin = appMode.value === 'admin' + const bg = dark ? '#121214' : '#ffffff' + const pageBg = admin ? '#0a0a0a' : dark ? '#050505' : '#faf8f4' + const title = dark ? '#ececec' : '#171717' + const muted = dark ? '#888888' : '#6b6b6b' + const border = dark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)' + return { + colorTheme: '#d4b383', + navbarColor: title, + navbarBg: admin + ? 'rgba(10,10,10,0.92)' + : dark + ? 'rgba(5,5,5,0.92)' + : 'rgba(250,248,244,0.92)', + navbarDescColor: title, + colorBg: bg, + colorTitle: title, + colorContent: title, + colorSecondary: muted, + darkColor: bg, + darkBackground: pageBg, + tabsNavBg: 'transparent', + tabsNavColor: muted, + tabsNavActiveColor: title, + tabsNavLineColor: '#d4b383', + tabsNavBorder: border, + cellBg: bg, + cellTitleColor: title, + cellValueColor: muted, + cellLabelColor: muted, + cellBorderColor: border, + collapseBg: bg, + collapseBodyBg: bg, + collapseHeaderBg: bg, + collapseTitleColor: title, + /* 后台表单需要可见底色,不能跟卡片同色透明 */ + inputBg: dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.03)', + inputInnerColor: title, + inputInnerPlaceholderColor: muted, + inputBorderColor: border, + textareaBg: dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.03)', + textareaInnerColor: title, + textareaInnerPlaceholderColor: muted, + searchInputBg: dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)', + searchInputColor: title, + searchPlaceholderColor: muted, + searchIconColor: muted, + } + }) + + apply() + + return { + themePref, + isDark, + effectiveDark, + themeLabel, + wotTheme, + themeVars, + setThemePref, + apply, + } +} diff --git a/src/config/menus.ts b/src/config/menus.ts new file mode 100644 index 0000000..995e8b2 --- /dev/null +++ b/src/config/menus.ts @@ -0,0 +1,107 @@ +export type AppMode = 'user' | 'admin' +export type LoginType = 'guest' | 'staff' + +export interface MenuItem { + key: string + title: string + path: string + icon?: string + children?: MenuItem[] +} + +/** 前台频道(功能入口)——路径指向 content 分包(单层,勿重复前缀) */ +export const PUBLIC_MENUS: MenuItem[] = [ + { key: 'blog', title: '思考', path: '/subPackages/content/pages/blog/index', icon: 'edit' }, + { key: 'columns', title: '专栏', path: '/subPackages/content/pages/columns/index', icon: 'book' }, + { key: 'works', title: '作品', path: '/subPackages/content/pages/works/index', icon: 'image' }, + { key: 'videos', title: '视频', path: '/subPackages/content/pages/videos/index', icon: 'video-camera' }, + { key: 'snippets', title: '代码', path: '/subPackages/content/pages/snippets/index', icon: 'code' }, + { key: 'about', title: '关于', path: '/subPackages/content/pages/about/index', icon: 'user' }, + { key: 'services', title: '合作', path: '/subPackages/content/pages/services/index', icon: 'headset' }, +] + +/** 后台菜单树(功能入口);icon 须为 @wot-ui v2 有效名 */ +export const ADMIN_MENUS: MenuItem[] = [ + { key: 'dashboard', title: '概览', path: '/subPackages/admin/pages/dashboard/index', icon: 'dashboard' }, + { key: 'analytics', title: '数据分析', path: '/subPackages/admin/pages/analytics/index', icon: 'mind-mapping' }, + { + key: 'content', + title: '内容管理', + path: '', + icon: 'book', + children: [ + { key: 'posts', title: '文章管理', path: '/subPackages/admin/pages/posts/list', icon: 'edit' }, + { key: 'categories', title: '分类管理', path: '/subPackages/admin/pages/resource/list?resource=categories', icon: 'list' }, + { key: 'columns', title: '专栏管理', path: '/subPackages/admin/pages/resource/list?resource=columns', icon: 'book' }, + { key: 'works', title: '作品管理', path: '/subPackages/admin/pages/works/list', icon: 'image' }, + { key: 'video-categories', title: '视频分类', path: '/subPackages/admin/pages/resource/list?resource=video-categories', icon: 'list' }, + { key: 'video-albums', title: '视频专辑', path: '/subPackages/admin/pages/resource/list?resource=video-albums', icon: 'folder' }, + { key: 'videos', title: '视频列表', path: '/subPackages/admin/pages/videos/list', icon: 'video-camera' }, + { key: 'snippets', title: '代码片段', path: '/subPackages/admin/pages/resource/list?resource=snippets', icon: 'code' }, + { key: 'code-types', title: '代码分类', path: '/subPackages/admin/pages/resource/list?resource=code-types', icon: 'code-block' }, + { key: 'tags', title: '标签管理', path: '/subPackages/admin/pages/resource/list?resource=tags', icon: 'apps' }, + ], + }, + { + key: 'pages', + title: '页面配置', + path: '', + icon: 'application', + children: [ + { key: 'about', title: '关于页面', path: '/subPackages/admin/pages/about/index', icon: 'user' }, + { key: 'testimonials', title: '客户评价', path: '/subPackages/admin/pages/resource/list?resource=testimonials', icon: 'message' }, + { key: 'partners', title: '合作伙伴', path: '/subPackages/admin/pages/resource/list?resource=partners', icon: 'user-group' }, + ], + }, + { + key: 'business', + title: '业务中心', + path: '', + icon: 'email', + children: [ + { key: 'inquiries', title: '合作咨询', path: '/subPackages/admin/pages/inquiries/list', icon: 'email' }, + { key: 'email-suffixes', title: '邮箱配置', path: '/subPackages/admin/pages/resource/list?resource=email-suffixes', icon: 'settings' }, + ], + }, + { + key: 'users', + title: '用户权限', + path: '', + icon: 'user-group', + children: [ + { key: 'users', title: '用户管理', path: '/subPackages/admin/pages/users/list', icon: 'user' }, + { key: 'roles', title: '角色管理', path: '/subPackages/admin/pages/resource/list?resource=roles', icon: 'lock' }, + ], + }, + { + key: 'attachments', + title: '附件管理', + path: '', + icon: 'folder', + children: [ + { key: 'attachments', title: '附件库', path: '/subPackages/admin/pages/attachments/index', icon: 'file' }, + { key: 'attachment-categories', title: '附件分类', path: '/subPackages/admin/pages/resource/list?resource=attachment-categories', icon: 'folder-add' }, + { key: 'oss-configs', title: 'OSS配置', path: '/subPackages/admin/pages/oss-configs/index', icon: 'cloud' }, + ], + }, + { + key: 'system', + title: '系统设置', + path: '', + icon: 'settings', + children: [ + { key: 'settings', title: '全局配置', path: '/subPackages/admin/pages/settings/index', icon: 'settings' }, + { key: 'ppt-templates', title: 'PPT 模板', path: '/subPackages/admin/pages/resource/list?resource=ppt-templates', icon: 'file' }, + { key: 'logs', title: '操作日志', path: '/subPackages/admin/pages/logs/operation', icon: 'list' }, + { key: 'access-logs', title: '访问日志', path: '/subPackages/admin/pages/logs/access', icon: 'eye' }, + ], + }, +] + +export const STAFF_ROLES = ['admin', 'editor'] as const + +export function isStaffRole(role?: string): boolean { + if (!role) return false + const r = role.toLowerCase() + return STAFF_ROLES.includes(r as (typeof STAFF_ROLES)[number]) || r.includes('admin') || r.includes('editor') +} diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..c454ab5 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_BASE: string + readonly VITE_UPLOAD_BASE: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/src/pages/explore/index.vue b/src/pages/explore/index.vue new file mode 100644 index 0000000..323ee4c --- /dev/null +++ b/src/pages/explore/index.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/pages/home/index.vue b/src/pages/home/index.vue new file mode 100644 index 0000000..47c7d3a --- /dev/null +++ b/src/pages/home/index.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/pages/mine/index.vue b/src/pages/mine/index.vue new file mode 100644 index 0000000..195a280 --- /dev/null +++ b/src/pages/mine/index.vue @@ -0,0 +1,263 @@ + + + diff --git a/src/resolvers/wot-ui-resolver.ts b/src/resolvers/wot-ui-resolver.ts new file mode 100644 index 0000000..085421e --- /dev/null +++ b/src/resolvers/wot-ui-resolver.ts @@ -0,0 +1,17 @@ +import type { ComponentResolver } from '@uni-helper/vite-plugin-uni-components' +import { kebabCase } from '@uni-helper/vite-plugin-uni-components' + +export function WotResolver(): ComponentResolver { + return { + type: 'component', + resolve: (name: string) => { + if (name.match(/^Wd[A-Z]/)) { + const compName = kebabCase(name) + return { + name, + from: `@wot-ui/ui/components/${compName}/${compName}.vue`, + } + } + }, + } +} diff --git a/src/static/default-avatar.svg b/src/static/default-avatar.svg new file mode 100644 index 0000000..21bb93e --- /dev/null +++ b/src/static/default-avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/styles/art-fonts.scss b/src/styles/art-fonts.scss new file mode 100644 index 0000000..8576fd7 --- /dev/null +++ b/src/styles/art-fonts.scss @@ -0,0 +1,48 @@ +/** + * 从 PC 端 @fontsource 引入的本地字体(woff)。 + * 放在 assets(非 static):小程序 CSS 会把 <40KB 资源打成 base64, + * 避免再拷一份到 static 造成双倍体积。 + * + * 与 PC 差异:Noto Sans SC 单 weight 约 1.5MB,主包装不下,故未引入; + * art-tokens 仍写 Noto 名,实际中文正文回退 PingFang SC / 系统黑体。 + * 标题/eyebrow 用 Playfair + JetBrains,与 PC 英文层对齐。 + */ + +/* Playfair Display — 标题 / serif italic */ +@font-face { + font-family: 'Playfair Display'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('@/assets/fonts/playfair-display-latin-400-normal.woff') format('woff'); +} +@font-face { + font-family: 'Playfair Display'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('@/assets/fonts/playfair-display-latin-400-italic.woff') format('woff'); +} +@font-face { + font-family: 'Playfair Display'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('@/assets/fonts/playfair-display-latin-600-normal.woff') format('woff'); +} +@font-face { + font-family: 'Playfair Display'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('@/assets/fonts/playfair-display-latin-700-normal.woff') format('woff'); +} + +/* JetBrains Mono — eyebrow / mono label */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('@/assets/fonts/jetbrains-mono-latin-400-normal.woff') format('woff'); +} diff --git a/src/styles/art-tokens.scss b/src/styles/art-tokens.scss new file mode 100644 index 0000000..f1a84d7 --- /dev/null +++ b/src/styles/art-tokens.scss @@ -0,0 +1,322 @@ +/* Art design tokens — ported from client/src/style.css */ + +/* 小程序里 min-height:100% 常失效,用 100vh 铺满视口;底色走 --art-bg,避免硬编码浅色闪屏 */ +page { + min-height: 100vh; + background-color: rgb(var(--art-bg)); +} + +page, +.page-root { + --art-bg: 250, 248, 244; + --art-surface: 255, 255, 255; + --art-border: rgba(0, 0, 0, 0.08); + --art-text: 23, 23, 23; + --art-muted: 107, 107, 107; + --art-accent: 212, 179, 131; + --art-error: 239, 68, 68; + --art-admin-bg: 10, 10, 10; + --art-admin-card: 18, 18, 20; + --art-admin-hover: 26, 26, 28; + --art-nav-bg: rgba(250, 248, 244, 0.9); + --art-card-bg: rgba(255, 255, 255, 0.55); + --art-card-border: rgba(0, 0, 0, 0.07); + --art-hover-border: rgba(212, 179, 131, 0.55); + --art-shadow: 0 18px 40px -12px rgba(80, 55, 20, 0.14); + --art-overlay: rgba(0, 0, 0, 0.45); + background-color: rgb(var(--art-bg)); + color: rgb(var(--art-text)); + font-family: 'Noto Sans SC', 'PingFang SC', 'Helvetica Neue', sans-serif; + min-height: 100vh; + box-sizing: border-box; +} + +.page-root.theme-dark, +.theme-dark { + --art-bg: 5, 5, 5; + --art-surface: 18, 18, 20; + --art-border: rgba(255, 255, 255, 0.08); + --art-text: 236, 236, 236; + --art-muted: 136, 136, 136; + --art-accent: 212, 179, 131; + --art-nav-bg: rgba(5, 5, 5, 0.85); + --art-card-bg: rgba(255, 255, 255, 0.02); + --art-card-border: rgba(255, 255, 255, 0.06); + --art-hover-border: rgba(212, 179, 131, 0.3); + --art-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5); + --art-overlay: rgba(0, 0, 0, 0.9); + background-color: rgb(var(--art-bg)); + color: rgb(var(--art-text)); +} + +.page-root.admin-mode, +.admin-mode { + --art-bg: 10, 10, 10; + --art-surface: 18, 18, 20; + --art-text: 236, 236, 236; + --art-muted: 136, 136, 136; + background-color: rgb(var(--art-admin-bg)); +} + +.glass-nav { + background: var(--art-nav-bg); + backdrop-filter: blur(20px); + border-bottom: 1px solid var(--art-card-border); +} + +.art-card { + background: var(--art-card-bg); + border: 1px solid var(--art-card-border); + border-radius: 16rpx; + overflow: hidden; + position: relative; + transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.35s ease, box-shadow 0.35s ease; +} + +.art-card::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, transparent 0%, rgba(212, 179, 131, 0.08) 100%); + opacity: 0; + transition: opacity 0.35s ease; + pointer-events: none; + z-index: 0; +} + +.art-card-press:active { + transform: translateY(-8rpx); + border-color: var(--art-hover-border); + box-shadow: var(--art-shadow); +} + +.art-card-press:active::before { + opacity: 1; +} + +.admin-card { + background: rgb(var(--art-admin-card)); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 16rpx; + transition: background 0.25s ease, border-color 0.25s ease; +} + +.admin-card:active { + background: rgb(var(--art-admin-hover)); + border-color: rgba(212, 179, 131, 0.25); +} + +.text-art-accent { color: rgb(var(--art-accent)); } +.text-art-muted { color: rgb(var(--art-muted)); } +.text-art-text { color: rgb(var(--art-text)); } + +.font-serif-italic { + font-family: 'Playfair Display', 'Noto Serif SC', serif; + font-style: italic; +} + +.font-mono-label { + font-family: 'JetBrains Mono', 'Menlo', monospace; + letter-spacing: 0.12em; + text-transform: uppercase; + font-size: 22rpx; +} + +.page-pad { padding: 32rpx 32rpx 200rpx; } +.page-pad-tab { padding-bottom: 200rpx; } + +.section-title { + font-family: 'Playfair Display', 'Noto Serif SC', serif; + font-style: italic; + font-size: 48rpx; + color: rgb(var(--art-text)); +} + +.hero-eyebrow { + color: rgb(var(--art-accent)); + font-family: 'JetBrains Mono', monospace; + font-size: 22rpx; + letter-spacing: 0.2em; + text-transform: uppercase; + margin-bottom: 24rpx; +} + +@keyframes art-reveal { + from { opacity: 0; transform: translateY(24rpx); } + to { opacity: 1; transform: translateY(0); } +} + +.animate-reveal { + animation: art-reveal 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.animate-reveal-delay { + animation: art-reveal 0.55s cubic-bezier(0.16, 1, 0.3, 1) 0.12s both; +} + +.fab-float { + transition: transform 0.2s ease, opacity 0.2s ease; +} +.fab-float:active { + transform: scale(0.92); +} + +/* Article markdown (mp-html / rich content) */ +.article-md { + font-size: 30rpx; + line-height: 1.85; + color: rgb(var(--art-text)); + word-break: break-word; +} +.article-md h1, .article-md h2, .article-md h3 { + font-family: 'Playfair Display', 'Noto Serif SC', serif; + margin: 1.2em 0 0.5em; + line-height: 1.35; +} +.article-md h1 { font-size: 44rpx; } +.article-md h2 { font-size: 38rpx; } +.article-md h3 { font-size: 34rpx; } +.article-md p { margin: 0.75em 0; } +.article-md a { color: rgb(var(--art-accent)); text-decoration: underline; } +.article-md blockquote { + margin: 1em 0; + padding: 0.5em 1em; + border-left: 6rpx solid rgb(var(--art-accent)); + color: rgb(var(--art-muted)); + background: rgba(212, 179, 131, 0.08); +} +.article-md ul, .article-md ol { padding-left: 1.4em; margin: 0.75em 0; } +.article-md img { max-width: 100%; border-radius: 12rpx; margin: 0.75em 0; } +.article-md code { + font-family: 'JetBrains Mono', Menlo, monospace; + font-size: 0.88em; + background: rgba(127, 127, 127, 0.12); + padding: 0.1em 0.35em; + border-radius: 6rpx; +} +.article-md pre { + background: #1e1e1e; + color: #e8e8e8; + padding: 24rpx; + border-radius: 16rpx; + overflow-x: auto; + margin: 1em 0; + font-size: 24rpx; + line-height: 1.6; +} +.article-md pre code { background: transparent; padding: 0; color: inherit; } +.article-md table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 26rpx; } +.article-md th, .article-md td { border: 1px solid var(--art-border); padding: 12rpx; } + +/* wot-ui 深浅色兜底:避免默认浅色组件压在 art 深色底上 */ +.page-root, +.theme-dark { + --wot-navbar-color: rgb(var(--art-text)); + --wot-navbar-desc-color: rgb(var(--art-text)); + --wot-navbar-bg: rgb(var(--art-bg)); +} + +/* C 端暗色:输入保持轻量透明,贴合卡片氛围 */ +.theme-dark:not(.admin-mode) .wd-input, +.page-root.theme-dark:not(.admin-mode) .wd-input { + --wot-input-bg: transparent; + --wot-color-bg: rgb(var(--art-surface)); + --wot-input-inner-color: rgb(var(--art-text)); + --wot-input-inner-placeholder-color: rgb(var(--art-muted)); + color: rgb(var(--art-text)); +} + +.theme-dark:not(.admin-mode) .wd-input__inner, +.page-root.theme-dark:not(.admin-mode) .wd-input__inner { + color: rgb(var(--art-text)) !important; +} + +.theme-dark:not(.admin-mode) .wd-textarea, +.page-root.theme-dark:not(.admin-mode) .wd-textarea { + --wot-textarea-bg: transparent; + --wot-textarea-inner-color: rgb(var(--art-text)); + --wot-textarea-inner-placeholder-color: rgb(var(--art-muted)); +} + +.theme-dark .wd-cell, +.page-root.theme-dark .wd-cell { + --wot-cell-bg: rgb(var(--art-surface)); + --wot-cell-title-color: rgb(var(--art-text)); + --wot-cell-value-color: rgb(var(--art-muted)); + --wot-cell-label-color: rgb(var(--art-muted)); +} + +/* 后台表单:可见底色 + 边框,避免与 admin-card 融成一片 */ +.admin-mode .admin-card.form-card, +.page-root.admin-mode .admin-card.form-card { + padding: 24rpx; +} + +.admin-mode .wd-input, +.page-root.admin-mode .wd-input, +.admin-mode .form-card .wd-input, +.page-root.admin-mode .form-card .wd-input { + --wot-input-bg: rgba(255, 255, 255, 0.06); + --wot-input-inner-color: rgb(var(--art-text)); + --wot-input-inner-placeholder-color: rgb(var(--art-muted)); + --wot-color-bg: rgba(255, 255, 255, 0.06); + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 12rpx; + margin-bottom: 16rpx; + padding: 4rpx 8rpx; + box-sizing: border-box; + color: rgb(var(--art-text)); +} + +.admin-mode .wd-input__inner, +.page-root.admin-mode .wd-input__inner { + color: rgb(var(--art-text)) !important; +} + +.admin-mode .wd-textarea, +.page-root.admin-mode .wd-textarea, +.admin-mode .form-card .wd-textarea, +.page-root.admin-mode .form-card .wd-textarea { + --wot-textarea-bg: rgba(255, 255, 255, 0.06); + --wot-textarea-inner-color: rgb(var(--art-text)); + --wot-textarea-inner-placeholder-color: rgb(var(--art-muted)); + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 12rpx; + margin-bottom: 16rpx; + padding: 8rpx; + box-sizing: border-box; + color: rgb(var(--art-text)); +} + +.admin-mode .admin-card { + border-color: rgba(255, 255, 255, 0.1); +} + +.admin-form-field { + display: flex; + flex-direction: column; + gap: 10rpx; + margin-bottom: 8rpx; +} + +.admin-form-field__label { + font-size: 24rpx; + color: rgb(var(--art-muted)); + letter-spacing: 0.04em; +} + +/* ActionSheet / Popup:暗色下避免默认白底 */ +.theme-dark, +.page-root.theme-dark { + --wot-action-sheet-bg: rgb(var(--art-surface)); + --wot-action-sheet-color: rgb(var(--art-text)); + --wot-action-sheet-cancel-color: rgb(var(--art-muted)); + --wot-popup-bg: rgb(var(--art-surface)); + --wot-color-bg: rgb(var(--art-surface)); + --wot-color-title: rgb(var(--art-text)); + --wot-color-content: rgb(var(--art-text)); + --wot-color-secondary: rgb(var(--art-muted)); + --wot-color-border: var(--art-border); +} diff --git a/src/subPackages/admin/components/AdminBarChart.vue b/src/subPackages/admin/components/AdminBarChart.vue new file mode 100644 index 0000000..15d2a6a --- /dev/null +++ b/src/subPackages/admin/components/AdminBarChart.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/subPackages/admin/components/AdminDetailDrawer.vue b/src/subPackages/admin/components/AdminDetailDrawer.vue new file mode 100644 index 0000000..97b5954 --- /dev/null +++ b/src/subPackages/admin/components/AdminDetailDrawer.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/subPackages/admin/components/AdminFilterBar.vue b/src/subPackages/admin/components/AdminFilterBar.vue new file mode 100644 index 0000000..67b5cd0 --- /dev/null +++ b/src/subPackages/admin/components/AdminFilterBar.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/subPackages/admin/components/AdminFormField.vue b/src/subPackages/admin/components/AdminFormField.vue new file mode 100644 index 0000000..b11e226 --- /dev/null +++ b/src/subPackages/admin/components/AdminFormField.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/subPackages/admin/components/AdminLineChart.vue b/src/subPackages/admin/components/AdminLineChart.vue new file mode 100644 index 0000000..d764400 --- /dev/null +++ b/src/subPackages/admin/components/AdminLineChart.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/src/subPackages/admin/components/AdminMediaPicker.vue b/src/subPackages/admin/components/AdminMediaPicker.vue new file mode 100644 index 0000000..db8ee12 --- /dev/null +++ b/src/subPackages/admin/components/AdminMediaPicker.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/subPackages/admin/components/AdminStatusBadge.vue b/src/subPackages/admin/components/AdminStatusBadge.vue new file mode 100644 index 0000000..76959ca --- /dev/null +++ b/src/subPackages/admin/components/AdminStatusBadge.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/subPackages/admin/composables/useAdminGuard.ts b/src/subPackages/admin/composables/useAdminGuard.ts new file mode 100644 index 0000000..3cf06e8 --- /dev/null +++ b/src/subPackages/admin/composables/useAdminGuard.ts @@ -0,0 +1,20 @@ +/** + * 后台页鉴权守卫:未登录或无员工角色则回我的页。 + */ +import { useAuth } from '@/composables/useAuth' +import { useAppMode } from '@/composables/useAppMode' + +export function useAdminGuard() { + const { isAuthenticated } = useAuth() + const { canEnterAdmin } = useAppMode() + + function guardAdmin(): boolean { + if (!isAuthenticated() || !canEnterAdmin.value) { + uni.reLaunch({ url: '/pages/mine/index' }) + return false + } + return true + } + + return { guardAdmin, isAuthenticated, canEnterAdmin } +} diff --git a/src/subPackages/admin/config/resourceSchemas.ts b/src/subPackages/admin/config/resourceSchemas.ts new file mode 100644 index 0000000..50a9b2f --- /dev/null +++ b/src/subPackages/admin/config/resourceSchemas.ts @@ -0,0 +1,380 @@ +/** + * 简单资源 Schema 注册表:驱动通用 list/form,消灭默认 4 字段 + JSON 主路径。 + * 复杂模块(posts/works/videos/attachments/settings/about/inquiries)走专用页,不在此注册。 + */ + +export type FieldType = + | 'text' + | 'textarea' + | 'number' + | 'switch' + | 'select' + | 'media' + | 'rating' + +export interface SchemaField { + key: string + label: string + type: FieldType + placeholder?: string + /** select 选项 */ + options?: { label: string, value: string | number }[] + /** 只读(如系统模板 slug) */ + readonly?: boolean + /** 编辑态才显示 */ + editOnly?: boolean +} + +export interface SchemaFilter { + key: string + label: string + type: 'text' | 'select' + options?: { label: string, value: string | number }[] +} + +export interface ResourceSchema { + title: string + /** 真只读:无新建/删除/编辑,点行开详情抽屉 */ + readonly?: boolean + /** 禁止编辑(可新建删除),如邮箱后缀 */ + noEdit?: boolean + /** 页内新建(邮箱后缀) */ + inlineCreate?: boolean + /** 服务端分页 */ + serverPaging?: boolean + /** 默认 pageSize */ + pageSize?: number + /** 列表主标题字段候选 */ + titleKeys?: string[] + /** 列表副标题字段候选 */ + subtitleKeys?: string[] + /** 封面/头像字段 */ + coverKey?: string + /** 状态字段(用于徽章) */ + statusKey?: string + /** 状态文案映射 */ + statusMap?: Record + filters?: SchemaFilter[] + fields: SchemaField[] + /** 本地关键词过滤字段 */ + localSearchKeys?: string[] +} + +const CODE_TYPE_OPTIONS = [ + { label: '前端', value: 0 }, + { label: '后端', value: 1 }, + { label: '其他', value: 2 }, +] + +/** 简单资源 Schema;未列出的走兜底 */ +export const RESOURCE_SCHEMAS: Record = { + categories: { + title: '分类管理', + titleKeys: ['name'], + subtitleKeys: ['slug', 'sortOrder'], + localSearchKeys: ['name', 'slug'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'slug', label: 'Slug', type: 'text' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + columns: { + title: '专栏管理', + titleKeys: ['name', 'title'], + subtitleKeys: ['description', 'sortOrder'], + coverKey: 'cover', + statusKey: 'isActive', + statusMap: { '1': '启用', '0': '停用', true: '启用', false: '停用' }, + filters: [ + { key: 'keyword', label: '关键词', type: 'text' }, + { + key: 'isActive', + label: '状态', + type: 'select', + options: [ + { label: '全部', value: '' }, + { label: '启用', value: 1 }, + { label: '停用', value: 0 }, + ], + }, + ], + localSearchKeys: ['name', 'title', 'description'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'cover', label: '封面', type: 'media' }, + { key: 'isActive', label: '启用', type: 'switch' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + 'video-categories': { + title: '视频分类', + titleKeys: ['name'], + subtitleKeys: ['slug', 'sortOrder'], + localSearchKeys: ['name', 'slug'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'slug', label: 'Slug', type: 'text' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + 'video-albums': { + title: '视频专辑', + titleKeys: ['name'], + subtitleKeys: ['categoryName', 'videoCount'], + coverKey: 'cover', + statusKey: 'isActive', + statusMap: { '1': '启用', '0': '停用', true: '启用', false: '停用' }, + localSearchKeys: ['name', 'description'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'categoryId', label: '分类ID', type: 'number' }, + { key: 'cover', label: '封面', type: 'media' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'isActive', label: '启用', type: 'switch' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + snippets: { + title: '代码片段', + titleKeys: ['title'], + subtitleKeys: ['description'], + localSearchKeys: ['title', 'description'], + fields: [ + { key: 'title', label: '标题', type: 'text' }, + { key: 'codeTypeId', label: '分类ID', type: 'number' }, + { key: 'code', label: '代码', type: 'textarea' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + 'code-types': { + title: '代码分类', + titleKeys: ['name'], + subtitleKeys: ['category'], + localSearchKeys: ['name'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'category', label: '类别', type: 'select', options: CODE_TYPE_OPTIONS }, + ], + }, + tags: { + title: '标签管理', + titleKeys: ['name'], + subtitleKeys: ['slug'], + localSearchKeys: ['name', 'slug'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'slug', label: 'Slug', type: 'text' }, + ], + }, + testimonials: { + title: '客户评价', + titleKeys: ['name'], + subtitleKeys: ['role', 'rating'], + coverKey: 'avatar', + filters: [ + { key: 'keyword', label: '关键词', type: 'text' }, + { + key: 'rating', + label: '评分', + type: 'select', + options: [ + { label: '全部', value: '' }, + { label: '5星', value: 5 }, + { label: '4星', value: 4 }, + { label: '3星', value: 3 }, + ], + }, + ], + localSearchKeys: ['name', 'role', 'content'], + fields: [ + { key: 'name', label: '姓名', type: 'text' }, + { key: 'role', label: '职位', type: 'text' }, + { key: 'avatar', label: '头像', type: 'media' }, + { key: 'rating', label: '评分', type: 'rating' }, + { key: 'content', label: '评价内容', type: 'textarea' }, + ], + }, + partners: { + title: '合作伙伴', + titleKeys: ['name'], + subtitleKeys: ['url', 'description'], + coverKey: 'logo', + localSearchKeys: ['name', 'description', 'url'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'logo', label: 'Logo', type: 'media' }, + { key: 'url', label: '链接', type: 'text' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + 'email-suffixes': { + title: '邮箱配置', + noEdit: true, + inlineCreate: true, + titleKeys: ['suffix'], + subtitleKeys: ['sortOrder', 'isActive'], + statusKey: 'isActive', + statusMap: { '1': '启用', '0': '停用', true: '启用', false: '停用' }, + localSearchKeys: ['suffix'], + fields: [ + { key: 'suffix', label: '后缀', type: 'text', placeholder: '@example.com' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'isActive', label: '启用', type: 'switch' }, + ], + }, + users: { + title: '用户管理', + serverPaging: true, + pageSize: 10, + titleKeys: ['username'], + subtitleKeys: ['email', 'role'], + coverKey: 'avatar', + statusKey: 'isActive', + statusMap: { '1': '启用', '0': '停用', true: '启用', false: '停用' }, + filters: [ + { key: 'keyword', label: '关键词', type: 'text' }, + { + key: 'role', + label: '角色', + type: 'select', + options: [ + { label: '全部', value: '' }, + { label: 'admin', value: 'admin' }, + { label: 'editor', value: 'editor' }, + { label: 'viewer', value: 'viewer' }, + ], + }, + { + key: 'isActive', + label: '状态', + type: 'select', + options: [ + { label: '全部', value: '' }, + { label: '启用', value: 1 }, + { label: '停用', value: 0 }, + ], + }, + ], + fields: [ + { key: 'username', label: '用户名', type: 'text' }, + { key: 'email', label: '邮箱', type: 'text' }, + { key: 'avatar', label: '头像', type: 'media' }, + { + key: 'role', + label: '角色', + type: 'select', + options: [ + { label: 'admin', value: 'admin' }, + { label: 'editor', value: 'editor' }, + { label: 'viewer', value: 'viewer' }, + ], + }, + { key: 'isActive', label: '启用', type: 'switch' }, + { key: 'bio', label: '简介', type: 'textarea' }, + { key: 'phone', label: '手机', type: 'text' }, + { key: 'wechat', label: '微信', type: 'text' }, + { key: 'wechatQrcode', label: '微信二维码', type: 'media' }, + ], + }, + roles: { + title: '角色管理', + titleKeys: ['name'], + subtitleKeys: ['description'], + localSearchKeys: ['name', 'description'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + 'attachment-categories': { + title: '附件分类', + titleKeys: ['name'], + subtitleKeys: ['sortOrder', 'description'], + localSearchKeys: ['name', 'description'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + }, + 'ppt-templates': { + title: 'PPT 模板', + titleKeys: ['name'], + subtitleKeys: ['slug', 'sortOrder'], + statusKey: 'isActive', + statusMap: { '1': '启用', '0': '停用', true: '启用', false: '停用' }, + localSearchKeys: ['name', 'slug'], + fields: [ + { key: 'name', label: '名称', type: 'text' }, + { key: 'slug', label: 'Slug', type: 'text' }, + { key: 'description', label: '描述', type: 'textarea' }, + { key: 'isDefault', label: '默认', type: 'switch' }, + { key: 'isActive', label: '启用', type: 'switch' }, + { key: 'sortOrder', label: '排序', type: 'number' }, + { key: 'config', label: '配置 JSON', type: 'textarea' }, + ], + }, + logs: { + title: '操作日志', + readonly: true, + serverPaging: true, + pageSize: 20, + titleKeys: ['action', 'path'], + subtitleKeys: ['username', 'createdAt', 'method', 'status'], + filters: [ + { key: 'action', label: '操作', type: 'text' }, + { key: 'method', label: '方法', type: 'text' }, + { key: 'status', label: '状态码', type: 'text' }, + ], + fields: [], + }, + 'access-logs': { + title: '访问日志', + readonly: true, + serverPaging: true, + pageSize: 20, + titleKeys: ['path'], + subtitleKeys: ['region', 'ip', 'createdAt', 'statusCode'], + filters: [ + { key: 'path', label: '路径', type: 'text' }, + { key: 'region', label: '地区', type: 'text' }, + ], + fields: [], + }, + /** 已拆专用页,保留兜底标题 */ + posts: { title: '文章管理', fields: [], titleKeys: ['title'] }, + works: { title: '作品管理', fields: [], titleKeys: ['title'] }, + videos: { title: '视频列表', fields: [], titleKeys: ['title'] }, + attachments: { title: '附件库', readonly: true, fields: [], titleKeys: ['originalName', 'fileName'] }, + inquiries: { title: '合作咨询', readonly: true, fields: [], titleKeys: ['name'] }, + 'oss-configs': { title: 'OSS配置', fields: [], titleKeys: ['name'] }, + about: { title: '关于页面', fields: [] }, + settings: { title: '全局配置', fields: [] }, +} + +export function getResourceSchema(resource: string): ResourceSchema { + return RESOURCE_SCHEMAS[resource] || { + title: resource || '资源管理', + titleKeys: ['title', 'name', 'username'], + subtitleKeys: ['description', 'createdAt'], + fields: [ + { key: 'title', label: '标题', type: 'text' }, + { key: 'name', label: '名称', type: 'text' }, + { key: 'description', label: '描述', type: 'textarea' }, + ], + } +} + +/** 从记录中按候选 key 取展示文案 */ +export function pickField(item: Record, keys?: string[]): string { + if (!keys?.length) return '' + for (const k of keys) { + const v = item[k] + if (v != null && v !== '') return String(v) + } + return '' +} diff --git a/src/subPackages/admin/config/settingsSchema.ts b/src/subPackages/admin/config/settingsSchema.ts new file mode 100644 index 0000000..22e8f5b --- /dev/null +++ b/src/subPackages/admin/config/settingsSchema.ts @@ -0,0 +1,62 @@ +/** + * 全局配置 Schema(自 Web client settingsSchema 精简移植,供小程序设置页分 Tab 编辑) + */ + +export type SettingFieldType = 'text' | 'textarea' | 'number' | 'menu-checkboxes' | 'email' | 'homepage-json' + +export interface SettingSchemaItem { + key: string + label: string + type: SettingFieldType + group: 'site' | 'seo' | 'navigation' | 'homepage' | 'pagination' | 'contact' + default: string + description: string +} + +export const SETTING_GROUPS: { id: SettingSchemaItem['group'], label: string }[] = [ + { id: 'site', label: '站点信息' }, + { id: 'seo', label: 'SEO' }, + { id: 'navigation', label: '导航菜单' }, + { id: 'homepage', label: '首页内容' }, + { id: 'pagination', label: '分页设置' }, + { id: 'contact', label: '联系方式' }, +] + +export const MENU_OPTIONS = [ + { key: 'home', label: '首页' }, + { key: 'blog', label: '思考' }, + { key: 'columns', label: '专栏' }, + { key: 'works', label: '作品' }, + { key: 'videos', label: '视频' }, + { key: 'snippets', label: '代码' }, + { key: 'about', label: '关于' }, + { key: 'services', label: '合作' }, +] + +export const SETTINGS_SCHEMA: SettingSchemaItem[] = [ + { key: 'site_title', label: '网站标题', type: 'text', group: 'site', default: '年糕崽崽.Dev', description: '站点名称' }, + { key: 'site_author', label: '网站作者', type: 'text', group: 'site', default: '', description: '作者名' }, + { key: 'site_description', label: '网站描述', type: 'textarea', group: 'seo', default: '', description: 'SEO 描述' }, + { key: 'site_keywords', label: '网站关键词', type: 'textarea', group: 'seo', default: '', description: '逗号分隔' }, + { + key: 'visible_menus', + label: '前台菜单', + type: 'menu-checkboxes', + group: 'navigation', + default: '["home","blog","columns","works","videos","snippets","about","services"]', + description: '控制前台导航显示', + }, + { + key: 'homepage_config', + label: '首页配置 JSON', + type: 'homepage-json', + group: 'homepage', + default: '{}', + description: 'Hero / Bento 等首页结构,小程序端用 JSON 编辑', + }, + { key: 'posts_per_page', label: '文章每页', type: 'number', group: 'pagination', default: '10', description: '' }, + { key: 'works_per_page', label: '作品每页', type: 'number', group: 'pagination', default: '12', description: '' }, + { key: 'snippets_per_page', label: '代码每页', type: 'number', group: 'pagination', default: '12', description: '' }, + { key: 'footer_icp', label: 'ICP 备案号', type: 'text', group: 'contact', default: '', description: '' }, + { key: 'contact_email', label: '联系邮箱', type: 'email', group: 'contact', default: '', description: '' }, +] diff --git a/src/subPackages/admin/pages/about/index.vue b/src/subPackages/admin/pages/about/index.vue new file mode 100644 index 0000000..8396328 --- /dev/null +++ b/src/subPackages/admin/pages/about/index.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/src/subPackages/admin/pages/analytics/index.vue b/src/subPackages/admin/pages/analytics/index.vue new file mode 100644 index 0000000..8b776e9 --- /dev/null +++ b/src/subPackages/admin/pages/analytics/index.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/subPackages/admin/pages/attachments/index.vue b/src/subPackages/admin/pages/attachments/index.vue new file mode 100644 index 0000000..5a0ad1c --- /dev/null +++ b/src/subPackages/admin/pages/attachments/index.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/src/subPackages/admin/pages/columns/form.vue b/src/subPackages/admin/pages/columns/form.vue new file mode 100644 index 0000000..172f68a --- /dev/null +++ b/src/subPackages/admin/pages/columns/form.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/subPackages/admin/pages/dashboard/index.vue b/src/subPackages/admin/pages/dashboard/index.vue new file mode 100644 index 0000000..b0224ee --- /dev/null +++ b/src/subPackages/admin/pages/dashboard/index.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/src/subPackages/admin/pages/explore/index.vue b/src/subPackages/admin/pages/explore/index.vue new file mode 100644 index 0000000..4b46201 --- /dev/null +++ b/src/subPackages/admin/pages/explore/index.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/subPackages/admin/pages/inquiries/list.vue b/src/subPackages/admin/pages/inquiries/list.vue new file mode 100644 index 0000000..99cf8d4 --- /dev/null +++ b/src/subPackages/admin/pages/inquiries/list.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/subPackages/admin/pages/oss-configs/index.vue b/src/subPackages/admin/pages/oss-configs/index.vue new file mode 100644 index 0000000..3ce355d --- /dev/null +++ b/src/subPackages/admin/pages/oss-configs/index.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/src/subPackages/admin/pages/posts/form.vue b/src/subPackages/admin/pages/posts/form.vue new file mode 100644 index 0000000..72ed2e3 --- /dev/null +++ b/src/subPackages/admin/pages/posts/form.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/src/subPackages/admin/pages/posts/list.vue b/src/subPackages/admin/pages/posts/list.vue new file mode 100644 index 0000000..04bf50a --- /dev/null +++ b/src/subPackages/admin/pages/posts/list.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/subPackages/admin/pages/profile/index.vue b/src/subPackages/admin/pages/profile/index.vue new file mode 100644 index 0000000..6ac998f --- /dev/null +++ b/src/subPackages/admin/pages/profile/index.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/src/subPackages/admin/pages/resource/form.vue b/src/subPackages/admin/pages/resource/form.vue new file mode 100644 index 0000000..637a130 --- /dev/null +++ b/src/subPackages/admin/pages/resource/form.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/src/subPackages/admin/pages/resource/list.vue b/src/subPackages/admin/pages/resource/list.vue new file mode 100644 index 0000000..077aa7c --- /dev/null +++ b/src/subPackages/admin/pages/resource/list.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/src/subPackages/admin/pages/settings/index.vue b/src/subPackages/admin/pages/settings/index.vue new file mode 100644 index 0000000..9c5c581 --- /dev/null +++ b/src/subPackages/admin/pages/settings/index.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/src/subPackages/admin/pages/snippets/form.vue b/src/subPackages/admin/pages/snippets/form.vue new file mode 100644 index 0000000..fde1638 --- /dev/null +++ b/src/subPackages/admin/pages/snippets/form.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/src/subPackages/admin/pages/users/form.vue b/src/subPackages/admin/pages/users/form.vue new file mode 100644 index 0000000..ee4204d --- /dev/null +++ b/src/subPackages/admin/pages/users/form.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/subPackages/admin/pages/users/list.vue b/src/subPackages/admin/pages/users/list.vue new file mode 100644 index 0000000..73d8730 --- /dev/null +++ b/src/subPackages/admin/pages/users/list.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/subPackages/admin/pages/video-albums/form.vue b/src/subPackages/admin/pages/video-albums/form.vue new file mode 100644 index 0000000..9f88c2a --- /dev/null +++ b/src/subPackages/admin/pages/video-albums/form.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/subPackages/admin/pages/videos/form.vue b/src/subPackages/admin/pages/videos/form.vue new file mode 100644 index 0000000..f233a0d --- /dev/null +++ b/src/subPackages/admin/pages/videos/form.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/src/subPackages/admin/pages/videos/list.vue b/src/subPackages/admin/pages/videos/list.vue new file mode 100644 index 0000000..04ff7fd --- /dev/null +++ b/src/subPackages/admin/pages/videos/list.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/subPackages/admin/pages/works/form.vue b/src/subPackages/admin/pages/works/form.vue new file mode 100644 index 0000000..c039bde --- /dev/null +++ b/src/subPackages/admin/pages/works/form.vue @@ -0,0 +1,211 @@ + + + + + diff --git a/src/subPackages/admin/pages/works/list.vue b/src/subPackages/admin/pages/works/list.vue new file mode 100644 index 0000000..cee157e --- /dev/null +++ b/src/subPackages/admin/pages/works/list.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/subPackages/content/components/CodeBlock.vue b/src/subPackages/content/components/CodeBlock.vue new file mode 100644 index 0000000..6af6c8f --- /dev/null +++ b/src/subPackages/content/components/CodeBlock.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/subPackages/content/components/ContentPageHeader.vue b/src/subPackages/content/components/ContentPageHeader.vue new file mode 100644 index 0000000..6286217 --- /dev/null +++ b/src/subPackages/content/components/ContentPageHeader.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/subPackages/content/components/StaffLoginForm.vue b/src/subPackages/content/components/StaffLoginForm.vue new file mode 100644 index 0000000..b7ea03e --- /dev/null +++ b/src/subPackages/content/components/StaffLoginForm.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/subPackages/content/lib/codeHighlight.ts b/src/subPackages/content/lib/codeHighlight.ts new file mode 100644 index 0000000..02b4c7d --- /dev/null +++ b/src/subPackages/content/lib/codeHighlight.ts @@ -0,0 +1,24 @@ +/** + * 代码语言别名:对齐 PC client/src/utils/codeHighlight.ts + * 放在 content/lib,避免分包 utils 与主包 utils 路径冲突。 + */ +const LANGUAGE_ALIASES: Record = { + js: 'javascript', + ts: 'typescript', + py: 'python', + rb: 'ruby', + sh: 'bash', + yml: 'yaml', + md: 'markdown', + xml: 'xml', + svg: 'xml', + noise: 'xml', +} + +/** 把业务侧语言名解析成 highlight.js 注册名 */ +export function resolveHighlightLanguage(typeName?: string): string { + if (!typeName) return 'plaintext' + const key = typeName.trim().toLowerCase() + if (!key) return 'plaintext' + return LANGUAGE_ALIASES[key] || key +} diff --git a/src/subPackages/content/lib/markdownRenderer.ts b/src/subPackages/content/lib/markdownRenderer.ts new file mode 100644 index 0000000..565b2d9 --- /dev/null +++ b/src/subPackages/content/lib/markdownRenderer.ts @@ -0,0 +1,120 @@ +/** + * Markdown 渲染:marked + 树摇 highlight.js。 + * 放在 content/lib(勿用分包 utils,会与主包 utils 撞路径导致 ENOENT)。 + * 不做行号(对齐 PC 移动端)。 + */ +import { marked, type Tokens } from 'marked' +import type { HLJSApi, LanguageFn } from 'highlight.js' +import hljsCore from 'highlight.js/lib/core' +import javascript from 'highlight.js/lib/languages/javascript' +import typescript from 'highlight.js/lib/languages/typescript' +import python from 'highlight.js/lib/languages/python' +import go from 'highlight.js/lib/languages/go' +import java from 'highlight.js/lib/languages/java' +import css from 'highlight.js/lib/languages/css' +import xml from 'highlight.js/lib/languages/xml' +import json from 'highlight.js/lib/languages/json' +import bash from 'highlight.js/lib/languages/bash' +import sql from 'highlight.js/lib/languages/sql' +import yaml from 'highlight.js/lib/languages/yaml' +import markdown from 'highlight.js/lib/languages/markdown' +import { resolveHighlightLanguage } from './codeHighlight' +import { rewriteHtmlMediaUrls } from '@/utils/request' + +const LANGUAGE_REGISTRY: Array<[string, LanguageFn]> = [ + ['javascript', javascript], + ['typescript', typescript], + ['python', python], + ['go', go], + ['java', java], + ['css', css], + ['xml', xml], + ['html', xml], + ['json', json], + ['bash', bash], + ['sh', bash], + ['sql', sql], + ['yaml', yaml], + ['yml', yaml], + ['markdown', markdown], + ['md', markdown], +] + +let hljsInstance: HLJSApi | null = null +let markedReady = false + +function getHljs(): HLJSApi { + if (!hljsInstance) { + hljsInstance = hljsCore + for (const [name, register] of LANGUAGE_REGISTRY) { + hljsInstance.registerLanguage(name, register) + } + } + return hljsInstance +} + +/** 转义 HTML,避免未高亮代码注入 */ +function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +/** + * 单段代码 → 带 Mac 风格头栏的 HTML(供 mp-html) + */ +export function highlightCodeBlock(code: string, language?: string): string { + const hljs = getHljs() + const lang = resolveHighlightLanguage(language) + let highlighted: string + if (lang && lang !== 'plaintext' && hljs.getLanguage(lang)) { + try { + highlighted = hljs.highlight(code, { language: lang, ignoreIllegals: true }).value + } + catch { + highlighted = escapeHtml(code) + } + } + else { + highlighted = escapeHtml(code) + } + const label = (lang && lang !== 'plaintext' ? lang : 'TEXT').toUpperCase() + return ` +
+
+
+ +
+ ${label} +
+
${highlighted}
+
` +} + +function ensureMarked() { + if (markedReady) return + const renderer = new marked.Renderer() + renderer.code = ({ text, lang }: Tokens.Code) => highlightCodeBlock(text, lang || '') + marked.setOptions({ + gfm: true, + breaks: false, + }) + marked.use({ renderer }) + markedReady = true +} + +/** 渲染 Markdown 为 HTML(含代码高亮块);相对图片路径拼上传域名 */ +export async function renderMarkdown(content: string): Promise { + if (!content) return '' + getHljs() + ensureMarked() + const html = marked.parse(content) as string + return rewriteHtmlMediaUrls(html) +} + +/** 预加载 highlight(进入详情/弹层前可调用) */ +export function preloadMarkdownRenderer(): void { + getHljs() +} diff --git a/src/subPackages/content/pages/about/index.vue b/src/subPackages/content/pages/about/index.vue new file mode 100644 index 0000000..450cda8 --- /dev/null +++ b/src/subPackages/content/pages/about/index.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/src/subPackages/content/pages/blog/detail.vue b/src/subPackages/content/pages/blog/detail.vue new file mode 100644 index 0000000..96fdf94 --- /dev/null +++ b/src/subPackages/content/pages/blog/detail.vue @@ -0,0 +1,368 @@ + + + + + diff --git a/src/subPackages/content/pages/blog/index.vue b/src/subPackages/content/pages/blog/index.vue new file mode 100644 index 0000000..b3873c5 --- /dev/null +++ b/src/subPackages/content/pages/blog/index.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/src/subPackages/content/pages/blog/ppt.vue b/src/subPackages/content/pages/blog/ppt.vue new file mode 100644 index 0000000..e5dbad9 --- /dev/null +++ b/src/subPackages/content/pages/blog/ppt.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/subPackages/content/pages/columns/detail.vue b/src/subPackages/content/pages/columns/detail.vue new file mode 100644 index 0000000..c26e177 --- /dev/null +++ b/src/subPackages/content/pages/columns/detail.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/subPackages/content/pages/columns/index.vue b/src/subPackages/content/pages/columns/index.vue new file mode 100644 index 0000000..b24ded7 --- /dev/null +++ b/src/subPackages/content/pages/columns/index.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/subPackages/content/pages/login/index.vue b/src/subPackages/content/pages/login/index.vue new file mode 100644 index 0000000..232ef3f --- /dev/null +++ b/src/subPackages/content/pages/login/index.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/subPackages/content/pages/services/index.vue b/src/subPackages/content/pages/services/index.vue new file mode 100644 index 0000000..26a37d4 --- /dev/null +++ b/src/subPackages/content/pages/services/index.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/subPackages/content/pages/snippets/index.vue b/src/subPackages/content/pages/snippets/index.vue new file mode 100644 index 0000000..4e56e0a --- /dev/null +++ b/src/subPackages/content/pages/snippets/index.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/subPackages/content/pages/theme/index.vue b/src/subPackages/content/pages/theme/index.vue new file mode 100644 index 0000000..ea31e49 --- /dev/null +++ b/src/subPackages/content/pages/theme/index.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/src/subPackages/content/pages/videos/album.vue b/src/subPackages/content/pages/videos/album.vue new file mode 100644 index 0000000..53ffc46 --- /dev/null +++ b/src/subPackages/content/pages/videos/album.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/subPackages/content/pages/videos/detail.vue b/src/subPackages/content/pages/videos/detail.vue new file mode 100644 index 0000000..1780170 --- /dev/null +++ b/src/subPackages/content/pages/videos/detail.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/src/subPackages/content/pages/videos/index.vue b/src/subPackages/content/pages/videos/index.vue new file mode 100644 index 0000000..52fc10e --- /dev/null +++ b/src/subPackages/content/pages/videos/index.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/src/subPackages/content/pages/works/detail.vue b/src/subPackages/content/pages/works/detail.vue new file mode 100644 index 0000000..13b1208 --- /dev/null +++ b/src/subPackages/content/pages/works/detail.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/src/subPackages/content/pages/works/index.vue b/src/subPackages/content/pages/works/index.vue new file mode 100644 index 0000000..216d931 --- /dev/null +++ b/src/subPackages/content/pages/works/index.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/utils/content.ts b/src/utils/content.ts new file mode 100644 index 0000000..f4e13ea --- /dev/null +++ b/src/utils/content.ts @@ -0,0 +1,62 @@ +/** + * 去掉 HTML 标签,用于列表摘要展示。 + * 保留实体简单还原,避免简介全是标签噪音。 + */ +export function stripHtml(html?: string | null): string { + if (!html) return '' + return String(html) + .replace(/<[^>]+>/g, ' ') + .replace(/ /gi, ' ') + .replace(/&/gi, '&') + .replace(/</gi, '<') + .replace(/>/gi, '>') + .replace(/\s+/g, ' ') + .trim() +} + +/** + * 外链打开:H5 新开;小程序复制到剪贴板(无 web-view 页时的折中)。 + */ +export function openExternalUrl(url?: string) { + const u = String(url || '').trim() + if (!u) return + // #ifdef H5 + window.open(u, '_blank') + // #endif + // #ifndef H5 + uni.setClipboardData({ + data: u, + success: () => uni.showToast({ title: '链接已复制', icon: 'none' }), + }) + // #endif +} + +/** 解析作品 links(可能是 JSON 字符串) */ +export function parseWorkLinks(links: unknown): { live?: string, demo?: string, github?: string } { + if (!links) return {} + if (typeof links === 'string') { + try { + return JSON.parse(links) as { live?: string, demo?: string, github?: string } + } + catch { + return {} + } + } + if (typeof links === 'object') return links as { live?: string, demo?: string, github?: string } + return {} +} + +/** 展平技术栈分组为标签列表 */ +export function flattenTechStack(stack?: { category?: string, items?: string[], [k: string]: unknown }[]): string[] { + if (!Array.isArray(stack)) return [] + const out: string[] = [] + stack.forEach((g) => { + const items = Array.isArray(g.items) ? g.items : [] + items.forEach((i) => { + if (i) out.push(String(i)) + }) + // 兼容 { category, item } 扁平行 + if (!items.length && g.item) out.push(String(g.item)) + }) + return out +} diff --git a/src/utils/markdownRenderer.ts b/src/utils/markdownRenderer.ts new file mode 100644 index 0000000..35e32ee --- /dev/null +++ b/src/utils/markdownRenderer.ts @@ -0,0 +1,103 @@ +import { marked } from 'marked' +import type { HLJSApi, LanguageFn } from 'highlight.js' +import hljsCore from 'highlight.js/lib/core' +import javascript from 'highlight.js/lib/languages/javascript' +import typescript from 'highlight.js/lib/languages/typescript' +import python from 'highlight.js/lib/languages/python' +import go from 'highlight.js/lib/languages/go' +import java from 'highlight.js/lib/languages/java' +import css from 'highlight.js/lib/languages/css' +import xml from 'highlight.js/lib/languages/xml' +import json from 'highlight.js/lib/languages/json' +import bash from 'highlight.js/lib/languages/bash' +import sql from 'highlight.js/lib/languages/sql' +import yaml from 'highlight.js/lib/languages/yaml' +import markdown from 'highlight.js/lib/languages/markdown' +import rust from 'highlight.js/lib/languages/rust' +import cpp from 'highlight.js/lib/languages/cpp' +import c from 'highlight.js/lib/languages/c' +import php from 'highlight.js/lib/languages/php' +import shell from 'highlight.js/lib/languages/shell' + +const LANGUAGE_REGISTRY: Array<[string, LanguageFn]> = [ + ['javascript', javascript], + ['typescript', typescript], + ['python', python], + ['go', go], + ['java', java], + ['css', css], + ['xml', xml], + ['html', xml], + ['json', json], + ['bash', bash], + ['sh', bash], + ['sql', sql], + ['yaml', yaml], + ['yml', yaml], + ['markdown', markdown], + ['md', markdown], + ['rust', rust], + ['cpp', cpp], + ['c', c], + ['php', php], + ['shell', shell], +] + +let hljsInstance: HLJSApi | null = null +let configured = false + +function getHljs(): HLJSApi { + if (!hljsInstance) { + hljsInstance = hljsCore + for (const [name, register] of LANGUAGE_REGISTRY) { + hljsInstance.registerLanguage(name, register) + } + } + return hljsInstance +} + +function ensureMarked() { + if (configured) return + const hljs = getHljs() + marked.setOptions({ + gfm: true, + breaks: false, + }) + const renderer = new marked.Renderer() + renderer.code = ({ text, lang }: { text: string, lang?: string }) => { + const language = (lang || '').split(/\s+/)[0] + let highlighted = text + if (language && hljs.getLanguage(language)) { + try { + highlighted = hljs.highlight(text, { language, ignoreIllegals: true }).value + } + catch { + highlighted = hljs.highlightAuto(text).value + } + } + else { + highlighted = hljs.highlightAuto(text).value + } + return `
${highlighted}
\n` + } + renderer.heading = ({ text, depth }: { text: string, depth: number }) => { + const slug = `heading-${String(text).toLowerCase().replace(/[^\w\u4e00-\u9fa5]+/g, '-')}` + return `${text}\n` + } + renderer.image = ({ href, text }: { href: string, text: string }) => { + return `${text || ''}` + } + marked.use({ renderer }) + configured = true +} + +export async function renderMarkdown(content: string): Promise { + if (!content) return '' + ensureMarked() + return marked.parse(content) as string +} + +export function preloadMarkdownRenderer(): void { + getHljs() + ensureMarked() +} diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..b2188bf --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,184 @@ +import { useAuth } from '@/composables/useAuth' + +export interface ApiResponse { + code: number + message: string + result: T +} + +const API_BASE = import.meta.env.VITE_API_BASE || 'http://127.0.0.1:8081/api' +export const UPLOAD_BASE = import.meta.env.VITE_UPLOAD_BASE || 'http://127.0.0.1:8081' + +/** + * 媒体地址:已是完整链接 / data: 则原样返回;否则拼上传域名(后端 /uploads 等)。 + */ +export function getImageUrl(url?: string | null): string { + if (!url) return '' + const raw = String(url).trim() + if (!raw) return '' + if (/^https?:\/\//i.test(raw) || raw.startsWith('data:') || raw.startsWith('//')) return raw + if (raw.startsWith('/')) return `${UPLOAD_BASE}${raw}` + return `${UPLOAD_BASE}/${raw}` +} + +/** 兼容旧名,与 getImageUrl 同逻辑 */ +export const resolveMediaUrl = getImageUrl + +/** + * 重写 HTML 中相对路径的 src/href(如 /uploads/...),供 mp-html / rich-text 使用。 + * 已是 http(s) / data: / 协议相对 // 的不改。 + */ +export function rewriteHtmlMediaUrls(html?: string | null): string { + if (!html) return '' + return String(html).replace( + /\b(src|href)\s*=\s*(["'])(\/[^"'>\s]*)\2/gi, + (_m, attr: string, quote: string, path: string) => { + // 协议相对 //cdn... 不要当成本地路径 + if (path.startsWith('//')) return `${attr}=${quote}${path}${quote}` + return `${attr}=${quote}${getImageUrl(path)}${quote}` + }, + ) +} + +function joinUrl(path: string): string { + if (/^https?:\/\//i.test(path)) return path + const base = API_BASE.replace(/\/$/, '') + const p = path.startsWith('/') ? path : `/${path}` + if (p.startsWith('/api/')) return `${UPLOAD_BASE}${p}` + return `${base}${p}` +} + +/** + * GET 查询参数清洗:去掉 undefined/null/'',避免 uni.request 把 undefined 序列化成字面量 "undefined"。 + */ +function omitEmptyQueryParams(data?: Record) { + if (!data) return undefined + const entries = Object.entries(data).filter(([, v]) => v !== undefined && v !== null && v !== '') + if (!entries.length) return undefined + return Object.fromEntries(entries) +} + +type RequestOpts = { + method?: UniApp.RequestOptions['method'] + data?: UniApp.RequestOptions['data'] + header?: Record + auth?: boolean + skipSessionExpired?: boolean +} + +export async function request(path: string, options: RequestOpts = {}): Promise { + const { auth = false, skipSessionExpired = false, header = {}, method = 'GET', data } = options + const headers: Record = { + 'Content-Type': 'application/json', + ...header, + } + + if (auth) { + const { getToken } = useAuth() + const token = getToken() + if (token) headers.Authorization = `Bearer ${token}` + } + + /** GET 走清洗后的 query;其它方法保持原 body */ + const payload = method === 'GET' + ? omitEmptyQueryParams(data as Record | undefined) + : data + + return new Promise((resolve, reject) => { + uni.request({ + url: joinUrl(path), + method, + data: payload, + header: headers, + success: (res) => { + const body = res.data as ApiResponse + if (!body || typeof body !== 'object' || !('code' in body)) { + reject(new Error('响应解析失败')) + return + } + if (body.code === 401 && !skipSessionExpired) { + const { handleSessionExpired } = useAuth() + handleSessionExpired() + reject(new Error(body.message || '登录已过期,请重新登录')) + return + } + if (body.code !== 200) { + reject(new Error(body.message || '请求失败')) + return + } + resolve(body.result) + }, + fail: (err) => reject(new Error(err.errMsg || '网络错误')), + }) + }) +} + +export function get(path: string, data?: Record, auth = false) { + return request(path, { method: 'GET', data, auth }) +} + +export function post(path: string, data?: unknown, auth = false) { + return request(path, { method: 'POST', data: data as UniApp.RequestOptions['data'], auth }) +} + +export function put(path: string, data?: unknown, auth = false) { + return request(path, { method: 'PUT', data: data as UniApp.RequestOptions['data'], auth }) +} + +export function del(path: string, auth = true) { + return request(path, { method: 'DELETE', auth }) +} + +/** PATCH 请求(文章发布状态等) */ +export function patch(path: string, data?: unknown, auth = false) { + return request(path, { + method: 'PATCH' as UniApp.RequestOptions['method'], + data: data as UniApp.RequestOptions['data'], + auth, + }) +} + +/** + * 直传文件到后台附件接口。 + * uni.uploadFile 不走 JSON Content-Type,需单独拼 Authorization。 + */ +export function uploadFile( + path: string, + filePath: string, + formData: Record = {}, + name = 'file', +): Promise { + const { getToken } = useAuth() + const token = getToken() + const header: Record = {} + if (token) header.Authorization = `Bearer ${token}` + return new Promise((resolve, reject) => { + uni.uploadFile({ + url: joinUrl(path), + filePath, + name, + formData, + header, + success: (res) => { + try { + const body = JSON.parse(res.data) as ApiResponse + if (body.code === 401) { + const { handleSessionExpired } = useAuth() + handleSessionExpired() + reject(new Error(body.message || '登录已过期,请重新登录')) + return + } + if (body.code !== 200) { + reject(new Error(body.message || '上传失败')) + return + } + resolve(body.result) + } + catch { + reject(new Error('上传响应解析失败')) + } + }, + fail: (err) => reject(new Error(err.errMsg || '上传失败')), + }) + }) +} diff --git a/src/utils/unsupported.ts b/src/utils/unsupported.ts new file mode 100644 index 0000000..f0ff65a --- /dev/null +++ b/src/utils/unsupported.ts @@ -0,0 +1,20 @@ +import { useDialog } from '@wot-ui/ui' + +/** 小程序/App 暂不支持的能力统一提示 */ +export function showUnsupported(featureName: string) { + try { + const dialog = useDialog() + dialog.alert({ + title: '暂时不支持', + msg: `「${featureName}」在当前端暂不可用,请前往网页端使用。`, + confirmButtonText: '知道了', + }) + } + catch { + uni.showModal({ + title: '暂时不支持', + content: `「${featureName}」在当前端暂不可用,请前往网页端使用。`, + showCancel: false, + }) + } +}