From f42c36c430d4f84afd5eda73e92b44b48944466b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Thu, 13 Aug 2026 19:02:16 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=9F=E9=9A=8F=E4=B8=BB=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=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 | 75 +- .cursor/rules/Uniapp-Scss.mdc | 95 + .env.development | 2 + .env.production | 1 + components.d.ts | 27 +- index.html | 2 +- package.json | 2 + pnpm-lock.yaml | 1814 +++++++++-------- pnpm-workspace.yaml | 3 + src/App.vue | 18 +- src/api/admin.ts | 26 + src/api/auth.ts | 34 + src/api/media.ts | 63 + src/api/menu.ts | 26 + src/api/role.ts | 30 + src/api/system.ts | 75 + src/api/upload.ts | 9 + src/components/AppBottomBar.vue | 96 + src/components/AppCardList.vue | 168 ++ src/components/AppCategoryTabs.vue | 106 + src/components/AppDetailHero.vue | 114 ++ src/components/AppEmpty.vue | 51 + src/components/AppFab.vue | 63 + src/components/AppFeatureList.vue | 93 + src/components/AppFilterBar.vue | 126 ++ src/components/AppFooter.vue | 34 - src/components/AppFormDrawer.vue | 154 ++ src/components/AppGlassCard.vue | 136 ++ src/components/AppInfoGroup.vue | 80 + src/components/AppLoading.vue | 56 + src/components/AppLogos.vue | 59 - src/components/AppPageAtmosphere.vue | 93 + src/components/AppPageForm.vue | 104 + src/components/AppSearchBar.vue | 107 + src/components/AppShortcutGrid.vue | 79 + src/components/AppSkeleton.vue | 95 + src/components/AppTabBar.vue | 238 +++ src/components/InputEntry.vue | 36 - src/composables/usePullRefresh.ts | 19 + src/composables/useThemeShell.ts | 23 + src/config/features.ts | 156 ++ src/config/theme.ts | 16 + src/custom-tab-bar/index.vue | 23 + src/manifest.json | 36 +- src/pages-sub/media/pages/article/detail.vue | 217 ++ src/pages-sub/media/pages/article/form.vue | 146 ++ src/pages-sub/media/pages/article/list.vue | 164 ++ .../media/pages/article/versions.vue | 104 + src/pages-sub/media/utils/markdown.ts | 63 + src/pages-sub/mine/pages/login-log.vue | 73 + src/pages-sub/mine/pages/op-log.vue | 72 + src/pages-sub/mine/pages/profile.vue | 119 ++ src/pages-sub/my-gen/README.md | 8 + src/pages-sub/my-gen/pages/readme.vue | 43 + src/pages-sub/system/pages/admin/detail.vue | 96 + src/pages-sub/system/pages/admin/form.vue | 141 ++ src/pages-sub/system/pages/admin/list.vue | 153 ++ .../system/pages/config/ai-detail.vue | 91 + src/pages-sub/system/pages/config/ai-form.vue | 102 + src/pages-sub/system/pages/config/ai.vue | 90 + .../system/pages/config/api-detail.vue | 67 + src/pages-sub/system/pages/config/api.vue | 64 + src/pages-sub/system/pages/config/index.vue | 127 ++ .../system/pages/config/oss-detail.vue | 88 + .../system/pages/config/oss-form.vue | 119 ++ src/pages-sub/system/pages/config/oss.vue | 77 + src/pages-sub/system/pages/menu/detail.vue | 52 + src/pages-sub/system/pages/menu/form.vue | 90 + src/pages-sub/system/pages/menu/list.vue | 70 + src/pages-sub/system/pages/role/detail.vue | 66 + src/pages-sub/system/pages/role/form.vue | 72 + src/pages-sub/system/pages/role/list.vue | 72 + src/pages.json | 190 +- src/pages/feature/index.vue | 284 +++ src/pages/index.vue | 20 - src/pages/login/login.vue | 153 ++ src/pages/mine/mine.vue | 270 ++- src/pages/workbench/index.vue | 288 +++ src/static/logo.png | Bin 0 -> 119754 bytes src/static/logo.svg | 1 - src/static/tabbar/feature-active.png | Bin 0 -> 210 bytes src/static/tabbar/feature.png | Bin 0 -> 214 bytes src/static/tabbar/mine-active.png | Bin 0 -> 339 bytes src/static/tabbar/mine.png | Bin 0 -> 341 bytes src/static/tabbar/workbench-active.png | Bin 0 -> 253 bytes src/static/tabbar/workbench.png | Bin 0 -> 258 bytes src/stores/counter.ts | 10 - src/stores/theme.ts | 136 ++ src/stores/user.ts | 86 + src/styles/app.scss | 199 ++ src/styles/tokens.scss | 5 + src/theme.json | 26 +- src/uni.scss | 150 +- src/utils/env.ts | 32 + src/utils/recent.ts | 29 + src/utils/request.ts | 169 ++ unpackage/res/icons/1024x1024.png | Bin 0 -> 1661839 bytes unpackage/res/icons/120x120.png | Bin 0 -> 31576 bytes unpackage/res/icons/144x144.png | Bin 0 -> 43392 bytes unpackage/res/icons/152x152.png | Bin 0 -> 47633 bytes unpackage/res/icons/167x167.png | Bin 0 -> 56293 bytes unpackage/res/icons/180x180.png | Bin 0 -> 64064 bytes unpackage/res/icons/192x192.png | Bin 0 -> 72286 bytes unpackage/res/icons/20x20.png | Bin 0 -> 1402 bytes unpackage/res/icons/29x29.png | Bin 0 -> 2683 bytes unpackage/res/icons/40x40.png | Bin 0 -> 4698 bytes unpackage/res/icons/58x58.png | Bin 0 -> 8905 bytes unpackage/res/icons/60x60.png | Bin 0 -> 9502 bytes unpackage/res/icons/72x72.png | Bin 0 -> 13069 bytes unpackage/res/icons/76x76.png | Bin 0 -> 14303 bytes unpackage/res/icons/80x80.png | Bin 0 -> 15682 bytes unpackage/res/icons/87x87.png | Bin 0 -> 18110 bytes unpackage/res/icons/96x96.png | Bin 0 -> 21478 bytes vite.config.ts | 15 +- 114 files changed, 8257 insertions(+), 1115 deletions(-) create mode 100644 .cursor/rules/Uniapp-Scss.mdc create mode 100644 .env.development create mode 100644 .env.production create mode 100644 src/api/admin.ts create mode 100644 src/api/auth.ts create mode 100644 src/api/media.ts create mode 100644 src/api/menu.ts create mode 100644 src/api/role.ts create mode 100644 src/api/system.ts create mode 100644 src/api/upload.ts create mode 100644 src/components/AppBottomBar.vue create mode 100644 src/components/AppCardList.vue create mode 100644 src/components/AppCategoryTabs.vue create mode 100644 src/components/AppDetailHero.vue create mode 100644 src/components/AppEmpty.vue create mode 100644 src/components/AppFab.vue create mode 100644 src/components/AppFeatureList.vue create mode 100644 src/components/AppFilterBar.vue delete mode 100644 src/components/AppFooter.vue create mode 100644 src/components/AppFormDrawer.vue create mode 100644 src/components/AppGlassCard.vue create mode 100644 src/components/AppInfoGroup.vue create mode 100644 src/components/AppLoading.vue delete mode 100644 src/components/AppLogos.vue create mode 100644 src/components/AppPageAtmosphere.vue create mode 100644 src/components/AppPageForm.vue create mode 100644 src/components/AppSearchBar.vue create mode 100644 src/components/AppShortcutGrid.vue create mode 100644 src/components/AppSkeleton.vue create mode 100644 src/components/AppTabBar.vue delete mode 100644 src/components/InputEntry.vue create mode 100644 src/composables/usePullRefresh.ts create mode 100644 src/composables/useThemeShell.ts create mode 100644 src/config/features.ts create mode 100644 src/config/theme.ts create mode 100644 src/custom-tab-bar/index.vue create mode 100644 src/pages-sub/media/pages/article/detail.vue create mode 100644 src/pages-sub/media/pages/article/form.vue create mode 100644 src/pages-sub/media/pages/article/list.vue create mode 100644 src/pages-sub/media/pages/article/versions.vue create mode 100644 src/pages-sub/media/utils/markdown.ts create mode 100644 src/pages-sub/mine/pages/login-log.vue create mode 100644 src/pages-sub/mine/pages/op-log.vue create mode 100644 src/pages-sub/mine/pages/profile.vue create mode 100644 src/pages-sub/my-gen/README.md create mode 100644 src/pages-sub/my-gen/pages/readme.vue create mode 100644 src/pages-sub/system/pages/admin/detail.vue create mode 100644 src/pages-sub/system/pages/admin/form.vue create mode 100644 src/pages-sub/system/pages/admin/list.vue create mode 100644 src/pages-sub/system/pages/config/ai-detail.vue create mode 100644 src/pages-sub/system/pages/config/ai-form.vue create mode 100644 src/pages-sub/system/pages/config/ai.vue create mode 100644 src/pages-sub/system/pages/config/api-detail.vue create mode 100644 src/pages-sub/system/pages/config/api.vue create mode 100644 src/pages-sub/system/pages/config/index.vue create mode 100644 src/pages-sub/system/pages/config/oss-detail.vue create mode 100644 src/pages-sub/system/pages/config/oss-form.vue create mode 100644 src/pages-sub/system/pages/config/oss.vue create mode 100644 src/pages-sub/system/pages/menu/detail.vue create mode 100644 src/pages-sub/system/pages/menu/form.vue create mode 100644 src/pages-sub/system/pages/menu/list.vue create mode 100644 src/pages-sub/system/pages/role/detail.vue create mode 100644 src/pages-sub/system/pages/role/form.vue create mode 100644 src/pages-sub/system/pages/role/list.vue create mode 100644 src/pages/feature/index.vue delete mode 100644 src/pages/index.vue create mode 100644 src/pages/login/login.vue create mode 100644 src/pages/workbench/index.vue create mode 100644 src/static/logo.png delete mode 100644 src/static/logo.svg create mode 100644 src/static/tabbar/feature-active.png create mode 100644 src/static/tabbar/feature.png create mode 100644 src/static/tabbar/mine-active.png create mode 100644 src/static/tabbar/mine.png create mode 100644 src/static/tabbar/workbench-active.png create mode 100644 src/static/tabbar/workbench.png delete mode 100644 src/stores/counter.ts create mode 100644 src/stores/theme.ts create mode 100644 src/stores/user.ts create mode 100644 src/styles/app.scss create mode 100644 src/styles/tokens.scss create mode 100644 src/utils/env.ts create mode 100644 src/utils/recent.ts create mode 100644 src/utils/request.ts create mode 100644 unpackage/res/icons/1024x1024.png create mode 100644 unpackage/res/icons/120x120.png create mode 100644 unpackage/res/icons/144x144.png create mode 100644 unpackage/res/icons/152x152.png create mode 100644 unpackage/res/icons/167x167.png create mode 100644 unpackage/res/icons/180x180.png create mode 100644 unpackage/res/icons/192x192.png create mode 100644 unpackage/res/icons/20x20.png create mode 100644 unpackage/res/icons/29x29.png create mode 100644 unpackage/res/icons/40x40.png create mode 100644 unpackage/res/icons/58x58.png create mode 100644 unpackage/res/icons/60x60.png create mode 100644 unpackage/res/icons/72x72.png create mode 100644 unpackage/res/icons/76x76.png create mode 100644 unpackage/res/icons/80x80.png create mode 100644 unpackage/res/icons/87x87.png create mode 100644 unpackage/res/icons/96x96.png diff --git a/.cursor/rules/Code-Standards.mdc b/.cursor/rules/Code-Standards.mdc index 673a218..394e6e6 100644 --- a/.cursor/rules/Code-Standards.mdc +++ b/.cursor/rules/Code-Standards.mdc @@ -47,7 +47,7 @@ src/ ## 主包只放这些 -- TabBar 页面(如首页、我的) +- TabBar 页面(工作台、功能、我的) - 登录 / 启动过渡 / 协议等冷启动必经页 - 全局强依赖且体积很小的公共组件与工具 @@ -127,6 +127,7 @@ src/ - 有业务逻辑的函数必须写中文注释(做什么、为什么) - 样式优先 `scoped`;尺寸用 rpx;颜色尽量走主题变量(`theme.json` / wot CSS 变量),避免散落魔法色值 - 列表分页、表单校验、空态/加载态要完整,不要只做「能点开」的半成品 +- **下拉刷新(强制)**:有数据列表的页面必须支持下拉刷新;`pages.json` 开 `enablePullDownRefresh`,逻辑用 `usePullRefresh(reload)`(见 `src/composables/usePullRefresh.ts`);内层 `scroll-view`/`swiper` 页改用 `refresher-enabled` # 命名规范 @@ -145,3 +146,75 @@ src/ - 禁止在业务页复制粘贴请求头、baseURL、token 逻辑 - 禁止抽屉不用 `page-container`、或用 `v-show` 控制抽屉 - 禁止提交无中文注释的复杂业务逻辑 +- 禁止把 PC 后台表格/工具栏交互原样搬到小程序 +- 禁止业务页自写一套分类 Tab / 顶部筛选(必须用通用组件) + +# 三端兼容(强制) + +业务代码与代码生成产物必须同时可运行于: + +- **微信小程序(mp-weixin)** +- **App(app-plus)** +- **H5** + +约定: + +- 优先使用 `uni.*` 跨端 API;仅在确有差异处使用 `#ifdef MP-WEIXIN / APP-PLUS / H5`,并写中文注释说明原因 +- 禁止依赖仅某一端可用的 DOM / BOM / 私有组件而不做兜底 +- TabBar、分包、`page-container` 等能力按三端都能接受的方式实现 + +# C 端 UI 风格(强制) + +能力可以是超管,**视觉与交互必须按 C 端消费级 App**,禁止做成缩小版 PC 后台: + +- 信息架构:卡片流 / 分组列表 / 宫格入口;一行一事;禁止宽表、多列表头 +- 触控:点击区域 ≥ 44px(约 88rpx);主操作底部大按钮或悬浮主按钮 +- 视觉:大圆角、留白、主题色走 `theme.json` / `uni.scss` 中的 `$nl-*`(详见 `Uniapp-Scss.mdc`) +- 反馈:空态、Toast;危险操作再用确认框 +- 登录 / 我的:品牌区 + 表单;头像 Hero + 单元格菜单 +- **列表 CRUD 交互(强制)**:列表点击 → **详情页** → 底栏「编辑」→ **全屏表单页**;新增也走全屏表单 +- **禁止**列表点开底部抽屉做主 CRUD(`AppFormDrawer` 仅轻量选择器可用) +- 系统配置:设置卡 / 入口列表,敏感信息脱敏 +- 详情页结构:`AppDetailHero` + `AppInfoGroup` + `AppBottomBar` +- 表单页结构:`AppPageForm`(分组字段 + 底栏提交) + +# TabBar 信息架构(强制) + +主包 Tab 固定为三页: + +| Tab | 路径 | 职责 | +|-----|------|------| +| 工作台 | `pages/workbench/index` | 概览、快捷入口、最近使用 | +| 功能 | `pages/feature/index` | 应用中心:分类浏览 + 筛选 + 入口列表 | +| 我的 | `pages/mine/mine` | 个人中心 | + +登录页不进 TabBar。业务深度页进 `pages-sub/*`。 + +## 功能页约定 + +- 分类 Tab 默认 **全部**;支持点击与 **左右滑动(swiper)** 切换 +- 分类选中态本地记忆(如 `nl_feature_tab_key`) +- 筛选项在分类 Tab **下方顶部**,默认收起摘要行,支持展开/收起;筛选条件可本地记忆 +- 入口数据来自 `src/config/features.ts` 注册表;新增能力必须登记 + +# 通用组件优先(禁止重复造轮子) + +优先复用 `src/components/`: + +- `AppPageAtmosphere`:页面氛围底(色斑渐变),毛玻璃卡片必须叠在此上才有「透」感 +- `AppGlassCard`:**统一毛玻璃卡片容器**(variant / pressable / title);禁止业务页自写半透明卡片 +- `AppCategoryTabs`:分类 Tab + 可选本地记忆 +- `AppFilterBar`:顶部可展开筛选壳 +- `AppSearchBar` / `AppFeatureList` / `AppCardList` / `AppEmpty` / `AppSkeleton` / `AppLoading` +- `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm`:详情与全屏表单 +- `AppFormDrawer`:非主路径,仅轻量选择器;CRUD 禁止用 +- `AppShortcutGrid`:工作台快捷宫格 +- `AppTabBar`:悬浮自定义 TabBar(`wd-tabbar` shape=round) + +新需求先扩展上述组件,禁止业务页复制一套 Tab/筛选/抽屉/毛玻璃卡片。 + +# 代码生成产物落点 + +- 粘贴目录:`src/pages-sub/my-gen//` +- 同步改 `pages.json` 的 `my-gen` 分包,并在 `src/config/features.ts` 登记(`category: 'gen'`) +- 生成模板必须遵守本文件的三端兼容、C 端 UI、通用组件约定 diff --git a/.cursor/rules/Uniapp-Scss.mdc b/.cursor/rules/Uniapp-Scss.mdc new file mode 100644 index 0000000..09cc562 --- /dev/null +++ b/.cursor/rules/Uniapp-Scss.mdc @@ -0,0 +1,95 @@ +--- +description: uni-app Sass/样式变量踩坑与正确写法(uni.scss 禁止 @import、禁止 additionalData) +alwaysApply: true +--- + +# Uniapp Sass / 样式变量规范 + +## 共享 SCSS 变量的正确方式(强制) + +uni-app(Vue3 + Vite)会把 [`src/uni.scss`](src/uni.scss) **全文注入**到每个页面/组件的 ` + diff --git a/src/api/admin.ts b/src/api/admin.ts new file mode 100644 index 0000000..3ee7bb1 --- /dev/null +++ b/src/api/admin.ts @@ -0,0 +1,26 @@ +/** + * 管理员 CRUD API + */ +import { get, post } from '@/utils/request' + +const prefix = 'admin/' + +export function getAdminList(params: Record = {}) { + return get(`${prefix}list`, params) +} + +export function getAdminInfo(id: number) { + return get(`${prefix}detail`, { id }) +} + +export function createAdmin(data: Record) { + return post(`${prefix}create`, data) +} + +export function updateAdmin(data: Record) { + return post(`${prefix}update`, data) +} + +export function deleteAdmin(data: Record) { + return post(`${prefix}delete`, data) +} diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..278e8fc --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,34 @@ +/** + * 认证相关 API + */ +import { get, post } from '@/utils/request' + +/** 账号密码登录 */ +export function loginApi(data: { account: string; password: string }) { + return post<{ token: string }>('login', data, { skipAuth: true, silentAuth: true }) +} + +/** 当前登录用户信息 */ +export function getUserInfoApi() { + return get('admin/my-info') +} + +/** 更新个人资料 */ +export function updateProfileApi(data: Record) { + return post('admin/update-profile', data) +} + +/** 修改密码 */ +export function changePasswordApi(data: Record) { + return post('admin/change-password', data) +} + +/** 我的登录日志 */ +export function getLoginLogApi(params: Record = {}) { + return get('admin/login-log', params) +} + +/** 我的操作日志 */ +export function getOpLogApi(params: Record = {}) { + return get('admin/op-log', params) +} diff --git a/src/api/media.ts b/src/api/media.ts new file mode 100644 index 0000000..95a4831 --- /dev/null +++ b/src/api/media.ts @@ -0,0 +1,63 @@ +/** + * 新媒体运营(公众号图文)相关请求 + * 移动端只做:列表 / 预览 / 一键发布 / 版本回退,不做排版编辑(完整排版在 PC 编辑器) + */ +import { get, post } from '@/utils/request' + +/** 图文分页列表(后端不返回 content_md 大字段) */ +export function getWechatArticleList(params: Record = {}) { + return get('wechat-article/list', params) +} + +/** 图文详情(含 content_md,预览页渲染用) */ +export function getWechatArticleInfo(id: number) { + return get('wechat-article/detail', { id }) +} + +/** 新建图文(后端写「创建」版本流水,排版主题在 PC 编辑器另配) */ +export function createWechatArticle(data: Record) { + return post('wechat-article/create', data) +} + +/** 更新图文(后端写「修改」版本流水,version_no+1) */ +export function updateWechatArticle(data: Record) { + return post('wechat-article/update', data) +} + +/** 删除图文(后端写「删除」版本流水留痕) */ +export function deleteWechatArticle(ids: number[]) { + return post('wechat-article/delete', { ids }) +} + +/** 某篇文章的版本流水(倒序) */ +export function getWechatArticleVersionList(params: Record = {}) { + return get('wechat-article/version-list', params) +} + +/** 版本快照详情(含该版本 content_md) */ +export function getWechatArticleVersionInfo(id: number) { + return get('wechat-article/version-detail', { id }) +} + +/** 回退到指定版本(快照覆盖回文章,version_no+1) */ +export function rollbackWechatArticle(data: { id: number, version_id: number }) { + return post('wechat-article/rollback', data) +} + +/** + * 一键发布:content_html 为渲染好的内联样式 HTML + * 移动端用简化排版渲染(详见 pages-sub/media/utils/markdown.ts),完整主题排版请在 PC 发布 + */ +export function publishWechatArticle(data: { id: number, content_html: string, account_id?: number }) { + return post('wechat-article/publish', data) +} + +/** 查询发布审核状态(freepublish 异步审核,需主动刷新) */ +export function getWechatArticlePublishStatus(id: number) { + return get('wechat-article/publish-status', { id }) +} + +/** 公众号账号下拉(发布时选账号,默认账号排最前) */ +export function getWechatAccountOption() { + return get('wechat-account/option') +} diff --git a/src/api/menu.ts b/src/api/menu.ts new file mode 100644 index 0000000..ebc9363 --- /dev/null +++ b/src/api/menu.ts @@ -0,0 +1,26 @@ +/** + * 菜单 CRUD API + */ +import { get, post } from '@/utils/request' + +const prefix = 'menu/' + +export function getMenuList(params: Record = {}) { + return get(`${prefix}list`, params) +} + +export function getMenuInfo(id: number) { + return get(`${prefix}detail`, { id }) +} + +export function createMenu(data: Record) { + return post(`${prefix}create`, data) +} + +export function updateMenu(data: Record) { + return post(`${prefix}update`, data) +} + +export function deleteMenu(data: Record) { + return post(`${prefix}delete`, data) +} diff --git a/src/api/role.ts b/src/api/role.ts new file mode 100644 index 0000000..9c2b16e --- /dev/null +++ b/src/api/role.ts @@ -0,0 +1,30 @@ +/** + * 角色 CRUD API + */ +import { get, post } from '@/utils/request' + +const prefix = 'role/' + +export function getRoleList(params: Record = {}) { + return get(`${prefix}list`, params) +} + +export function getRoleOption(params: Record = {}) { + return get(`${prefix}option`, params) +} + +export function getRoleInfo(id: number) { + return get(`${prefix}detail`, { id }) +} + +export function createRole(data: Record) { + return post(`${prefix}create`, data) +} + +export function updateRole(data: Record) { + return post(`${prefix}update`, data) +} + +export function deleteRole(data: Record) { + return post(`${prefix}delete`, data) +} diff --git a/src/api/system.ts b/src/api/system.ts new file mode 100644 index 0000000..5316963 --- /dev/null +++ b/src/api/system.ts @@ -0,0 +1,75 @@ +/** + * 系统配置相关:AI / OSS / 接口注册表 + */ +import { get, post } from '@/utils/request' + +/** AI 运行时选项 */ +export function getAiRuntimeOptions() { + return get('ai-config/runtime-options') +} + +export function saveAiRuntime(data: Record) { + return post('ai-config/save-runtime', data) +} + +export function getAiKeyList(params: Record = {}) { + return get('ai-config/key-list', params) +} + +export function getAiPlatformOption() { + return get('ai-config/platform-option') +} + +export function createAiKey(data: Record) { + return post('ai-config/create-key', data) +} + +export function updateAiKey(data: Record) { + return post('ai-config/update-key', data) +} + +export function deleteAiKey(ids: number[]) { + return post('ai-config/delete-key', { ids }) +} + +export function getAiGenerationList(params: Record = {}) { + return get('ai-config/generation-list', params) +} + +/** OSS */ +export function getOssDriverOptions() { + return get('oss-config/driver-options') +} + +export function getOssRuntimeOptions() { + return get('oss-config/runtime-options') +} + +export function saveOssRuntime(data: { id: number }) { + return post('oss-config/save-runtime', data) +} + +export function getOssConfigList(params: Record = {}) { + return get('oss-config/list', params) +} + +export function createOssConfig(data: Record) { + return post('oss-config/create', data) +} + +export function updateOssConfig(data: Record) { + return post('oss-config/update', data) +} + +export function deleteOssConfig(ids: number[]) { + return post('oss-config/delete', { ids }) +} + +/** 接口注册表 */ +export function getApiEndpointList(params: Record = {}) { + return get('api-endpoint/list', params) +} + +export function updateApiEndpoint(data: Record) { + return post('api-endpoint/update', data) +} diff --git a/src/api/upload.ts b/src/api/upload.ts new file mode 100644 index 0000000..f23e4ad --- /dev/null +++ b/src/api/upload.ts @@ -0,0 +1,9 @@ +/** + * 上传 API + */ +import { uploadFile } from '@/utils/request' + +/** 上传图片,返回带 url 的结果 */ +export function uploadImage(filePath: string) { + return uploadFile('upload/image', filePath, 'file') +} diff --git a/src/components/AppBottomBar.vue b/src/components/AppBottomBar.vue new file mode 100644 index 0000000..210979e --- /dev/null +++ b/src/components/AppBottomBar.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/components/AppCardList.vue b/src/components/AppCardList.vue new file mode 100644 index 0000000..a26be97 --- /dev/null +++ b/src/components/AppCardList.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/src/components/AppCategoryTabs.vue b/src/components/AppCategoryTabs.vue new file mode 100644 index 0000000..78b2dde --- /dev/null +++ b/src/components/AppCategoryTabs.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/AppDetailHero.vue b/src/components/AppDetailHero.vue new file mode 100644 index 0000000..f28f4cd --- /dev/null +++ b/src/components/AppDetailHero.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/components/AppEmpty.vue b/src/components/AppEmpty.vue new file mode 100644 index 0000000..b0ec9c9 --- /dev/null +++ b/src/components/AppEmpty.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/AppFab.vue b/src/components/AppFab.vue new file mode 100644 index 0000000..aac5dda --- /dev/null +++ b/src/components/AppFab.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/components/AppFeatureList.vue b/src/components/AppFeatureList.vue new file mode 100644 index 0000000..da08a11 --- /dev/null +++ b/src/components/AppFeatureList.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/AppFilterBar.vue b/src/components/AppFilterBar.vue new file mode 100644 index 0000000..483f6b3 --- /dev/null +++ b/src/components/AppFilterBar.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue deleted file mode 100644 index 61049b6..0000000 --- a/src/components/AppFooter.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/src/components/AppFormDrawer.vue b/src/components/AppFormDrawer.vue new file mode 100644 index 0000000..c38b5f9 --- /dev/null +++ b/src/components/AppFormDrawer.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/src/components/AppGlassCard.vue b/src/components/AppGlassCard.vue new file mode 100644 index 0000000..1be28aa --- /dev/null +++ b/src/components/AppGlassCard.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/AppInfoGroup.vue b/src/components/AppInfoGroup.vue new file mode 100644 index 0000000..db06389 --- /dev/null +++ b/src/components/AppInfoGroup.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/components/AppLoading.vue b/src/components/AppLoading.vue new file mode 100644 index 0000000..4eb8939 --- /dev/null +++ b/src/components/AppLoading.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/components/AppLogos.vue b/src/components/AppLogos.vue deleted file mode 100644 index 8779ca6..0000000 --- a/src/components/AppLogos.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/src/components/AppPageAtmosphere.vue b/src/components/AppPageAtmosphere.vue new file mode 100644 index 0000000..486348d --- /dev/null +++ b/src/components/AppPageAtmosphere.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/AppPageForm.vue b/src/components/AppPageForm.vue new file mode 100644 index 0000000..81d7e8d --- /dev/null +++ b/src/components/AppPageForm.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/components/AppSearchBar.vue b/src/components/AppSearchBar.vue new file mode 100644 index 0000000..a3382d5 --- /dev/null +++ b/src/components/AppSearchBar.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/src/components/AppShortcutGrid.vue b/src/components/AppShortcutGrid.vue new file mode 100644 index 0000000..4480a16 --- /dev/null +++ b/src/components/AppShortcutGrid.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/AppSkeleton.vue b/src/components/AppSkeleton.vue new file mode 100644 index 0000000..8d1855f --- /dev/null +++ b/src/components/AppSkeleton.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/AppTabBar.vue b/src/components/AppTabBar.vue new file mode 100644 index 0000000..bcdbf23 --- /dev/null +++ b/src/components/AppTabBar.vue @@ -0,0 +1,238 @@ + + + + + + + diff --git a/src/components/InputEntry.vue b/src/components/InputEntry.vue deleted file mode 100644 index 10bc891..0000000 --- a/src/components/InputEntry.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/src/composables/usePullRefresh.ts b/src/composables/usePullRefresh.ts new file mode 100644 index 0000000..376cf7d --- /dev/null +++ b/src/composables/usePullRefresh.ts @@ -0,0 +1,19 @@ +/** + * 页面级下拉刷新:pages.json 需开 enablePullDownRefresh + * 为什么封装:统一 stopPullDownRefresh,避免各页漏调导致转圈不消失 + */ +import { onPullDownRefresh } from '@dcloudio/uni-app' + +/** + * 注册下拉刷新:执行 refresh 后自动结束原生刷新动画 + * @param refresh 刷新逻辑(通常是 reload / loadAll) + */ +export function usePullRefresh(refresh: () => void | Promise) { + onPullDownRefresh(async () => { + try { + await Promise.resolve(refresh()) + } finally { + uni.stopPullDownRefresh() + } + }) +} diff --git a/src/composables/useThemeShell.ts b/src/composables/useThemeShell.ts new file mode 100644 index 0000000..e3e401d --- /dev/null +++ b/src/composables/useThemeShell.ts @@ -0,0 +1,23 @@ +/** + * 页面壳主题组合式函数:返回 wd-config-provider 需要的 theme 值 + 进页时同步导航栏 + * + * 用法(对齐 wot 官方深色方案):页面壳根节点用 + * 包裹整页内容; + * AppPageAtmosphere / AppPageForm 已内置,业务页无需关心 + */ +import { computed } from 'vue' +import { onShow } from '@dcloudio/uni-app' + +import { useThemeStore } from '@/stores/theme' + +export function useThemeShell() { + const themeStore = useThemeStore() + + /** 'light' | 'dark',跟随系统时已解析为具体值,首帧即正确 */ + const wotTheme = computed(() => themeStore.wotTheme) + + // 每次进页补齐原生导航栏/窗口背景(强制模式下 theme.json 不会帮忙) + onShow(() => themeStore.applyNavChrome()) + + return { themeStore, wotTheme } +} diff --git a/src/config/features.ts b/src/config/features.ts new file mode 100644 index 0000000..acab0f0 --- /dev/null +++ b/src/config/features.ts @@ -0,0 +1,156 @@ +/** + * 功能入口注册表:功能 Tab 分类浏览、工作台快捷入口均读此配置 + * 新增分包页后在此登记,并同步 pages.json + */ + +/** 功能分类:全部为虚拟分类,其余按 category 过滤 */ +export type FeatureCategory = 'all' | 'system' | 'config' | 'media' | 'gen' | 'other' + +export interface FeatureItem { + key: string + title: string + desc: string + /** wot-design-uni wd-icon 名称,禁止再塞 emoji/单字占位 */ + icon: string + category: Exclude + path: string + keywords?: string[] + /** 工作台默认置顶 */ + pinned?: boolean +} + +export interface FeatureCategoryOption { + key: FeatureCategory + label: string +} + +/** 分类 Tab 定义(默认全部) */ +export const FEATURE_CATEGORIES: FeatureCategoryOption[] = [ + { key: 'all', label: '全部' }, + { key: 'system', label: '系统' }, + { key: 'config', label: '配置' }, + { key: 'media', label: '新媒体' }, + { key: 'gen', label: '生成' }, + { key: 'other', label: '其他' }, +] + +/** 全部功能入口 */ +export const FEATURE_LIST: FeatureItem[] = [ + { + key: 'admin', + title: '管理员', + desc: '账号与角色分配', + icon: 'user', + category: 'system', + path: '/pages-sub/system/pages/admin/list', + keywords: ['用户', '账号'], + pinned: true, + }, + { + key: 'role', + title: '角色管理', + desc: '角色与菜单权限', + icon: 'usergroup', + category: 'system', + path: '/pages-sub/system/pages/role/list', + keywords: ['权限'], + pinned: true, + }, + { + key: 'menu', + title: '菜单管理', + desc: 'PC 菜单树维护', + icon: 'view-list', + category: 'system', + path: '/pages-sub/system/pages/menu/list', + keywords: ['路由'], + }, + { + key: 'system-config', + title: '系统配置', + desc: 'AI / OSS / 接口总览', + icon: 'setting', + category: 'config', + path: '/pages-sub/system/pages/config/index', + keywords: ['设置'], + pinned: true, + }, + { + key: 'ai-config', + title: 'AI 管理', + desc: '模型与密钥', + icon: 'secured', + category: 'config', + path: '/pages-sub/system/pages/config/ai', + keywords: ['大模型', 'key'], + }, + { + key: 'oss-config', + title: 'OSS 管理', + desc: '对象存储与启用配置', + icon: 'cloud', + category: 'config', + path: '/pages-sub/system/pages/config/oss', + keywords: ['上传', '存储'], + }, + { + key: 'api-endpoint', + title: '接口管理', + desc: '操作日志开关', + icon: 'link', + category: 'config', + path: '/pages-sub/system/pages/config/api', + keywords: ['日志', '接口'], + }, + { + key: 'wechat-article', + title: '图文创作', + desc: '公众号图文预览与发布', + icon: 'subscribe', + category: 'media', + path: '/pages-sub/media/pages/article/list', + keywords: ['公众号', '微信', '发布', '文章'], + pinned: true, + }, + { + key: 'my-gen', + title: '代码生成模块', + desc: '粘贴生成的 uniapp 页面', + icon: 'code', + category: 'gen', + path: '/pages-sub/my-gen/pages/readme', + keywords: ['codegen', '生成'], + }, +] + +/** 按 key 取功能 */ +export function getFeatureByKey(key: string): FeatureItem | undefined { + return FEATURE_LIST.find((f) => f.key === key) +} + +/** + * 按分类 + 关键词 + 仅最近使用 过滤功能列表 + */ +export function filterFeatures(options: { + category: FeatureCategory + keyword?: string + recentOnly?: boolean + recentKeys?: string[] +}): FeatureItem[] { + const kw = (options.keyword || '').trim().toLowerCase() + let list = FEATURE_LIST.slice() + if (options.category !== 'all') { + list = list.filter((f) => f.category === options.category) + } + if (options.recentOnly && options.recentKeys) { + const set = new Set(options.recentKeys) + list = list.filter((f) => set.has(f.key)) + } + if (kw) { + list = list.filter((f) => { + const bag = [f.title, f.desc, ...(f.keywords || [])].join(' ').toLowerCase() + return bag.includes(kw) + }) + } + return list +} diff --git a/src/config/theme.ts b/src/config/theme.ts new file mode 100644 index 0000000..87ec347 --- /dev/null +++ b/src/config/theme.ts @@ -0,0 +1,16 @@ +/** + * 全局 wot / C 端主题色(与 uni.scss $nl-color-primary 对齐) + * 挂在 page 上,让 wd-* 默认读到 --wot-color-theme,无需每页写 ConfigProvider + */ +export const NL_THEME_PRIMARY = '#ff6b00' + +export const wotThemeVars = { + colorTheme: NL_THEME_PRIMARY, + colorSuccess: '#16a34a', + colorWarning: '#f59e0b', + colorDanger: '#ef4444', + buttonPrimaryBgColor: NL_THEME_PRIMARY, + buttonPrimaryColor: '#ffffff', + tabsNavLineColor: NL_THEME_PRIMARY, + tabbarActiveColor: NL_THEME_PRIMARY, +} diff --git a/src/custom-tab-bar/index.vue b/src/custom-tab-bar/index.vue new file mode 100644 index 0000000..19a67e6 --- /dev/null +++ b/src/custom-tab-bar/index.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/manifest.json b/src/manifest.json index 3458c59..13aaca0 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,5 +1,5 @@ { - "name": "", + "name": "NL Admin", "appid": "", "description": "", "versionName": "1.0.0", @@ -40,7 +40,39 @@ ] }, "ios": {}, - "sdkConfigs": {} + "sdkConfigs": {}, + "icons": { + "android": { + "hdpi": "unpackage/res/icons/72x72.png", + "xhdpi": "unpackage/res/icons/96x96.png", + "xxhdpi": "unpackage/res/icons/144x144.png", + "xxxhdpi": "unpackage/res/icons/192x192.png" + }, + "ios": { + "appstore": "unpackage/res/icons/1024x1024.png", + "ipad": { + "app": "unpackage/res/icons/76x76.png", + "app@2x": "unpackage/res/icons/152x152.png", + "notification": "unpackage/res/icons/20x20.png", + "notification@2x": "unpackage/res/icons/40x40.png", + "proapp@2x": "unpackage/res/icons/167x167.png", + "settings": "unpackage/res/icons/29x29.png", + "settings@2x": "unpackage/res/icons/58x58.png", + "spotlight": "unpackage/res/icons/40x40.png", + "spotlight@2x": "unpackage/res/icons/80x80.png" + }, + "iphone": { + "app@2x": "unpackage/res/icons/120x120.png", + "app@3x": "unpackage/res/icons/180x180.png", + "notification@2x": "unpackage/res/icons/40x40.png", + "notification@3x": "unpackage/res/icons/60x60.png", + "settings@2x": "unpackage/res/icons/58x58.png", + "settings@3x": "unpackage/res/icons/87x87.png", + "spotlight@2x": "unpackage/res/icons/80x80.png", + "spotlight@3x": "unpackage/res/icons/120x120.png" + } + } + } } }, "h5": { diff --git a/src/pages-sub/media/pages/article/detail.vue b/src/pages-sub/media/pages/article/detail.vue new file mode 100644 index 0000000..e6a901e --- /dev/null +++ b/src/pages-sub/media/pages/article/detail.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/src/pages-sub/media/pages/article/form.vue b/src/pages-sub/media/pages/article/form.vue new file mode 100644 index 0000000..814bd2e --- /dev/null +++ b/src/pages-sub/media/pages/article/form.vue @@ -0,0 +1,146 @@ + + +