commit 0d82386f8f056a111f3736b390058c6f096592d2
Author: 赵忠林 <170083662@qq.com>
Date: Wed Apr 29 01:33:33 2026 +0800
新版官网模板
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5e791aa
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/.nuxt/
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..6e50d12
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..8894a98
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,334 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "associatedIndex": 6
+}
+
+
+
+
+
+
+
+
+ {
+ "keyToString": {
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "git-widget-placeholder": "master",
+ "junie.onboarding.icon.badge.shown": "true",
+ "last_opened_file_path": "/Users/gxwebsoft/VUE/template-10398",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "npm.build.executor": "Run",
+ "npm.dev.executor": "Run",
+ "settings.editor.selected.configurable": "preferences.fileTypes",
+ "to.speed.mode.migration.done": "true",
+ "ts.external.directory.path": "/Users/gxwebsoft/VUE/template-10398/node_modules/typescript/lib",
+ "vue.rearranger.settings.migration": "true"
+ },
+ "keyToStringList": {
+ "vue.recent.templates": [
+ "Vue Composition API Component"
+ ]
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1737991350538
+
+
+ 1737991350538
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1737991382247
+
+
+
+ 1737991382247
+
+
+
+ 1737991482466
+
+
+
+ 1737991482466
+
+
+
+ 1737992096276
+
+
+
+ 1737992096276
+
+
+
+ 1739349430003
+
+
+
+ 1739349430003
+
+
+
+ 1739352568726
+
+
+
+ 1739352568726
+
+
+
+ 1739559355993
+
+
+
+ 1739559355993
+
+
+
+ 1739559687832
+
+
+
+ 1739559687832
+
+
+
+ 1739616110552
+
+
+
+ 1739616110552
+
+
+
+ 1739777280019
+
+
+
+ 1739777280019
+
+
+
+ 1740188044006
+
+
+
+ 1740188044006
+
+
+
+ 1740191113248
+
+
+
+ 1740191113248
+
+
+
+ 1740278860048
+
+
+
+ 1740278860048
+
+
+
+ 1740797833986
+
+
+
+ 1740797833986
+
+
+
+ 1740809410991
+
+
+
+ 1740809410991
+
+
+
+ 1742028204628
+
+
+
+ 1742028204628
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..cbe6572
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,33 @@
+# syntax=docker/dockerfile:1.7
+ARG NODE_VERSION=20.19.0
+
+FROM node:${NODE_VERSION}-slim AS build
+WORKDIR /app
+
+ENV NODE_ENV=development \
+ NUXT_TELEMETRY_DISABLED=1
+
+COPY package.json package-lock.json ./
+COPY scripts ./scripts
+
+RUN npm ci --ignore-scripts
+
+COPY . .
+
+RUN npm rebuild
+RUN npm run postinstall
+RUN npm run build
+
+FROM node:${NODE_VERSION}-slim AS runner
+WORKDIR /app
+
+ENV NODE_ENV=production \
+ HOST=0.0.0.0 \
+ PORT=3000 \
+ NUXT_TELEMETRY_DISABLED=1
+
+COPY --from=build /app/.output ./.output
+
+EXPOSE 3000
+
+CMD ["node", ".output/server/index.mjs"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..93271de
--- /dev/null
+++ b/README.md
@@ -0,0 +1,193 @@
+
+
🌐 Websopy Admin
+
葳溯科技 · 基于 Vue 3 + Ant Design Vue 的企业级后台管理系统
+
+
+
+
+
+
+
+
+
+
+## 📖 项目简介
+
+Websopy Admin(葳溯科技)是一个基于 **Vue 3 + Ant Design Vue** 构建的现代化企业级后台管理系统,采用最新的前端技术栈:
+
+- **前端框架**:Vue 3 + TypeScript + Vite
+- **UI 组件库**:Ant Design Vue 3.x
+- **富文本编辑器**:TinyMCE(支持图片/视频上传、一键排版)
+- **图表库**:ECharts + G2
+- **工具库**:Lodash、Day.js、CryptoJS
+
+
+
+## 项目演示
+| 后台管理系统 | https://mp.websoft.top |
+|--------|-------------------------------------------------------------------------------------------------------------------------------------|
+| 账号密码 | [立即注册](https://mp.websoft.top/register/?inviteCode=github) |
+| 关注公众号 |  |
+
+
+
+
+## 🛠️ 技术栈
+
+### 核心技术
+| 技术 | 版本 | 说明 |
+|------|------|------|
+| Vue | 3.x | 渐进式 JavaScript 框架 |
+| TypeScript | 4.x | JavaScript 的超集 |
+| Vite | 4.x | 下一代前端构建工具 |
+| Ant Design Vue | 3.2.11 | 企业级 UI 设计语言 |
+| EleAdmin Pro | 1.10.1 | 企业级组件库 |
+
+### 功能组件
+- **TinyMCE** - 富文本编辑器,支持图片/视频上传
+- **ECharts** - 数据可视化图表库
+- **CropperJS** - 图片裁剪组件
+- **ExcelJS** - Excel 文件处理
+- **Ali OSS** - 阿里云对象存储
+
+## 📋 环境要求
+
+### 基础环境
+- 🟢 **Node.js 16+**
+- 📦 **npm 8+ / yarn 1.22+**
+- 🌐 **现代浏览器**(Chrome 63+、Firefox、Safari、Edge)
+
+### 开发工具
+- **推荐**:VS Code / WebStorm
+- **插件**:Vetur / Volar(Vue 3 支持)
+
+## 🚀 快速开始
+
+### 1. 克隆项目
+```bash
+git clone https://github.com/websoft-top/mp-vue.git
+cd mp-vue
+```
+
+### 2. 安装依赖
+```bash
+# 使用 npm
+npm install
+
+# 或使用 yarn
+yarn install
+```
+
+### 3. 配置环境变量
+```bash
+# 复制环境变量示例文件
+cp .env.example .env
+
+# 编辑 .env 文件,填入您的配置信息
+# 注意:请不要将 .env 文件提交到版本控制系统
+```
+
+### 4. 启动开发服务器
+```bash
+# 开发模式
+npm run dev
+
+# 或
+yarn dev
+```
+
+访问 `http://localhost:3000` 即可看到管理后台。
+
+### 5. 构建生产版本
+```bash
+# 生产构建
+npm run build
+
+# 预览构建结果
+npm run serve
+```
+
+## 🧩 常见问题
+
+### ERROR: EMFILE: too many open files, watch
+
+- 项目已在 `nuxt.config.ts` 中配置忽略 `node_modules/.nuxt/.output` 等目录以减少监听数量。
+- 若仍出现该错误,可在启动前临时提高文件句柄上限后重试:`ulimit -n 8192 && npm run dev`。
+
+## ⚙️ 环境变量配置
+
+项目使用环境变量来管理敏感信息和配置。请按照以下步骤配置:
+
+### 必需配置
+```bash
+# API 配置
+VITE_API_URL=https://your-api.com/api # 后端 API 地址
+VITE_SERVER_API_URL=https://your-server.com/api # 服务端 API 地址
+VITE_FILE_SERVER=https://your-file-server.com # 文件服务器地址
+
+# 应用配置
+VITE_APP_SECRET=your_app_secret # 应用密钥
+VITE_TENANT_ID=your_tenant_id # 租户 ID
+```
+
+### 可选配置
+```bash
+# 高德地图 (如需使用地图功能)
+VITE_MAP_KEY=your_map_key # 高德地图 Key
+VITE_MAP_CODE=your_map_security_code # 高德地图安全密钥
+
+# WebSoftAdmin 授权 (商业版功能)
+VITE_LICENSE_CODE=your_license_code # 授权码
+```
+
+### 获取配置信息
+- **高德地图密钥**:访问 [高德开放平台](https://lbs.amap.com/) 申请
+- **WebSoftAdmin 授权码**:联系 [官方网站](https://websoft.top/) 获取
+- **其他 API 配置**:根据您的后端服务配置
+
+## 🎯 核心功能
+
+### 📝 内容管理系统
+- **文章管理**:支持富文本编辑、图片/视频上传
+- **一键排版**:智能文章格式优化,包含10种专业排版样式
+- **媒体库**:图片/视频文件管理,支持分组和搜索
+- **首行缩进**:中文段落格式智能切换
+
+### 🛒 商城管理
+- **商品管理**:商品信息编辑、规格设置
+- **订单管理**:订单流程跟踪
+- **库存管理**:商品库存监控
+
+### 👥 用户权限
+- **用户管理**:用户信息维护
+- **角色权限**:基于角色的访问控制
+- **菜单管理**:动态菜单配置
+
+## 🎨 富文本编辑器特色功能
+
+### 📸 媒体上传
+- **图片上传**:支持拖拽、粘贴、文件选择
+- **视频上传**:支持多种视频格式
+- **媒体库**:统一的媒体文件管理
+- **OSS 存储**:阿里云对象存储集成
+
+### ✨ 智能排版
+- **一键排版**:10种专业排版优化
+- **首行缩进**:中文段落格式切换
+- **样式优化**:标题、段落、列表、表格等元素美化
+- **响应式**:适配不同屏幕尺寸
+
+## 🏗️ 项目结构
+
+```
+src/
+├── components/ # 公共组件
+├── views/ # 页面组件
+│ ├── cms/ # 内容管理
+│ ├── shop/ # 商城管理
+│ └── system/ # 系统管理
+├── router/ # 路由配置
+├── store/ # 状态管理
+├── utils/ # 工具函数
+└── assets/ # 静态资源
+```
diff --git a/app/api/app/apikey/index.ts b/app/api/app/apikey/index.ts
new file mode 100644
index 0000000..a9c9853
--- /dev/null
+++ b/app/api/app/apikey/index.ts
@@ -0,0 +1,72 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppApiKey, AppApiKeyParam } from './model'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/apikey'
+
+/** 分页查询 API Key - 直接用基础路径,后端 @GetMapping 支持分页参数 */
+export async function pageAppApiKey(params: AppApiKeyParam) {
+ const res = await request.get>>(BASE, { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询全部 API Key */
+export async function listAppApiKey(params?: AppApiKeyParam) {
+ const res = await request.get>(BASE, { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 创建 API Key */
+export async function createAppApiKey(data: {
+ name: string
+ expireTime?: string
+ scopes?: string
+ remark?: string
+}) {
+ const res = await request.post>(BASE, data)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 更新 API Key 状态 */
+export async function updateAppApiKeyStatus(id: number, status: number) {
+ const res = await request.put>(BASE + `/${id}/status?status=${status}`)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除 API Key */
+export async function removeAppApiKey(id: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取 API Key 速率限制 */
+export async function getApiRateLimits() {
+ const res = await request.get>(BASE + '/rate-limits')
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取 API Key 统计 */
+export async function getAppApiKeyStats() {
+ const res = await request.get>(BASE + '/stats')
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/apikey/model.ts b/app/api/app/apikey/model.ts
new file mode 100644
index 0000000..e236057
--- /dev/null
+++ b/app/api/app/apikey/model.ts
@@ -0,0 +1,31 @@
+/** API Key 数据模型 */
+export interface AppApiKey {
+ id: number
+ name: string
+ apiKey: string
+ keyPrefix: string
+ status: number // 0=正常, 1=禁用
+ scopes?: string
+ expireTime?: string
+ lastUsedAt?: string
+ usageCount: number
+ remark?: string
+ createTime: string
+ updateTime: string
+}
+
+/** API Key 查询参数 */
+export interface AppApiKeyParam {
+ page?: number
+ limit?: number // 与后端 BaseParam 一致
+ name?: string
+ status?: number
+}
+
+/** 分页结果 - 后端 MyBatis-Plus 返回 records */
+export interface AppApiKeyPageResult {
+ records: AppApiKey[]
+ total: number
+ size: number
+ current: number
+}
diff --git a/app/api/app/appConfig/index.ts b/app/api/app/appConfig/index.ts
new file mode 100644
index 0000000..1095cd7
--- /dev/null
+++ b/app/api/app/appConfig/index.ts
@@ -0,0 +1,120 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { AppConfig, AppConfigParam, BatchSaveRequest } from './model';
+import {APP_API_URL} from '@/config/setting';
+
+/**
+ * 分页查询应用配置
+ */
+export async function pageAppConfig(params: AppConfigParam) {
+ const res = await request.get>>(
+ APP_API_URL + '/app-config/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 获取应用配置列表
+ */
+export async function listAppConfig(params?: AppConfigParam) {
+ const res = await request.get>(
+ APP_API_URL + '/app-config',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据应用ID获取配置映射
+ */
+export async function getConfigsMap(productId: number) {
+ const res = await request.get>>(
+ APP_API_URL + `/app/app-config/map/${productId}`
+ );
+ if (res.data.code === 0) {
+ return res.data.data || {};
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 获取单个配置值
+ */
+export async function getConfigValue(productId: number, configKey: string) {
+ const res = await request.get>(
+ APP_API_URL + '/app-config/value',
+ {
+ params: { productId, configKey }
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 保存配置
+ */
+export async function saveAppConfig(data: AppConfig) {
+ const res = await request.post>(
+ APP_API_URL + '/app-config',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量保存配置
+ */
+export async function batchSaveAppConfig(data: BatchSaveRequest) {
+ const res = await request.post>(
+ APP_API_URL + '/app-config/batch',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 更新配置
+ */
+export async function updateAppConfig(data: AppConfig) {
+ const res = await request.put>(
+ APP_API_URL + '/app-config',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除配置
+ */
+export async function deleteAppConfig(configId: number) {
+ const res = await request.delete>(
+ APP_API_URL + `/app-config/${configId}`
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/app/appConfig/model/index.ts b/app/api/app/appConfig/model/index.ts
new file mode 100644
index 0000000..0c19cb5
--- /dev/null
+++ b/app/api/app/appConfig/model/index.ts
@@ -0,0 +1,66 @@
+import type { PageParam } from '@/api';
+import type { PageResult } from '@/api';
+
+/**
+ * 应用配置
+ */
+export interface AppConfig {
+ configId?: number;
+ productId?: number;
+ configKey?: string;
+ configValue?: string;
+ configType?: string;
+ isEncrypted?: number;
+ isSecret?: number;
+ description?: string;
+ sortNumber?: number;
+ tenantId?: number;
+ createdTime?: string;
+ updatedTime?: string;
+ deleted?: number;
+}
+
+/**
+ * 应用配置查询参数
+ */
+export interface AppConfigParam extends PageParam {
+ configId?: number;
+ productId?: number;
+ configKey?: string;
+ configType?: string;
+ isSecret?: number;
+}
+
+/**
+ * 批量保存请求
+ */
+export interface BatchSaveRequest {
+ productId: number;
+ configs: AppConfig[];
+}
+
+/**
+ * 配置类型定义
+ */
+export interface ConfigType {
+ key: string;
+ name: string;
+ icon: string;
+ description: string;
+ configs: ConfigField[];
+}
+
+/**
+ * 配置字段定义
+ */
+export interface ConfigField {
+ key: string;
+ label: string;
+ type: 'input' | 'textarea' | 'number' | 'select' | 'switch' | 'password' | 'json';
+ required?: boolean;
+ placeholder?: string;
+ options?: Array<{ label: string; value: any }>;
+ defaultValue?: any;
+ description?: string;
+ secret?: boolean;
+}
diff --git a/app/api/app/appCredential/index.ts b/app/api/app/appCredential/index.ts
new file mode 100644
index 0000000..bf1f4bd
--- /dev/null
+++ b/app/api/app/appCredential/index.ts
@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppCredential, AppCredentialParam } from './model'
+import {APP_API_URL} from '@/config/setting'
+
+const BASE = APP_API_URL + '/app-credential'
+
+/** 分页查询凭证 */
+export async function pageAppCredential(params: AppCredentialParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询全部凭证 */
+export async function listAppCredential(params?: AppCredentialParam) {
+ const res = await request.get>(BASE, { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 根据ID查询凭证 */
+export async function getAppCredential(id: number) {
+ const res = await request.get>(BASE + '/' + id)
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 创建凭证(自动生成 AppID/AppSecret,仅此次返回明文) */
+export async function createAppCredential(data: AppCredential) {
+ const res = await request.post>(BASE, data)
+ if (res.data.code === 0) return { message: res.data.message, data: res.data.data }
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 修改凭证信息(不含密钥) */
+export async function updateAppCredential(data: AppCredential) {
+ const res = await request.put>(BASE, data)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 重置 AppSecret */
+export async function resetAppCredentialSecret(id: number) {
+ const res = await request.post>(BASE + '/resetSecret/' + id)
+ if (res.data.code === 0) return { message: res.data.message, data: res.data.data }
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 禁用/启用凭证 */
+export async function updateAppCredentialStatus(id: number, status: number) {
+ const res = await request.put>(BASE + `/status/${id}/${status}`)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除凭证 */
+export async function removeAppCredential(id: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/appCredential/model.ts b/app/api/app/appCredential/model.ts
new file mode 100644
index 0000000..d9a0d1a
--- /dev/null
+++ b/app/api/app/appCredential/model.ts
@@ -0,0 +1,36 @@
+import type { PageParam } from '@/api'
+
+export interface AppCredential {
+ id?: number
+ /** 关联应用ID(AppProduct.productId 的字符串形式),用于显示为 AppID */
+ appId?: string
+ /** OAuth Client ID(格式:app_xxxxxxxxxxxx) */
+ clientId?: string
+ /** OAuth Client Secret */
+ clientSecret?: string
+ name?: string
+ type?: string
+ scopes?: string
+ expireTime?: string
+ lastUsedAt?: string
+ remark?: string
+ sortNumber?: number
+ status?: number
+ deleted?: number
+ userId?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+ // 关联字段
+ productName?: string
+ productCode?: string
+ icon?: string
+}
+
+export interface AppCredentialParam extends PageParam {
+ appId?: string
+ name?: string
+ clientId?: string
+ type?: string
+ status?: number
+}
diff --git a/app/api/app/appEvent/index.ts b/app/api/app/appEvent/index.ts
new file mode 100644
index 0000000..a3006a9
--- /dev/null
+++ b/app/api/app/appEvent/index.ts
@@ -0,0 +1,43 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppEvent, AppEventParam } from './model'
+import {APP_API_URL} from '@/config/setting'
+
+const BASE = APP_API_URL + '/app-event'
+
+/** 分页查询动态 */
+export async function pageAppEvent(params: AppEventParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询全部动态 */
+export async function listAppEvent(params?: AppEventParam) {
+ const res = await request.get>(BASE, { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取应用最新一条动态 */
+export async function getLatestAppEvent(productId: number) {
+ const res = await request.get>(BASE + '/latest/' + productId)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 记录一条操作动态(静默:失败不抛出) */
+export async function addAppEvent(data: AppEvent): Promise {
+ try {
+ await request.post>(BASE, data)
+ } catch {
+ // 动态记录失败不影响主流程,静默处理
+ }
+}
+
+/** 清空应用所有动态 */
+export async function clearAppEvents(productId: number) {
+ const res = await request.delete>(BASE + '/clear/' + productId)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/appEvent/model.ts b/app/api/app/appEvent/model.ts
new file mode 100644
index 0000000..e5d70ee
--- /dev/null
+++ b/app/api/app/appEvent/model.ts
@@ -0,0 +1,26 @@
+import type { PageParam } from '@/api'
+
+export interface AppEvent {
+ id?: number
+ appId?: number
+ eventType?: string
+ title?: string
+ content?: string
+ operatorId?: number
+ operator?: string
+ refId?: number
+ refType?: string
+ extra?: string
+ sortNumber?: number
+ status?: number
+ userId?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+}
+
+export interface AppEventParam extends PageParam {
+ appId?: number
+ eventType?: string
+ operatorId?: number
+}
diff --git a/app/api/app/appProduct/index.ts b/app/api/app/appProduct/index.ts
new file mode 100644
index 0000000..f710e6a
--- /dev/null
+++ b/app/api/app/appProduct/index.ts
@@ -0,0 +1,222 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppProduct, AppProductParam } from './model'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/product'
+
+/** 统一成功码判断:兼容 code=0(旧约定)和 code=200(Spring Boot 默认) */
+const ok = (code: number) => code === 0 || code === 200
+
+// ============ 基础 CRUD ============
+
+/** 分页查询应用产品 */
+export async function pageAppProduct(params: AppProductParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 分页查询应用产品(别名,兼容 cmsWebsite 接口) */
+export const page = pageAppProduct
+
+/** 分页查询应用产品(别名,兼容旧代码) */
+export async function pageAppProductAll(params: AppProductParam) {
+ return pageAppProduct(params)
+}
+
+/** 获取应用产品详情 */
+export async function getAppProduct(productId: number) {
+ const res = await request.get>(BASE + '/detail/' + productId)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取应用产品详情ByCode */
+export async function getAppProductByCode(code: string) {
+ const res = await request.get>(BASE + '/info/' + code)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 新增应用产品 */
+export async function addAppProduct(data: Partial) {
+ const res = await request.post>(BASE + '/create', data)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 修改应用产品 */
+export async function updateAppProduct(data: Partial) {
+ const res = await request.put>(BASE + '/update', data)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除应用产品 */
+export async function removeAppProduct(id: number) {
+ const res = await request.delete>(BASE + '/delete/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 开发者相关 ============
+
+/** 获取开发者应用列表 */
+export async function getDeveloperApps(params?: AppProductParam) {
+ const res = await request.get>(BASE + '/list', { params })
+ if (ok(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 分页查询我的应用(创建者) */
+export async function getMyApps(params: { page?: number; limit?: number }) {
+ const res = await request.get>>(BASE + '/my/page', { params })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 分页查询我参与的应用 */
+export async function getJoinedApps(params: { page?: number; limit?: number }) {
+ const res = await request.get>>(BASE + '/joined/page', { params })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 开发者提交审核 */
+export async function submitReview(id: number) {
+ const res = await request.post>(BASE + '/submit/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 提交发布审核(别名) */
+export const submitPublishReview = submitReview
+
+/** 撤回审核申请 */
+export async function withdrawPublishReview(id: number) {
+ const res = await request.post>(BASE + '/withdraw/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 开发者下架应用 */
+export async function deprecated(id: number) {
+ const res = await request.post>(BASE + '/unpublish/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 下架应用(别名) */
+export const unpublishAppProduct = deprecated
+
+// ============ 市场相关 ============
+
+/** 获取市场应用列表 */
+export async function getMarketApps(params: AppProductParam) {
+ const res = await request.get>>(BASE + '/market/page', { params })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取市场应用详情 */
+export async function getMarketApp(productId: number) {
+ const res = await request.get>(BASE + '/detail/' + productId)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 管理后台 ============
+
+/** 获取待审核列表 */
+export async function getPendingReviewApps(params: AppProductParam) {
+ const res = await request.get>>(BASE + '/page', {
+ params: { ...params, publishStatus: 'pending_review' },
+ })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 分页查询发布审核列表(别名) */
+export const pagePublishReviews = getPendingReviewApps
+
+/** 审核通过 */
+export async function approve(id: number) {
+ const res = await request.post>(BASE + '/approve/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 审核通过(别名) */
+export const approvePublishReview = approve
+
+/** 审核拒绝 */
+export async function reject(id: number, reason: string) {
+ const res = await request.post>(BASE + '/reject/' + id, null, { params: { reason } })
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 审核拒绝(别名,接收对象参数) */
+export async function rejectPublishReview(params: { productId: number; rejectReason: string }) {
+ return reject(params.productId, params.rejectReason)
+}
+
+// ============ 其他 ============
+
+/** 按用户ID列表批量统计应用数量 */
+export interface UserAppStats {
+ userId: number
+ totalCount: number
+ publishedCount: number
+}
+
+export async function getUserAppStats(userIds: number[]) {
+ const res = await request.post>(BASE + '/user-stats', userIds)
+ if (ok(res.data.code) && res.data.data) return res.data.data
+ return []
+}
+
+/** 获取全部应用(下拉框用) */
+export async function getAllAppProduct() {
+ const res = await request.get>(BASE + '/all')
+ if (ok(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取当前用户可访问的应用列表(带角色信息,用于权限过滤) */
+export async function getMyAccessibleApps() {
+ const res = await request.get>(BASE + '/accessible')
+ if (ok(res.data.code) && res.data.data) return res.data.data
+ return []
+}
+
+/** 更新访问量 */
+export async function updateClicks(id: number) {
+ const res = await request.post>(BASE + '/view/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 记录应用访问(更新最近访问时间) */
+export async function recordVisit(id: number) {
+ const res = await request.post>(BASE + '/visit/' + id)
+ // 静默失败,不影响主流程
+ if (!ok(res.data.code)) {
+ console.warn('记录访问失败', res.data.message)
+ }
+}
+
+/** 收藏/取消收藏 */
+export async function toggleLike(id: number) {
+ const res = await request.post>(BASE + '/like/' + id)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 重新生成密钥 */
+export async function regenerateSecret(id: number) {
+ const res = await request.post>(BASE + '/regenerateSecret/' + id)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/appProduct/model.ts b/app/api/app/appProduct/model.ts
new file mode 100644
index 0000000..860f1d9
--- /dev/null
+++ b/app/api/app/appProduct/model.ts
@@ -0,0 +1,187 @@
+import type { PageParam } from '@/api'
+import type { AppRole } from '../appUser/model'
+
+export interface AppProduct {
+ productId?: number
+ productName?: string
+ productCode?: string
+ productSecret?: string
+ appType?: number
+ categoryId?: number
+ industryParent?: string
+ industryChild?: string
+ logo?: string
+ icon?: string
+ qrcode?: string
+ screenshots?: string
+ description?: string
+ content?: string
+ keywords?: string
+ domain?: string
+ prefix?: string
+ packageName?: string
+ homeUrl?: string
+ adminUrl?: string
+ apiUrl?: string
+ downloadUrl?: string
+ version?: string
+ edition?: string
+ minVersion?: string
+ priceType?: string
+ price?: number
+ linePrice?: number
+ renewPrice?: number
+ deliveryMethod?: number
+ chargingMethod?: number
+ subscriptionPeriod?: string
+ publishStatus?: string
+ publishTime?: string
+ reviewTime?: string
+ reviewerId?: number
+ rejectReason?: string
+ clicks?: number
+ installs?: number
+ downloads?: number
+ rating?: number
+ likes?: number
+ developer?: string
+ developerPhone?: string
+ developerEmail?: string
+ recommend?: number
+ official?: number
+ market?: number
+ showIndex?: number
+ searchEnabled?: number
+ templateId?: number
+ style?: string
+ config?: string
+ themeColor?: string
+ lang?: string
+ icpNo?: string
+ policeNo?: string
+ status?: number
+ statusText?: string
+ running?: number
+ expirationTime?: string
+ sortNumber?: number
+ deleted?: number
+ userId?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+ /** 当前用户在该应用中的角色(仅 joined/accessible 查询返回) */
+ myRole?: AppRole
+}
+
+export interface AppProductParam extends PageParam {
+ appType?: number
+ categoryId?: number
+ publishStatus?: string
+ status?: number
+ keywords?: string
+ developer?: string
+ market?: number
+ official?: number
+ recommend?: number
+ userId?: number
+ tenantId?: number
+}
+
+// App Type 枚举
+export const APP_TYPE = {
+ WEBSITE: 10,
+ WECHAT_MP: 20,
+ DOUYIN_MP: 30,
+ BAIDU_MP: 40,
+ ALIPAY_MP: 50,
+ ANDROID: 60,
+ IOS: 70,
+ MACOS: 80,
+ WINDOWS: 90,
+ PLUGIN: 100,
+} as const
+
+export type APP_TYPE_KEY = keyof typeof APP_TYPE
+
+export const APP_TYPE_NAME: Record = {
+ [APP_TYPE.WEBSITE]: '网站',
+ [APP_TYPE.WECHAT_MP]: '微信小程序',
+ [APP_TYPE.DOUYIN_MP]: '抖音小程序',
+ [APP_TYPE.BAIDU_MP]: '百度小程序',
+ [APP_TYPE.ALIPAY_MP]: '支付宝小程序',
+ [APP_TYPE.ANDROID]: 'Android APP',
+ [APP_TYPE.IOS]: 'iOS APP',
+ [APP_TYPE.MACOS]: 'macOS 应用',
+ [APP_TYPE.WINDOWS]: 'Windows 应用',
+ [APP_TYPE.PLUGIN]: '插件',
+}
+
+// Publish Status 枚举
+export const PUBLISH_STATUS = {
+ DEVELOPING: 'developing',
+ PENDING_REVIEW: 'pending_review',
+ PUBLISHED: 'published',
+ REJECTED: 'rejected',
+ DEPRECATED: 'deprecated',
+} as const
+
+export type PUBLISH_STATUS_KEY = keyof typeof PUBLISH_STATUS
+
+export const PUBLISH_STATUS_NAME: Record = {
+ [PUBLISH_STATUS.DEVELOPING]: '开发中',
+ [PUBLISH_STATUS.PENDING_REVIEW]: '待审核',
+ [PUBLISH_STATUS.PUBLISHED]: '已上架',
+ [PUBLISH_STATUS.REJECTED]: '审核未通过',
+ [PUBLISH_STATUS.DEPRECATED]: '已下架',
+}
+
+// Price Type 枚举
+export const PRICE_TYPE = {
+ FREE: 'free',
+ ONE_TIME: 'one_time',
+ SUBSCRIPTION: 'subscription',
+} as const
+
+export type PRICE_TYPE_KEY = keyof typeof PRICE_TYPE
+
+export const PRICE_TYPE_NAME: Record = {
+ [PRICE_TYPE.FREE]: '免费',
+ [PRICE_TYPE.ONE_TIME]: '一次性',
+ [PRICE_TYPE.SUBSCRIPTION]: '订阅',
+}
+
+// Edition 枚举
+export const EDITION = {
+ STANDARD: 'standard',
+ PROFESSIONAL: 'professional',
+ PERPETUAL: 'perpetual',
+} as const
+
+export type EDITION_KEY = keyof typeof EDITION
+
+export const EDITION_NAME: Record = {
+ [EDITION.STANDARD]: '标准版',
+ [EDITION.PROFESSIONAL]: '专业版',
+ [EDITION.PERPETUAL]: '永久授权',
+}
+
+// Status 枚举
+export const STATUS = {
+ INACTIVE: 0,
+ RUNNING: 1,
+ MAINTENANCE: 2,
+ CLOSED: 3,
+ ARREARS: 4,
+ VIOLATION: 5,
+} as const
+
+export type STATUS_KEY = keyof typeof STATUS
+
+export const STATUS_NAME: Record = {
+ [STATUS.INACTIVE]: '未开通',
+ [STATUS.RUNNING]: '运行中',
+ [STATUS.MAINTENANCE]: '维护中',
+ [STATUS.CLOSED]: '已关闭',
+ [STATUS.ARREARS]: '已欠费',
+ [STATUS.VIOLATION]: '违规停机',
+}
diff --git a/app/api/app/appResource/index.ts b/app/api/app/appResource/index.ts
new file mode 100644
index 0000000..d7c7162
--- /dev/null
+++ b/app/api/app/appResource/index.ts
@@ -0,0 +1,158 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppResource, AppResourceParam } from './model'
+import {APP_API_URL} from '@/config/setting'
+
+const BASE = APP_API_URL + '/developer-resource'
+
+/** 分页查询资源 */
+export async function pageAppResource(params: AppResourceParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询资源列表(不分页) */
+export async function listAppResource(params?: AppResourceParam) {
+ const res = await request.get>(BASE, { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取资源详情 */
+export async function getAppResource(resourceId: number) {
+ const res = await request.get>(BASE + '/' + resourceId)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 统计各类型资源数量 */
+export async function statsAppResource(): Promise> {
+ const res = await request.get>>(BASE + '/stats')
+ if (res.data.code === 0) return res.data.data ?? {}
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 新增资源 */
+export async function addAppResource(data: AppResource) {
+ const res = await request.post>(BASE, data)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 修改资源 */
+export async function updateAppResource(data: AppResource) {
+ const res = await request.put>(BASE, data)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除资源 */
+export async function removeAppResource(resourceId: number, code?: string) {
+ const res = await request.delete>(BASE + '/' + resourceId, code ? { data: { code } } : {})
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 批量删除资源 */
+export async function removeBatchAppResource(ids: number[]) {
+ const res = await request.delete>(BASE + '/batch', { data: ids })
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 测试数据库连接 */
+export async function testConnection(params: { host: string; port?: number; dbType?: string; username: string; password: string; dbName?: string }) {
+ const res = await request.post>(BASE + '/test-connection', params)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 测试 SSH 连接 */
+export async function testSshConnection(params: { host: string; port?: number; username: string; password: string }) {
+ const res = await request.post>(BASE + '/test-ssh', params)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 重试创建数据库 */
+export async function retryCreateDatabase(resourceId: number) {
+ const res = await request.post>(BASE + '/retry-create-database/' + resourceId)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 重置数据库密码 */
+export async function resetDatabasePassword(resourceId: number) {
+ const res = await request.post>(BASE + '/reset-password/' + resourceId)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 刷新存储桶信息 */
+export async function refreshStorage(resourceId: number) {
+ const res = await request.post>(BASE + '/refresh-storage/' + resourceId)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 检查域名 DNS 状态 */
+export async function checkDomainDns(resourceId: number) {
+ const res = await request.post>(BASE + '/check-dns/' + resourceId)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 生成 Nginx 配置 */
+export async function generateNginxConfig(resourceId: number) {
+ const res = await request.get>(BASE + '/nginx-config/' + resourceId)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取域名统计信息 */
+export async function getDomainStats() {
+ const res = await request.get>(BASE + '/domain-stats')
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 批量导入域名 */
+export async function importDomains(data: { domains: Array> }) {
+ const res = await request.post>(BASE + '/import-domains', data)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 导出域名列表 */
+export async function exportDomains(params?: AppResourceParam) {
+ const res = await request.get(BASE + '/export-domains', { params, responseType: 'blob' })
+ return res.data
+}
+
+/** 获取1Panel服务器状态 */
+export interface ServerStatus {
+ cpu: string // CPU 使用率 %
+ memory: string // 内存使用率 %
+ memoryUsed: string // 内存已用
+ memoryTotal: string // 内存总量
+ disk: string // 磁盘使用率 %
+ diskUsed: string // 磁盘已用
+ diskTotal: string // 磁盘总量
+ load: string // 系统负载
+ uptime: string // 运行时间
+ online: number // 在线状态 1=在线 0=离线
+ message?: string // 错误信息
+}
+
+export async function getServerStatus(resourceId: number): Promise {
+ const res = await request.get>(BASE + '/server-status/' + resourceId)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data!
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/appResource/model/index.ts b/app/api/app/appResource/model/index.ts
new file mode 100644
index 0000000..3ee2b9a
--- /dev/null
+++ b/app/api/app/appResource/model/index.ts
@@ -0,0 +1,156 @@
+import type { PageParam } from '@/api'
+
+/**
+ * 开发者资源
+ */
+export interface AppResource {
+ resourceId?: number
+ resourceType?: string // server | database | storage | domain | ssl
+ name?: string
+ provider?: string // tencent | aliyun | huawei | other
+
+ // 服务器
+ ip?: string
+ sshPort?: number // SSH 端口(默认22)
+ sshUsername?: string // SSH 用户名(用于远程执行命令)
+ sshPassword?: string // SSH 密码(AES加密,用于远程执行命令)
+ mysqlPort?: number // MySQL 端口(默认3306)
+ pgPort?: number // PostgreSQL 端口(默认5432)
+ panelPort?: number // 1Panel 面板端口(默认8888)
+ panelUsername?: string // 1Panel 面板用户名
+ panelPassword?: string // 1Panel 面板密码(AES加密)
+ panelPath?: string // 1Panel 面板路径前缀(如 /abc123)
+ adminUsername?: string // MySQL/PostgreSQL 管理员用户名(用于远程建库)
+ adminPassword?: string // MySQL/PostgreSQL 管理员密码(AES加密,用于远程建库)
+
+ // 数据库
+ dbType?: string // MySQL | PostgreSQL | Redis | MongoDB
+ serverResourceId?: number // 关联服务器资源ID
+ host?: string
+ port?: number
+ dbUsername?: string
+ dbPassword?: string
+
+ // 云存储
+ region?: string
+ acl?: string // public-read | private
+ usedBytes?: number
+
+ // 域名
+ domain?: string
+ registrar?: string
+ icp?: boolean
+ icpNo?: string
+ sslBound?: boolean
+ sslResourceId?: number // 关联的 SSL 证书资源ID
+ sslCertName?: string // 关联的 SSL 证书名称
+ dnsRecords?: DnsRecord[] // DNS 记录列表
+ whoisInfo?: WhoisInfo // WHOIS 信息
+ autoRenew?: boolean // 是否自动续费
+
+ // 域名健康状态
+ dnsStatus?: 'ok' | 'error' | 'pending' | 'unknown' // DNS 解析状态
+ dnsIp?: string // DNS 解析到的 IP
+ healthStatus?: 'healthy' | 'warning' | 'error' | 'unknown' // 综合健康状态
+ healthCheckAt?: string // 最后健康检查时间
+ subDomains?: AppResource[] // 子域名列表
+ parentDomainId?: number // 父域名 ID(用于子域名)
+ isWildcard?: boolean // 是否通配符域名
+ serverResourceId?: number // 关联的服务器资源ID
+ serverName?: string // 关联的服务器名称
+ nginxConfig?: string // Nginx 配置
+
+ // SSL
+ certType?: string // DV | OV | EV
+ issuer?: string
+ privateKey?: string // 私钥(AES加密存储)
+ publicKey?: string // 公钥
+ certificate?: string // 证书内容/证书文件
+ certChain?: string // 证书链(中间证书)
+ algorithm?: string // 加密算法:RSA/ECC
+ keyBits?: number // 密钥长度:2048/4096等
+
+ // Git仓库
+ gitPath?: string // Git仓库路径(如: websopy/core)
+ gitCloneUrl?: string // Git Clone URL
+ gitWebUrl?: string // Git Web访问URL(Gitea页面地址)
+ gitAccessLevel?: string // Git权限级别: read/write/admin
+
+ // 云存储凭证
+ credentialId?: number // 关联的云账号凭证ID
+
+ // 通用
+ status?: string // running | stopped | expired | pending
+ appId?: number
+ productName?: string
+ expireAt?: string
+ remark?: string
+ userId?: number
+ tenantId?: number
+ deleted?: number
+ createTime?: string
+ updateTime?: string
+
+ // ─── 协作权限字段 ────────────────────────────────────────────
+ /**
+ * 资源所有者 userId(创建者)
+ * 后端在创建时自动设置,前端只读
+ */
+ ownerUserId?: number
+
+ /**
+ * 当前访问者对该资源的权限级别(后端计算,前端只读)
+ * 0 - 无权限
+ * 1 - 基础查看(名称/IP/端口/状态,所有团队成员)
+ * 2 - 连接查看(用户名、连接方式,技术负责人)
+ * 3 - 完全权限(包含密码/私钥,仅资源Owner)
+ */
+ accessLevel?: 0 | 1 | 2 | 3
+
+ /**
+ * 敏感字段是否为脱敏占位符(后端返回 "******" 表示已脱敏)
+ */
+ isMasked?: boolean
+
+ /** 是否是资源创建者(前端根据 ownerUserId vs 当前 UserId 计算) */
+ isOwner?: boolean
+}
+
+/**
+ * DNS 记录
+ */
+export interface DnsRecord {
+ type: string // A, AAAA, CNAME, MX, TXT, NS
+ name: string
+ value: string
+ ttl?: number
+ priority?: number
+}
+
+/**
+ * WHOIS 信息
+ */
+export interface WhoisInfo {
+ registrar?: string
+ creationDate?: string
+ expirationDate?: string
+ updatedDate?: string
+ nameServers?: string[]
+ status?: string[]
+}
+
+/**
+ * 资源查询参数
+ */
+export interface AppResourceParam extends PageParam {
+ resourceId?: number
+ resourceType?: string
+ appId?: number
+ provider?: string
+ status?: string
+ userId?: number
+ tenantId?: number
+ keywords?: string
+ icp?: boolean
+ sslBound?: boolean
+}
diff --git a/app/api/app/appUser/index.ts b/app/api/app/appUser/index.ts
new file mode 100644
index 0000000..cbac12a
--- /dev/null
+++ b/app/api/app/appUser/index.ts
@@ -0,0 +1,110 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppUser, AppUserParam, CheckAccessResult } from './model'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/app-user'
+
+type InviteApiItem = AppUser & {
+ appName?: string
+ app_name?: string
+ product_name?: string
+ appIcon?: string
+ inviterName?: string
+ inviter_name?: string
+ productCode?: string
+ product_code?: string
+}
+
+function normalizeInvite(invite: InviteApiItem): AppUser {
+ return {
+ ...invite,
+ productName: invite.productName || invite.appName || invite.product_name || invite.app_name,
+ productCode: invite.productCode || invite.product_code,
+ icon: invite.icon || invite.appIcon || invite.avatar,
+ username: invite.username || invite.inviterName || invite.inviter_name || invite.nickname
+ }
+}
+
+/** 分页查询成员 */
+export async function pageAppUser(params: AppUserParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询全部成员 */
+export async function listAppUser(params?: AppUserParam) {
+ const res = await request.get>(BASE, { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 邀请成员(userId 或 phone 二选一) */
+export async function inviteAppUser(data: { productId: number; userId?: number; phone?: string; role?: string }) {
+ const res = await request.post>(BASE + '/invite', data)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 修改成员角色 */
+export async function updateAppUserRole(id: number, role: string) {
+ const res = await request.put>(BASE + `/role/${id}/${role}`)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除成员 */
+export async function removeAppUser(id: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 邀请确认相关 ============
+
+/** 查询当前用户的待确认邀请列表 */
+export async function listPendingInvites() {
+ const res = await request.get>(BASE + '/invites/pending')
+ const { code, data, message } = res.data
+ if (code === 0 || code === 200) {
+ return Array.isArray(data) ? data.map(normalizeInvite) : []
+ }
+ return Promise.reject(new Error(message))
+}
+
+/** 统计当前用户的待确认邀请数量 */
+export async function countPendingInvites() {
+ const res = await request.get>(BASE + '/invites/pending/count')
+ const { code, data, message } = res.data
+ if ((code === 0 || code === 200) && data) return data.count
+ return Promise.reject(new Error(message))
+}
+
+/** 接受邀请 */
+export async function acceptInvite(inviteId: number) {
+ const res = await request.post>(BASE + `/invites/${inviteId}/accept`)
+ if (res.data.code === 0 || res.data.code === 200) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 拒绝邀请 */
+export async function rejectInvite(inviteId: number) {
+ const res = await request.post>(BASE + `/invites/${inviteId}/reject`)
+ if (res.data.code === 0 || res.data.code === 200) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 权限相关 ============
+
+/**
+ * 检查当前用户是否有开发者中心访问权限
+ * 返回:accessible(是否有权限)、isPlatformDeveloper(是否平台级开发者)、apps(可访问应用列表)
+ */
+export async function checkAppAccess() {
+ const res = await request.get>(BASE + '/check-access')
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ // 兼容 code=200
+ if (res.data.code === 200 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/appUser/model.ts b/app/api/app/appUser/model.ts
new file mode 100644
index 0000000..00776de
--- /dev/null
+++ b/app/api/app/appUser/model.ts
@@ -0,0 +1,54 @@
+import type { PageParam } from '@/api'
+
+/** 应用成员角色 */
+export type AppRole = 'owner' | 'admin' | 'developer' | 'viewer'
+
+export interface AppUser {
+ id?: number
+ appId?: number
+ userId?: number
+ username?: string
+ avatar?: string
+ role?: AppRole
+ inviteBy?: number
+ inviteTime?: string
+ inviteExpireTime?: string
+ sortNumber?: number
+ status?: number
+ inviteStatus?: number // 0-正常(直接加入), 1-待确认, 2-已拒绝
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+ // 关联字段(来自 sys_user)
+ nickname?: string
+ userAvatar?: string
+ phone?: string
+ // 关联字段(来自 app_product)
+ productName?: string
+ productCode?: string
+ icon?: string
+}
+
+export interface AppUserParam extends PageParam {
+ appId?: number
+ userId?: number
+ role?: string
+ status?: number
+ username?: string
+}
+
+/** 用户在某应用中的权限信息(check-access / accessible 接口返回) */
+export interface AppPermissionInfo {
+ appId: number
+ productName: string
+ productCode?: string
+ icon?: string
+ role: AppRole
+}
+
+/** check-access 接口返回 */
+export interface CheckAccessResult {
+ accessible: boolean
+ isPlatformDeveloper: boolean
+ apps?: AppPermissionInfo[]
+}
diff --git a/app/api/app/appVersion/index.ts b/app/api/app/appVersion/index.ts
new file mode 100644
index 0000000..50bc51f
--- /dev/null
+++ b/app/api/app/appVersion/index.ts
@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppVersion, AppVersionParam } from './model'
+import {APP_API_URL} from '@/config/setting'
+
+const BASE = APP_API_URL + '/app-version'
+
+/** 分页查询版本 */
+export async function pageAppVersion(params: AppVersionParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询全部版本 */
+export async function listAppVersion(params?: AppVersionParam) {
+ const res = await request.get>(BASE, { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取当前版本 */
+export async function getCurrentVersion(productId: number) {
+ const res = await request.get>(BASE + '/current/' + productId)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 新增版本 */
+export async function addAppVersion(data: AppVersion) {
+ const res = await request.post>(BASE, data)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 修改版本 */
+export async function updateAppVersion(data: AppVersion) {
+ const res = await request.put>(BASE, data)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 发布版本 */
+export async function publishAppVersion(id: number) {
+ const res = await request.post>(BASE + '/publish/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 回滚到指定版本 */
+export async function rollbackAppVersion(id: number) {
+ const res = await request.post>(BASE + '/rollback/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除版本 */
+export async function removeAppVersion(id: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/appVersion/model.ts b/app/api/app/appVersion/model.ts
new file mode 100644
index 0000000..5fd7d4d
--- /dev/null
+++ b/app/api/app/appVersion/model.ts
@@ -0,0 +1,32 @@
+import type { PageParam } from '@/api'
+
+export interface AppVersion {
+ id?: number
+ appId?: number
+ versionNo?: string
+ versionName?: string
+ changelog?: string
+ packageUrl?: string
+ packageSize?: number
+ packageHash?: string
+ env?: string
+ status?: number
+ isCurrent?: boolean
+ publishBy?: number
+ publishTime?: string
+ remark?: string
+ sortNumber?: number
+ userId?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+ // 关联字段
+ productName?: string
+}
+
+export interface AppVersionParam extends PageParam {
+ appId?: number
+ env?: string
+ status?: number
+ isCurrent?: boolean
+}
diff --git a/app/api/app/article/index.ts b/app/api/app/article/index.ts
new file mode 100644
index 0000000..7406735
--- /dev/null
+++ b/app/api/app/article/index.ts
@@ -0,0 +1,58 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppArticle, AppArticleParam, AppArticleCount } from './model'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/article'
+
+function isSuccess(code?: number) {
+ return code === 0 || code === 200
+}
+
+export async function pageAppArticle(params: AppArticleParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (isSuccess(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function listAppArticle(params?: AppArticleParam) {
+ const res = await request.get>(BASE, { params })
+ if (isSuccess(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function addAppArticle(data: AppArticle) {
+ const res = await request.post>(BASE, data)
+ if (isSuccess(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function updateAppArticle(data: AppArticle) {
+ const res = await request.put>(BASE, data)
+ if (isSuccess(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function removeAppArticle(id?: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (isSuccess(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function getAppArticle(id: number) {
+ const res = await request.get>(BASE + '/' + id)
+ if (isSuccess(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function getAppArticleByCode(code: string) {
+ const res = await request.get>(BASE + '/getByCode/' + code)
+ if (isSuccess(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function getAppArticleCount(params: AppArticleParam) {
+ const res = await request.get>(BASE + '/data', { params })
+ if (isSuccess(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/article/model/index.ts b/app/api/app/article/model/index.ts
new file mode 100644
index 0000000..6ef7207
--- /dev/null
+++ b/app/api/app/article/model/index.ts
@@ -0,0 +1,43 @@
+import type { PageParam } from '@/api'
+
+export interface AppArticle {
+ articleId?: number
+ title?: string
+ type?: number
+ model?: string
+ code?: string
+ categoryId?: number
+ categoryName?: string
+ parentId?: number
+ image?: string
+ source?: string
+ overview?: string
+ content?: string
+ actualViews?: number
+ likes?: number
+ userId?: number
+ author?: string
+ recommend?: number
+ sortNumber?: number
+ status?: number
+ deleted?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+}
+
+export interface AppArticleParam extends PageParam {
+ articleId?: number
+ model?: string
+ status?: number
+ categoryId?: number
+ recommend?: number
+ keywords?: string
+ tenantId?: number
+}
+
+export interface AppArticleCount {
+ totalNum?: number
+ publishedNum?: number
+ recommendNum?: number
+}
diff --git a/app/api/app/articleCategory/index.ts b/app/api/app/articleCategory/index.ts
new file mode 100644
index 0000000..5d3252f
--- /dev/null
+++ b/app/api/app/articleCategory/index.ts
@@ -0,0 +1,46 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppArticleCategory, AppArticleCategoryParam } from './model'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/article-category'
+
+function isSuccess(code?: number) {
+ return code === 0 || code === 200
+}
+
+export async function pageAppArticleCategory(params: AppArticleCategoryParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (isSuccess(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function listAppArticleCategory(params?: AppArticleCategoryParam) {
+ const res = await request.get>(BASE, { params })
+ if (isSuccess(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function addAppArticleCategory(data: AppArticleCategory) {
+ const res = await request.post>(BASE, data)
+ if (isSuccess(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function updateAppArticleCategory(data: AppArticleCategory) {
+ const res = await request.put>(BASE, data)
+ if (isSuccess(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function removeAppArticleCategory(id?: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (isSuccess(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+export async function getAppArticleCategory(id: number) {
+ const res = await request.get>(BASE + '/' + id)
+ if (isSuccess(res.data.code) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/articleCategory/model/index.ts b/app/api/app/articleCategory/model/index.ts
new file mode 100644
index 0000000..756c484
--- /dev/null
+++ b/app/api/app/articleCategory/model/index.ts
@@ -0,0 +1,33 @@
+import type { PageParam } from '@/api'
+
+export interface AppArticleCategory {
+ categoryId?: number
+ categoryCode?: string
+ title?: string
+ type?: number
+ image?: string
+ parentId?: number
+ path?: string
+ userId?: number
+ count?: number
+ sortNumber?: number
+ comments?: string
+ description?: string
+ hide?: number
+ recommend?: number
+ showIndex?: number
+ status?: number
+ deleted?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+ value?: number
+ label?: string
+}
+
+export interface AppArticleCategoryParam extends PageParam {
+ categoryId?: number
+ status?: number
+ keywords?: string
+ tenantId?: number
+}
diff --git a/app/api/app/cicd.ts b/app/api/app/cicd.ts
new file mode 100644
index 0000000..e7191be
--- /dev/null
+++ b/app/api/app/cicd.ts
@@ -0,0 +1,307 @@
+import request from '@/utils/request'
+
+// ========== 流水线 API ==========
+
+/**
+ * 分页查询流水线
+ */
+export function pagePipeline(params?: {
+ page?: number
+ limit?: number
+ appId?: number
+ ciType?: string
+ enabled?: boolean
+}) {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ records: AppPipeline[]
+ total: number
+ }
+ }>('/api/app/cicd/pipeline/page', { params })
+}
+
+/**
+ * 查询应用的所有流水线
+ */
+export function listPipelineByApp(appId: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: AppPipeline[]
+ }>('/api/app/cicd/pipeline/app/' + appId)
+}
+
+/**
+ * 查询流水线详情
+ */
+export function getPipeline(id: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: AppPipeline
+ }>('/api/app/cicd/pipeline/' + id)
+}
+
+/**
+ * 创建流水线
+ */
+export function createPipeline(data: Partial) {
+ return request.post<{
+ code: number
+ message: string
+ }>('/api/app/cicd/pipeline', data)
+}
+
+/**
+ * 更新流水线
+ */
+export function updatePipeline(data: Partial) {
+ return request.put<{
+ code: number
+ message: string
+ }>('/api/app/cicd/pipeline', data)
+}
+
+/**
+ * 删除流水线
+ */
+export function deletePipeline(id: number) {
+ return request.delete<{
+ code: number
+ message: string
+ }>('/api/app/cicd/pipeline/' + id)
+}
+
+/**
+ * 启用/禁用流水线
+ */
+export function togglePipeline(id: number, enabled: boolean) {
+ return request.post<{
+ code: number
+ message: string
+ }>('/api/app/cicd/pipeline/' + id + '/toggle?enabled=' + enabled)
+}
+
+/**
+ * 获取流水线状态
+ */
+export function getPipelineStatus(id: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ id: number
+ status: string
+ lastBuildId?: number
+ lastBuildTime?: string
+ successCount: number
+ failureCount: number
+ }
+ }>('/api/app/cicd/pipeline/' + id + '/status')
+}
+
+// ========== 构建 API ==========
+
+/**
+ * 分页查询构建记录
+ */
+export function pageBuild(params?: {
+ page?: number
+ limit?: number
+ appId?: number
+ status?: string
+ ciType?: string
+ branch?: string
+}) {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ records: AppBuild[]
+ total: number
+ }
+ }>('/api/app/cicd/build/page', { params })
+}
+
+/**
+ * 查询应用的所有构建记录
+ */
+export function listBuildByApp(appId: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: AppBuild[]
+ }>('/api/app/cicd/build/app/' + appId)
+}
+
+/**
+ * 查询构建详情
+ */
+export function getBuild(id: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: AppBuild
+ }>('/api/app/cicd/build/' + id)
+}
+
+/**
+ * 获取应用最新构建
+ */
+export function getLatestBuild(appId: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: AppBuild | null
+ }>('/api/app/cicd/build/latest/' + appId)
+}
+
+/**
+ * 触发构建
+ */
+export function triggerBuild(appId: number, branch?: string) {
+ return request.post<{
+ code: number
+ message: string
+ data: {
+ id: number
+ buildNumber: string
+ status: string
+ branch: string
+ }
+ }>('/api/app/cicd/build/trigger', null, {
+ params: { appId, branch }
+ })
+}
+
+/**
+ * 获取构建日志
+ */
+export function getBuildLog(id: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ log: string
+ buildId: number
+ }
+ }>('/api/app/cicd/build/' + id + '/log')
+}
+
+/**
+ * 取消构建
+ */
+export function cancelBuild(id: number) {
+ return request.post<{
+ code: number
+ message: string
+ }>('/api/app/cicd/build/' + id + '/cancel')
+}
+
+/**
+ * 重试构建
+ */
+export function retryBuild(id: number) {
+ return request.post<{
+ code: number
+ message: string
+ data: {
+ id: number
+ buildNumber: string
+ status: string
+ }
+ }>('/api/app/cicd/build/' + id + '/retry')
+}
+
+/**
+ * 获取构建统计
+ */
+export function getBuildStats(appId: number) {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ total: number
+ success: number
+ failed: number
+ running: number
+ }
+ }>('/api/app/cicd/build/stats/' + appId)
+}
+
+/**
+ * 获取CI系统配置
+ */
+export function getCIConfig() {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ supportedCI: string[]
+ defaultCI: string
+ giteaUrl: string
+ }
+ }>('/api/app/cicd/config')
+}
+
+// ========== 类型定义 ==========
+
+export interface AppPipeline {
+ id?: number
+ appId?: number
+ name?: string
+ description?: string
+ ciType?: 'gitea' | 'jenkins' | 'github'
+ repoFullName?: string
+ workflowFile?: string
+ stages?: string
+ env?: 'development' | 'staging' | 'production'
+ defaultBranch?: string
+ enabled?: boolean
+ autoDeploy?: boolean
+ timeout?: number
+ config?: string
+ lastBuildId?: number
+ lastBuildStatus?: string
+ lastBuildTime?: string
+ successCount?: number
+ failureCount?: number
+ userId?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+}
+
+export interface AppBuild {
+ id?: number
+ appId?: number
+ versionId?: number
+ buildNumber?: string
+ name?: string
+ branch?: string
+ commitSha?: string
+ commitMessage?: string
+ commitAuthor?: string
+ ciType?: 'gitea' | 'jenkins' | 'github'
+ ciJobId?: string
+ ciRunId?: string
+ ciApiUrl?: string
+ status?: 'pending' | 'running' | 'success' | 'failed' | 'cancelled'
+ startedAt?: string
+ finishedAt?: string
+ duration?: number
+ logUrl?: string
+ artifactUrl?: string
+ artifactName?: string
+ artifactSize?: number
+ errorMessage?: string
+ triggerType?: 'manual' | 'webhook' | 'schedule'
+ triggeredBy?: number
+ config?: string
+ userId?: number
+ tenantId?: number
+ createTime?: string
+ updateTime?: string
+}
diff --git a/app/api/app/cloudCredential/index.ts b/app/api/app/cloudCredential/index.ts
new file mode 100644
index 0000000..faa3bec
--- /dev/null
+++ b/app/api/app/cloudCredential/index.ts
@@ -0,0 +1,76 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { AppCloudCredential, AppCloudCredentialParam } from './model'
+import {
+ CLOUD_PROVIDER_OPTIONS,
+ getProviderLabel,
+ getProviderIcon,
+} from './model'
+
+// 重新导出,保持向后兼容
+export { CLOUD_PROVIDER_OPTIONS, getProviderLabel, getProviderIcon }
+
+const BASE = '/api/app/cloud-credential'
+
+/**
+ * 分页查询云账号凭证
+ */
+export async function pageCloudCredential(params?: AppCloudCredentialParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/**
+ * 查询用户全部云账号凭证
+ */
+export async function listCloudCredential(provider?: string) {
+ const res = await request.get>(BASE, { params: { provider } })
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/**
+ * 根据ID查询云账号凭证
+ */
+export async function getCloudCredential(id: number) {
+ const res = await request.get>(BASE + '/' + id)
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/**
+ * 创建云账号凭证
+ */
+export async function createCloudCredential(data: AppCloudCredential) {
+ const res = await request.post>(BASE, data)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/**
+ * 修改云账号凭证
+ */
+export async function updateCloudCredential(data: AppCloudCredential) {
+ const res = await request.put>(BASE, data)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/**
+ * 删除云账号凭证
+ */
+export async function removeCloudCredential(id: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/**
+ * 测试云账号凭证连接
+ */
+export async function testCloudCredential(id: number) {
+ const res = await request.post>(BASE + '/test/' + id)
+ if (res.data.code === 200 || res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
\ No newline at end of file
diff --git a/app/api/app/cloudCredential/model.ts b/app/api/app/cloudCredential/model.ts
new file mode 100644
index 0000000..9d6bd4b
--- /dev/null
+++ b/app/api/app/cloudCredential/model.ts
@@ -0,0 +1,61 @@
+import type { PageParam } from '@/api'
+
+/**
+ * 云账号凭证
+ */
+export interface AppCloudCredential {
+ id?: number
+ userId?: number
+ tenantId?: number
+ name?: string
+ provider?: string // aliyun/tencent/huawei/qiniu
+ accessKeyId?: string // AK
+ accessKeySecret?: string // SK (AES加密存储)
+ status?: number // 0-禁用 1-启用
+ testStatus?: number // 0-未测试 1-测试成功 2-测试失败
+ testMessage?: string
+ remark?: string
+ deleted?: number
+ createTime?: string
+ updateTime?: string
+}
+
+/**
+ * 查询参数
+ */
+export interface AppCloudCredentialParam extends PageParam {
+ provider?: string
+ status?: number
+ name?: string
+}
+
+/**
+ * 云服务商选项
+ */
+export const CLOUD_PROVIDER_OPTIONS = [
+ { label: '阿里云 OSS', value: 'aliyun', icon: '🟢' },
+ { label: '腾讯云 COS', value: 'tencent', icon: '🔵' },
+ { label: '华为云 OBS', value: 'huawei', icon: '🟠' },
+ { label: '七牛云 Kodo', value: 'qiniu', icon: '🟡' },
+]
+
+/**
+ * 获取云服务商显示文本
+ */
+export function getProviderLabel(provider: string) {
+ const opt = CLOUD_PROVIDER_OPTIONS.find((i) => i.value === provider)
+ return opt ? `${opt.icon} ${opt.label}` : provider
+}
+
+/**
+ * 获取云服务商图标
+ */
+export function getProviderIcon(provider: string) {
+ const iconMap: Record = {
+ aliyun: '🟢',
+ tencent: '🔵',
+ huawei: '🟠',
+ qiniu: '🟡',
+ }
+ return iconMap[provider] || '☁️'
+}
\ No newline at end of file
diff --git a/app/api/app/contract/index.ts b/app/api/app/contract/index.ts
new file mode 100644
index 0000000..39a8ea5
--- /dev/null
+++ b/app/api/app/contract/index.ts
@@ -0,0 +1,123 @@
+import request from '@/utils/request'
+import type { ApiResult } from '@/api'
+import type { PageResult } from '@/api'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/contract'
+
+// 合同类型
+export type ContractType = 'service' | 'cooperation' | 'purchase' | 'other'
+export type ContractStatus = 'draft' | 'pending' | 'active' | 'expired' | 'terminated'
+
+// 合同模型
+export interface Contract {
+ contractId?: number
+ contractNo?: string
+ title: string
+ contractType: ContractType
+ partyA?: string
+ partyB?: string
+ amount?: number
+ startDate?: string
+ endDate?: string
+ status: ContractStatus
+ fileUrl?: string
+ fileName?: string
+ remark?: string
+ userId?: number
+ userName?: string
+ createTime?: string
+ updateTime?: string
+}
+
+// 合同类型选项
+export const contractTypeOptions = [
+ { label: '服务合同', value: 'service' },
+ { label: '合作协议', value: 'cooperation' },
+ { label: '采购合同', value: 'purchase' },
+ { label: '其他', value: 'other' },
+]
+
+// 合同状态选项
+export const contractStatusOptions = [
+ { label: '草稿', value: 'draft', color: 'default' },
+ { label: '待签署', value: 'pending', color: 'orange' },
+ { label: '生效中', value: 'active', color: 'green' },
+ { label: '已过期', value: 'expired', color: 'red' },
+ { label: '已终止', value: 'terminated', color: 'purple' },
+]
+
+/** 获取合同类型文本 */
+export function contractTypeText(type?: ContractType | null): string {
+ if (!type) return '-'
+ const map: Record = {
+ service: '服务合同',
+ cooperation: '合作协议',
+ purchase: '采购合同',
+ other: '其他',
+ }
+ return map[type] || '-'
+}
+
+/** 获取合同状态标签 */
+export function contractStatusInfo(status?: ContractStatus | null) {
+ if (!status) return { text: '-', color: 'default' }
+ const map: Record = {
+ draft: { text: '草稿', color: 'default' },
+ pending: { text: '待签署', color: 'orange' },
+ active: { text: '生效中', color: 'green' },
+ expired: { text: '已过期', color: 'red' },
+ terminated: { text: '已终止', color: 'purple' },
+ }
+ return map[status] || { text: '-', color: 'default' }
+}
+
+// ============ 接口方法 ============
+
+/** 分页查询合同列表 */
+export async function pageContract(params: {
+ page?: number
+ limit?: number
+ keywords?: string
+ contractType?: ContractType
+ status?: ContractStatus
+}) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取合同详情 */
+export async function getContract(contractId: number) {
+ const res = await request.get>(BASE + '/' + contractId)
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 新增合同 */
+export async function addContract(data: Partial) {
+ const res = await request.post>(BASE, data)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 更新合同 */
+export async function updateContract(contractId: number, data: Partial) {
+ const res = await request.put>(BASE + '/' + contractId, data)
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除合同 */
+export async function removeContract(contractId: number) {
+ const res = await request.delete>(BASE + '/' + contractId)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 合同统计 */
+export async function statsContract() {
+ const res = await request.get>>(BASE + '/stats')
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/invite/index.ts b/app/api/app/invite/index.ts
new file mode 100644
index 0000000..3597d93
--- /dev/null
+++ b/app/api/app/invite/index.ts
@@ -0,0 +1,90 @@
+import request from '@/utils/request'
+
+/**
+ * 生成二维码邀请
+ */
+export function generateInviteQrCode(data: {
+ appId: number
+ role: string
+}) {
+ return request.post<{
+ code: number
+ message: string
+ data: {
+ token: string
+ inviteUrl: string // 邀请链接
+ miniprogramQrCode?: string // 小程序码Base64图片(优先展示)
+ expireTime?: string
+ }
+ }>('/api/app/developer/invite/qrcode', data)
+}
+
+/**
+ * 生成链接邀请
+ */
+export function generateInviteLink(data: {
+ appId: number
+ role: string
+}) {
+ return request.post<{
+ code: number
+ message: string
+ data: {
+ inviteUrl: string
+ token: string
+ expireTime: string
+ }
+ }>('/api/app/developer/invite/link', data)
+}
+
+/**
+ * 验证邀请
+ */
+export function verifyInvite(data: {
+ token: string
+ appId?: number
+}) {
+ return request.post<{
+ code: number
+ message: string
+ data: {
+ valid: boolean
+ appId: number
+ appName: string
+ role: string
+ inviterName: string
+ inviterAvatar?: string
+ expireTime: string
+ }
+ }>('/api/app/developer/invite/verify', data)
+}
+
+/**
+ * 接受邀请
+ */
+export function acceptInvite(data: {
+ token: string
+ appId?: number
+ role?: string
+}) {
+ return request.post<{
+ code: number
+ message: string
+ data: null
+ }>('/api/app/developer/invite/accept', data)
+}
+
+/**
+ * 查询邀请状态(用于轮询检测是否被使用)
+ */
+export function getInviteStatus(token: string) {
+ return request.get<{
+ code: number
+ message: string
+ data: {
+ status: number // 0: 未使用, 1: 已使用
+ usedAt?: string // 使用时间
+ usedBy?: string // 使用者名称
+ }
+ }>('/api/app/developer/invite/status', { token })
+}
diff --git a/app/api/app/notification/index.ts b/app/api/app/notification/index.ts
new file mode 100644
index 0000000..83f2b21
--- /dev/null
+++ b/app/api/app/notification/index.ts
@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import type { Notification, NotificationParam, UnreadCountResult } from './model'
+import { APP_API_URL } from '@/config/setting'
+
+const BASE = APP_API_URL + '/notification'
+
+/** 分页查询通知列表 */
+export async function pageNotification(params: NotificationParam) {
+ const res = await request.get>>(BASE + '/page', { params })
+ if (res.data.code === 0) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询最近通知(用于铃铛下拉,最多 20 条) */
+export async function listRecentNotification(params?: { type?: string; limit?: number }) {
+ const res = await request.get>(BASE + '/recent', { params })
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取未读数量统计 */
+export async function getUnreadCount() {
+ const res = await request.get>(BASE + '/unread-count')
+ if (res.data.code === 0 && res.data.data) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 标记单条通知为已读 */
+export async function markRead(id: number) {
+ const res = await request.put>(BASE + `/read/${id}`)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 标记全部已读 */
+export async function markAllRead(data?: { type?: string }) {
+ const res = await request.put>(BASE + '/read-all', data)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 删除单条通知 */
+export async function removeNotification(id: number) {
+ const res = await request.delete>(BASE + '/' + id)
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 清空全部已读通知 */
+export async function clearReadNotifications(data?: { type?: string }) {
+ const res = await request.delete>(BASE + '/clear-read', { data })
+ if (res.data.code === 0) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
diff --git a/app/api/app/notification/model.ts b/app/api/app/notification/model.ts
new file mode 100644
index 0000000..dcf0c53
--- /dev/null
+++ b/app/api/app/notification/model.ts
@@ -0,0 +1,60 @@
+import type { PageParam } from '@/api'
+
+/** 通知类型枚举 */
+export type NotificationType = 'ticket' | 'review' | 'system' | 'resource' | 'permission' | 'member' | 'payment'
+
+/** 通知实体 */
+export interface Notification {
+ /** 通知 ID */
+ id?: number
+ /** 接收用户 ID */
+ userId?: number
+ /** 通知类型 */
+ type?: NotificationType
+ /** 通知标题 */
+ title?: string
+ /** 通知内容摘要 */
+ content?: string
+ /** 是否已读:0 未读,1 已读 */
+ isRead?: number
+ /** 关联业务 ID(如工单ID、权限申请ID等) */
+ refId?: number
+ /** 关联业务类型(如 ticket、permission_request 等) */
+ refType?: string
+ /** 跳转链接 */
+ linkUrl?: string
+ /** 发送者 ID(系统通知为 0) */
+ senderId?: number
+ /** 发送者名称 */
+ senderName?: string
+ /** 发送者头像 */
+ senderAvatar?: string
+ /** 租户 ID */
+ tenantId?: number
+ /** 创建时间 */
+ createTime?: string
+ /** 更新时间(标记已读时间) */
+ updateTime?: string
+}
+
+/** 通知查询参数 */
+export interface NotificationParam extends PageParam {
+ /** 通知类型筛选 */
+ type?: NotificationType | ''
+ /** 是否已读:0 未读,1 已读,不传查全部 */
+ isRead?: number
+}
+
+/** 未读统计 */
+export interface UnreadCountResult {
+ /** 总未读数 */
+ total?: number
+ /** 各类型未读数 */
+ ticket?: number
+ review?: number
+ system?: number
+ resource?: number
+ permission?: number
+ member?: number
+ payment?: number
+}
diff --git a/app/api/app/setting/index.ts b/app/api/app/setting/index.ts
new file mode 100644
index 0000000..e8c2d06
--- /dev/null
+++ b/app/api/app/setting/index.ts
@@ -0,0 +1,148 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { AppSetting } from './model';
+
+/**
+ * 分页查询设置
+ */
+export async function pageSetting(params: any) {
+ const res = await request.get>>(
+ '/api/app/setting/page',
+ { params }
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 获取设置列表
+ */
+export async function listSetting(params?: any) {
+ const res = await request.get>(
+ '/api/app/setting',
+ { params }
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.data || [];
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据分类获取设置
+ */
+export async function getSettingsByCategory(category: string) {
+ const res = await request.get>(
+ `/api/app/setting/category/${category}`
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.data || [];
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 获取分类设置值(Map格式)
+ */
+export async function getCategoryValues(category: string) {
+ const res = await request.get>>(
+ `/api/app/setting/category/${category}/values`
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.data || {};
+ }
+ return {};
+}
+
+/**
+ * 根据key获取设置值
+ */
+export async function getSettingValue(key: string) {
+ const res = await request.get>(
+ `/api/app/setting/key/${key}`
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.data;
+ }
+ return null;
+}
+
+/**
+ * 根据key获取完整设置
+ */
+export async function getSettingByKey(key: string) {
+ const res = await request.get>(
+ `/api/app/setting/info/${key}`
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.data;
+ }
+ return null;
+}
+
+/**
+ * 保存或更新设置
+ */
+export async function saveSetting(data: Partial) {
+ const res = await request.post>(
+ '/api/app/setting',
+ data
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 更新设置
+ */
+export async function updateSetting(data: Partial) {
+ const res = await request.put>(
+ '/api/app/setting',
+ data
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量保存分类设置
+ */
+export async function batchSaveCategory(category: string, values: Record) {
+ const res = await request.post>(
+ `/api/app/setting/batch/${category}`,
+ values
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除设置
+ */
+export async function removeSetting(settingId: number) {
+ const res = await request.delete>(
+ `/api/app/setting/${settingId}`
+ );
+ // 兼容后端返回 0 或 200 作为成功码
+ if (res.data.code === 0 || res.data.code === 200) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/app/setting/model/index.ts b/app/api/app/setting/model/index.ts
new file mode 100644
index 0000000..f059a08
--- /dev/null
+++ b/app/api/app/setting/model/index.ts
@@ -0,0 +1,45 @@
+/**
+ * 平台设置
+ */
+export interface AppSetting {
+ /** 设置ID */
+ settingId?: number;
+ /** 设置分类:basic/review/market/register/notify/maintenance */
+ category?: string;
+ /** 设置项标识 */
+ settingKey?: string;
+ /** 设置项名称 */
+ settingName?: string;
+ /** 设置值(JSON格式) */
+ settingValue?: string;
+ /** 设置类型:string/number/boolean/json */
+ valueType?: string;
+ /** 设置说明 */
+ description?: string;
+ /** 排序号 */
+ sortNumber?: number;
+ /** 是否启用 0否 1是 */
+ isEnabled?: number;
+ /** 是否公开(前端可读)0否 1是 */
+ isPublic?: number;
+ /** 租户id */
+ tenantId?: number;
+ /** 创建时间 */
+ createdTime?: number;
+ /** 更新时间 */
+ updatedTime?: number;
+}
+
+/**
+ * 平台设置查询参数
+ */
+export interface AppSettingParam {
+ settingId?: number;
+ category?: string;
+ settingKey?: string;
+ settingName?: string;
+ valueType?: string;
+ isEnabled?: number;
+ isPublic?: number;
+ tenantId?: number;
+}
diff --git a/app/api/app/subscription/index.ts b/app/api/app/subscription/index.ts
new file mode 100644
index 0000000..5178aa1
--- /dev/null
+++ b/app/api/app/subscription/index.ts
@@ -0,0 +1,114 @@
+/**
+ * 应用订阅 API
+ * 前端路径:/api/app/subscription → 后端 /api/app/subscription
+ */
+import request from '@/utils/request'
+import type { ApiResult, PageResult } from '@/api'
+import { APP_API_URL } from '@/config/setting'
+import type { AppSubscription, AppSubscriptionParam, SubscribeResult } from './model'
+
+const BASE = APP_API_URL + '/subscription'
+
+/** 统一成功码判断:兼容 code=0(旧约定)和 code=200(Spring Boot 默认) */
+const ok = (code: number) => code === 0 || code === 200
+
+// ============ 订阅操作 ============
+
+/** 创建订阅(免费直接激活,付费创建待支付) */
+export async function createSubscription(data: {
+ productId: number
+ priceType: 'free' | 'one_time' | 'subscription'
+ subscriptionPeriod?: 'month' | 'year'
+ quantity?: number
+}) {
+ const res = await request.post>(BASE + '/subscribe', data)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 获取用户余额 */
+export async function getUserBalance(): Promise<{ balance: number }> {
+ const res = await request.get>(BASE + '/balance')
+ if (ok(res.data.code)) return res.data.data ?? { balance: 0 }
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 发起支付(支持微信/余额支付) */
+export async function paySubscription(id: number, method: 'wechat' | 'balance' | 'alipay' = 'wechat') {
+ const res = await request.post>(BASE + '/pay/' + id, null, {
+ params: { method }
+ })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 查询支付状态(轮询用),同时返回订单详情 */
+export async function checkPayStatus(subscriptionNo: string) {
+ const res = await request.get>(BASE + '/check-status/' + subscriptionNo)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 我的订阅 ============
+
+/** 我的订阅列表(分页) */
+export async function mySubscriptions(params?: AppSubscriptionParam) {
+ const res = await request.get>>(BASE + '/my/page', { params })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 订阅详情 */
+export async function getSubscription(id: number) {
+ const res = await request.get>(BASE + '/detail/' + id)
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 订阅管理 ============
+
+/** 续费 */
+export async function renewSubscription(id: number, period: 'month' | 'year') {
+ const res = await request.post>(BASE + '/renew/' + id, null, { params: { period } })
+ if (ok(res.data.code)) return res.data.data
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 退订/取消 */
+export async function cancelSubscription(id: number) {
+ const res = await request.post>(BASE + '/cancel/' + id)
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+/** 启用/禁用 */
+export async function toggleEnable(id: number, enabled: boolean) {
+ const res = await request.post>(BASE + '/toggle-enable/' + id, null, { params: { enabled } })
+ if (ok(res.data.code)) return res.data.message
+ return Promise.reject(new Error(res.data.message))
+}
+
+// ============ 查询 ============
+
+/** 检查是否已购买某应用 */
+export async function checkPurchased(productId: number) {
+ const res = await request.get>(BASE + '/check-purchased/' + productId)
+ if (ok(res.data.code)) return res.data.data ?? false
+ // 接口未实现时静默返回 false,不影响主流程
+ return false
+}
diff --git a/app/api/app/subscription/model.ts b/app/api/app/subscription/model.ts
new file mode 100644
index 0000000..9bc49b6
--- /dev/null
+++ b/app/api/app/subscription/model.ts
@@ -0,0 +1,110 @@
+import type { PageParam } from '@/api'
+
+/**
+ * 应用订阅
+ */
+export interface AppSubscription {
+ id?: number
+ /** 订阅编号 */
+ subscriptionNo?: string
+ /** 购买用户ID */
+ userId?: number
+ /** 应用产品ID */
+ productId?: number
+ /** 租户ID */
+ tenantId?: number
+
+ /** 订阅状态: pending/active/expired/cancelled */
+ status?: string
+ /** 价格类型: free/one_time/subscription */
+ priceType?: string
+ /** 原价(分) */
+ originalPrice?: number
+ /** 实付金额(分) */
+ payPrice?: number
+ /** 支付方式: 0余额 1微信 3支付宝 12免费 */
+ payType?: number
+ /** 支付状态: 0未支付 1已支付 */
+ payStatus?: number
+ /** 支付时间 */
+ payTime?: string
+ /** 第三方交易号 */
+ transactionId?: string
+
+ /** 订阅周期: month/year */
+ subscriptionPeriod?: string
+ /** 生效时间 */
+ startTime?: string
+ /** 到期时间 */
+ expireTime?: string
+ /** 是否自动续费 */
+ autoRenew?: number
+
+ /** 分配的域名 */
+ instanceDomain?: string
+ /** 实例管理后台URL */
+ instanceAdminUrl?: string
+ /** 实例配置 JSON */
+ instanceConfig?: string
+
+ /** 关联的支付订单号 */
+ orderNo?: string
+ /** 关联的支付订单ID */
+ orderId?: number
+
+ /** 关联的产品信息(查询时返回) */
+ productName?: string
+ productIcon?: string
+ productLogo?: string
+ productAppType?: number
+ productDescription?: string
+ developerName?: string
+
+ remark?: string
+ deleted?: number
+ sortNumber?: number
+ createTime?: string
+ updateTime?: string
+}
+
+/**
+ * 订阅搜索条件
+ */
+export interface AppSubscriptionParam extends PageParam {
+ productId?: number
+ status?: string
+ priceType?: string
+ subscriptionNo?: string
+ keywords?: string
+}
+
+/**
+ * 订阅状态枚举
+ */
+export const SUBSCRIPTION_STATUS = {
+ PENDING: 'pending',
+ ACTIVE: 'active',
+ EXPIRED: 'expired',
+ CANCELLED: 'cancelled',
+} as const
+
+export type SUBSCRIPTION_STATUS_KEY = keyof typeof SUBSCRIPTION_STATUS
+
+export const SUBSCRIPTION_STATUS_NAME: Record = {
+ [SUBSCRIPTION_STATUS.PENDING]: '待支付',
+ [SUBSCRIPTION_STATUS.ACTIVE]: '生效中',
+ [SUBSCRIPTION_STATUS.EXPIRED]: '已过期',
+ [SUBSCRIPTION_STATUS.CANCELLED]: '已取消',
+}
+
+/**
+ * 订阅结果
+ */
+export interface SubscribeResult {
+ subscriptionId?: number
+ subscriptionNo?: string
+ status?: string
+ message?: string
+ payPrice?: number
+ orderNo?: string
+}
diff --git a/app/api/cms/cmsAd/index.ts b/app/api/cms/cmsAd/index.ts
new file mode 100644
index 0000000..c465bd4
--- /dev/null
+++ b/app/api/cms/cmsAd/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsAd, CmsAdParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询广告位
+ */
+export async function pageCmsAd(params: CmsAdParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-ad/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询广告位列表
+ */
+export async function listCmsAd(params?: CmsAdParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-ad',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加广告位
+ */
+export async function addCmsAd(data: CmsAd) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-ad',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改广告位
+ */
+export async function updateCmsAd(data: CmsAd) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-ad',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除广告位
+ */
+export async function removeCmsAd(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-ad/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除广告位
+ */
+export async function removeBatchCmsAd(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-ad/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询广告位
+ */
+export async function getCmsAd(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-ad/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsAd/model/index.ts b/app/api/cms/cmsAd/model/index.ts
new file mode 100644
index 0000000..4e0dac9
--- /dev/null
+++ b/app/api/cms/cmsAd/model/index.ts
@@ -0,0 +1,65 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 广告位
+ */
+export interface CmsAd {
+ // ID
+ adId?: number;
+ // 类型
+ type?: number;
+ // 唯一标识
+ code?: string;
+ // 栏目分类
+ categoryId?: number;
+ // 栏目名称
+ categoryName?: string;
+ // 页面ID
+ designId?: number;
+ // 广告类型
+ adType?: string;
+ // 广告位名称
+ name?: string;
+ // 宽
+ width?: string;
+ // 高
+ height?: string;
+ // css样式
+ style?: string;
+ // 广告图片
+ images?: any;
+ // 广告图片
+ imageList?: any;
+ // 路由/链接地址
+ path?: string;
+ // 用户ID
+ userId?: number;
+ // 语言
+ lang?: string;
+ // 页面ID
+ pageId?: number;
+ // 页面名称
+ pageName?: string;
+ // 排序(数字越小越靠前)
+ sortNumber?: number;
+ // 备注
+ description?: string;
+ // 状态, 0正常, 1冻结
+ status?: number;
+ // 是否删除, 0否, 1是
+ deleted?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ merchantId?: number;
+}
+
+/**
+ * 广告位搜索条件
+ */
+export interface CmsAdParam extends PageParam {
+ adId?: number;
+ pageId?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsAdRecord/index.ts b/app/api/cms/cmsAdRecord/index.ts
new file mode 100644
index 0000000..840e6d0
--- /dev/null
+++ b/app/api/cms/cmsAdRecord/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsAdRecord, CmsAdRecordParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询广告图片
+ */
+export async function pageCmsAdRecord(params: CmsAdRecordParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-ad-record/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询广告图片列表
+ */
+export async function listCmsAdRecord(params?: CmsAdRecordParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-ad-record',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加广告图片
+ */
+export async function addCmsAdRecord(data: CmsAdRecord) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-ad-record',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改广告图片
+ */
+export async function updateCmsAdRecord(data: CmsAdRecord) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-ad-record',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除广告图片
+ */
+export async function removeCmsAdRecord(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-ad-record/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除广告图片
+ */
+export async function removeBatchCmsAdRecord(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-ad-record/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询广告图片
+ */
+export async function getCmsAdRecord(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-ad-record/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsAdRecord/model/index.ts b/app/api/cms/cmsAdRecord/model/index.ts
new file mode 100644
index 0000000..4d6e69a
--- /dev/null
+++ b/app/api/cms/cmsAdRecord/model/index.ts
@@ -0,0 +1,35 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 广告图片
+ */
+export interface CmsAdRecord {
+ // ID
+ adRecordId?: number;
+ // 广告标题
+ title?: string;
+ // 图片地址
+ path?: string;
+ // 链接地址
+ url?: string;
+ // 广告位ID
+ adId?: number;
+ // 排序(数字越小越靠前)
+ sortNumber?: number;
+ // 备注
+ description?: string;
+ // 状态, 0正常, 1冻结
+ status?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+}
+
+/**
+ * 广告图片搜索条件
+ */
+export interface CmsAdRecordParam extends PageParam {
+ adRecordId?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsAppConfig/index.ts b/app/api/cms/cmsAppConfig/index.ts
new file mode 100644
index 0000000..11ed2de
--- /dev/null
+++ b/app/api/cms/cmsAppConfig/index.ts
@@ -0,0 +1,120 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsAppConfig, CmsAppConfigParam, BatchSaveRequest } from './model';
+import {MODULES_API_URL} from '@/config/setting';
+
+/**
+ * 分页查询应用配置
+ */
+export async function pageCmsAppConfig(params: CmsAppConfigParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-app-config/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 获取应用配置列表
+ */
+export async function listCmsAppConfig(params?: CmsAppConfigParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-app-config/list',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据应用ID获取配置映射
+ */
+export async function getConfigsMap(productId: number) {
+ const res = await request.get>>(
+ MODULES_API_URL + `/cms/cms-app-config/map/${productId}`
+ );
+ if (res.data.code === 0) {
+ return res.data.data || {};
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 获取单个配置值
+ */
+export async function getConfigValue(productId: number, configKey: string) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-app-config/value',
+ {
+ params: { productId, configKey }
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 保存配置
+ */
+export async function saveCmsAppConfig(data: CmsAppConfig) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-app-config',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量保存配置
+ */
+export async function batchSaveCmsAppConfig(data: BatchSaveRequest) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-app-config/batch',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 更新配置
+ */
+export async function updateCmsAppConfig(data: CmsAppConfig) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-app-config',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除配置
+ */
+export async function deleteCmsAppConfig(configId: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + `/cms/cms-app-config/${configId}`
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsAppConfig/model/index.ts b/app/api/cms/cmsAppConfig/model/index.ts
new file mode 100644
index 0000000..66b97ed
--- /dev/null
+++ b/app/api/cms/cmsAppConfig/model/index.ts
@@ -0,0 +1,66 @@
+import type { PageParam } from '@/api';
+import type { PageResult } from '@/api';
+
+/**
+ * 应用配置
+ */
+export interface CmsAppConfig {
+ configId?: number;
+ productId?: number;
+ configKey?: string;
+ configValue?: string;
+ configType?: string;
+ isEncrypted?: number;
+ isSecret?: number;
+ description?: string;
+ sortNumber?: number;
+ tenantId?: number;
+ createdTime?: string;
+ updatedTime?: string;
+ deleted?: number;
+}
+
+/**
+ * 应用配置查询参数
+ */
+export interface CmsAppConfigParam extends PageParam {
+ configId?: number;
+ productId?: number;
+ configKey?: string;
+ configType?: string;
+ isSecret?: number;
+}
+
+/**
+ * 批量保存请求
+ */
+export interface BatchSaveRequest {
+ productId: number;
+ configs: CmsAppConfig[];
+}
+
+/**
+ * 配置类型定义
+ */
+export interface ConfigType {
+ key: string;
+ name: string;
+ icon: string;
+ description: string;
+ configs: ConfigField[];
+}
+
+/**
+ * 配置字段定义
+ */
+export interface ConfigField {
+ key: string;
+ label: string;
+ type: 'input' | 'textarea' | 'number' | 'select' | 'switch' | 'password' | 'json';
+ required?: boolean;
+ placeholder?: string;
+ options?: Array<{ label: string; value: any }>;
+ defaultValue?: any;
+ description?: string;
+ secret?: boolean;
+}
diff --git a/app/api/cms/cmsArticle/index.ts b/app/api/cms/cmsArticle/index.ts
new file mode 100644
index 0000000..ab4ed6c
--- /dev/null
+++ b/app/api/cms/cmsArticle/index.ts
@@ -0,0 +1,162 @@
+import request from '@/utils/request';
+import type {ApiResult, PageResult} from '@/api';
+import type {CmsArticle, CmsArticleParam} from './model';
+
+function isSuccess(code?: number) {
+ return code === 0 || code === 200;
+}
+
+/**
+ * 分页查询文章
+ */
+export async function pageCmsArticle(params: CmsArticleParam) {
+ const res = await request.get>>(
+ '/api/cms/cms-article/page',
+ {
+ params
+ }
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询文章列表
+ */
+export async function listCmsArticle(params?: CmsArticleParam) {
+ const res = await request.get>(
+ '/api/cms/cms-article',
+ {
+ params
+ }
+ );
+ if (isSuccess(res.data.code) && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加文章
+ */
+export async function addCmsArticle(data: CmsArticle) {
+ const res = await request.post>(
+ '/api/cms/cms-article',
+ data
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改文章
+ */
+export async function updateCmsArticle(data: CmsArticle) {
+ const res = await request.put>(
+ '/api/cms/cms-article',
+ data
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量修改文章
+ */
+export async function updateBatchCmsArticle(data: any) {
+ const res = await request.put>(
+ '/api/cms/cms-article/batch',
+ data
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除文章
+ */
+export async function removeCmsArticle(id?: number) {
+ const res = await request.delete>(
+ '/api/cms/cms-article/' + id
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除文章
+ */
+export async function removeBatchCmsArticle(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ '/api/cms/cms-article/batch',
+ {
+ data
+ }
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询文章
+ */
+export async function getCmsArticle(id: number) {
+ const res = await request.get>(
+ '/api/cms/cms-article/' + id
+ );
+ if (isSuccess(res.data.code) && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据code查询文章
+ */
+export async function getByCode(code: string) {
+ const res = await request.get>(
+ '/api/cms/cms-article/getByCode/' + code
+ );
+ if (isSuccess(res.data.code) && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+export async function getCount(params: CmsArticleParam) {
+ const res = await request.get('/api/cms/cms-article/data', {
+ params
+ });
+ if (isSuccess(res.data.code)) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 文章批量导入
+ */
+export async function importArticles(file: File) {
+ const formData = new FormData();
+ formData.append('file', file);
+ const res = await request.post>(
+ '/api/cms/cms-article/import',
+ formData
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsArticle/model/index.ts b/app/api/cms/cmsArticle/model/index.ts
new file mode 100644
index 0000000..d91a9fe
--- /dev/null
+++ b/app/api/cms/cmsArticle/model/index.ts
@@ -0,0 +1,133 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 文章
+ */
+export interface CmsArticle {
+ // 文章ID
+ articleId?: number;
+ // 文章标题
+ title?: string;
+ // 文章类型 0常规 1视频
+ type?: number;
+ // 文章模型
+ model?: string;
+ // 文章编号
+ code?: string;
+ // 文章详情
+ detail?: string;
+ // 列表显示方式(10小图展示 20大图展示)
+ showType?: number;
+ // 话题
+ topic?: string;
+ // 标签
+ tags?: any;
+ // 父级ID
+ parentId?: number;
+ parentName?: string;
+ // 栏目ID
+ categoryId?: number;
+ // 栏目名称
+ categoryName?: string;
+ // 封面图
+ image?: string;
+ // 来源
+ source?: string;
+ // 摘要
+ overview?: string;
+ // 虚拟阅读量(仅用作展示)
+ virtualViews?: number;
+ // 实际阅读量
+ actualViews?: number;
+ // 浏览权限(0公开 1会员 2密码)
+ permission?: number;
+ // 访问密码
+ password?: string;
+ // 确认密码
+ password2?: string;
+ // 发布来源客户端 (APP、H5、小程序等)
+ platform?: string;
+ // 文章附件
+ files?: string;
+ // 视频地址
+ video?: string;
+ // 接受的文件类型
+ accept?: string;
+ // 经度
+ longitude?: string;
+ // 纬度
+ latitude?: string;
+ // 所在省份
+ province?: string;
+ // 所在城市
+ city?: string;
+ // 所在辖区
+ region?: string;
+ // 街道地址
+ address?: string;
+ // 点赞数
+ likes?: number;
+ // 评论数
+ commentNumbers?: number;
+ // 提醒谁看
+ toUsers?: string;
+ // 文章内容
+ content?: string;
+ // 编辑器类型
+ editor?: number;
+ // PDF地址
+ pdfUrl?: string;
+ // 用户ID
+ userId?: number;
+ // 商户ID
+ merchantId?: number;
+ // 作者
+ author?: string;
+ // 语言
+ lang?: string;
+ // 是否推荐
+ recommend?: number;
+ // 是否同步翻译其他语言版本
+ translation?: boolean;
+ // 排序(数字越小越靠前)
+ sortNumber?: number;
+ // 备注
+ description?: string;
+ // 状态, 0已发布, 1待审核 2已驳回 3违规内容
+ status?: number;
+ // 状态描述
+ statusText?: string;
+ // 是否删除, 0否, 1是
+ deleted?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+ // 是否移动端
+ isMobile?: boolean;
+ // 二维码
+ qrcode?: string;
+ // 文章路径
+ url?: string;
+}
+
+/**
+ * 文章搜索条件
+ */
+export interface CmsArticleParam extends PageParam {
+ articleId?: number;
+ model?: string;
+ status?: number;
+ categoryId?: number;
+ recommend?: number;
+ keywords?: string;
+}
+
+export interface CmsArticleCount {
+ totalNum?: number;
+ totalNum2?: number;
+ totalNum3?: number;
+ totalNum4?: number;
+}
diff --git a/app/api/cms/cmsArticleCategory/index.ts b/app/api/cms/cmsArticleCategory/index.ts
new file mode 100644
index 0000000..e1cebda
--- /dev/null
+++ b/app/api/cms/cmsArticleCategory/index.ts
@@ -0,0 +1,112 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsArticleCategory, CmsArticleCategoryParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+function isSuccess(code?: number) {
+ return code === 0 || code === 200;
+}
+
+/**
+ * 分页查询文章分类表
+ */
+export async function pageCmsArticleCategory(params: CmsArticleCategoryParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-article-category/page',
+ {
+ params
+ }
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询文章分类表列表
+ */
+export async function listCmsArticleCategory(params?: CmsArticleCategoryParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-category',
+ {
+ params
+ }
+ );
+ if (isSuccess(res.data.code) && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加文章分类表
+ */
+export async function addCmsArticleCategory(data: CmsArticleCategory) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-article-category',
+ data
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改文章分类表
+ */
+export async function updateCmsArticleCategory(data: CmsArticleCategory) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-article-category',
+ data
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除文章分类表
+ */
+export async function removeCmsArticleCategory(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-category/' + id
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除文章分类表
+ */
+export async function removeBatchCmsArticleCategory(
+ data: (number | undefined)[]
+) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-category/batch',
+ {
+ data
+ }
+ );
+ if (isSuccess(res.data.code)) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询文章分类表
+ */
+export async function getCmsArticleCategory(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-category/' + id
+ );
+ if (isSuccess(res.data.code) && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsArticleCategory/model/index.ts b/app/api/cms/cmsArticleCategory/model/index.ts
new file mode 100644
index 0000000..34c13f0
--- /dev/null
+++ b/app/api/cms/cmsArticleCategory/model/index.ts
@@ -0,0 +1,60 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 文章分类表
+ */
+export interface CmsArticleCategory {
+ // 文章分类ID
+ categoryId?: number;
+ // 分类标识
+ categoryCode?: string;
+ // 分类名称
+ title?: string;
+ // 类型 0列表 1单页 2外链
+ type?: number;
+ // 分类图片
+ image?: string;
+ // 上级分类ID
+ parentId?: number;
+ // 路由/链接地址
+ path?: string;
+ // 组件路径
+ component?: string;
+ // 绑定的页面
+ pageId?: number;
+ // 用户ID
+ userId?: number;
+ // 文章数量
+ count?: number;
+ // 排序(数字越小越靠前)
+ sortNumber?: number;
+ // 备注
+ description?: string;
+ // 是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)
+ hide?: number;
+ // 是否推荐
+ recommend?: number;
+ // 是否显示在首页
+ showIndex?: number;
+ // 状态, 0正常, 1禁用
+ status?: number;
+ // 是否删除, 0否, 1是
+ deleted?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+ value?: number;
+ label?: string;
+}
+
+/**
+ * 文章分类表搜索条件
+ */
+export interface CmsArticleCategoryParam extends PageParam {
+ categoryId?: number;
+ status?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsArticleComment/index.ts b/app/api/cms/cmsArticleComment/index.ts
new file mode 100644
index 0000000..085579f
--- /dev/null
+++ b/app/api/cms/cmsArticleComment/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsArticleComment, CmsArticleCommentParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询文章评论表
+ */
+export async function pageCmsArticleComment(params: CmsArticleCommentParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-article-comment/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询文章评论表列表
+ */
+export async function listCmsArticleComment(params?: CmsArticleCommentParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-comment',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加文章评论表
+ */
+export async function addCmsArticleComment(data: CmsArticleComment) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-article-comment',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改文章评论表
+ */
+export async function updateCmsArticleComment(data: CmsArticleComment) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-article-comment',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除文章评论表
+ */
+export async function removeCmsArticleComment(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-comment/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除文章评论表
+ */
+export async function removeBatchCmsArticleComment(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-comment/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询文章评论表
+ */
+export async function getCmsArticleComment(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-comment/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsArticleComment/model/index.ts b/app/api/cms/cmsArticleComment/model/index.ts
new file mode 100644
index 0000000..83d212c
--- /dev/null
+++ b/app/api/cms/cmsArticleComment/model/index.ts
@@ -0,0 +1,47 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 文章评论表
+ */
+export interface CmsArticleComment {
+ // 评价ID
+ commentId?: number;
+ // 文章ID
+ articleId?: number;
+ // 评分 (10好评 20中评 30差评)
+ score?: number;
+ // 评价内容
+ content?: string;
+ // 是否为图片评价
+ isPicture?: number;
+ // 评论者ID
+ userId?: number;
+ // 被评价者ID
+ toUserId?: number;
+ // 回复的评论ID
+ replyCommentId?: number;
+ // 回复者ID
+ replyUserId?: number;
+ // 排序(数字越小越靠前)
+ sortNumber?: number;
+ // 备注
+ description?: string;
+ // 状态, 0未读, 1已读
+ status?: number;
+ // 是否删除, 0否, 1是
+ deleted?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+}
+
+/**
+ * 文章评论表搜索条件
+ */
+export interface CmsArticleCommentParam extends PageParam {
+ commentId?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsArticleContent/index.ts b/app/api/cms/cmsArticleContent/index.ts
new file mode 100644
index 0000000..c25d8f4
--- /dev/null
+++ b/app/api/cms/cmsArticleContent/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsArticleContent, CmsArticleContentParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询文章记录表
+ */
+export async function pageCmsArticleContent(params: CmsArticleContentParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-article-content/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询文章记录表列表
+ */
+export async function listCmsArticleContent(params?: CmsArticleContentParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-content',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加文章记录表
+ */
+export async function addCmsArticleContent(data: CmsArticleContent) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-article-content',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改文章记录表
+ */
+export async function updateCmsArticleContent(data: CmsArticleContent) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-article-content',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除文章记录表
+ */
+export async function removeCmsArticleContent(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-content/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除文章记录表
+ */
+export async function removeBatchCmsArticleContent(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-content/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询文章记录表
+ */
+export async function getCmsArticleContent(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-content/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsArticleContent/model/index.ts b/app/api/cms/cmsArticleContent/model/index.ts
new file mode 100644
index 0000000..432c376
--- /dev/null
+++ b/app/api/cms/cmsArticleContent/model/index.ts
@@ -0,0 +1,25 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 文章记录表
+ */
+export interface CmsArticleContent {
+ //
+ id?: number;
+ // 文章ID
+ articleId?: number;
+ // 文章内容
+ content?: string;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+}
+
+/**
+ * 文章记录表搜索条件
+ */
+export interface CmsArticleContentParam extends PageParam {
+ id?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsArticleCount/index.ts b/app/api/cms/cmsArticleCount/index.ts
new file mode 100644
index 0000000..0d88f6d
--- /dev/null
+++ b/app/api/cms/cmsArticleCount/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsArticleCount, CmsArticleCountParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询点赞文章
+ */
+export async function pageCmsArticleCount(params: CmsArticleCountParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-article-count/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询点赞文章列表
+ */
+export async function listCmsArticleCount(params?: CmsArticleCountParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-count',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加点赞文章
+ */
+export async function addCmsArticleCount(data: CmsArticleCount) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-article-count',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改点赞文章
+ */
+export async function updateCmsArticleCount(data: CmsArticleCount) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-article-count',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除点赞文章
+ */
+export async function removeCmsArticleCount(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-count/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除点赞文章
+ */
+export async function removeBatchCmsArticleCount(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-count/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询点赞文章
+ */
+export async function getCmsArticleCount(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-count/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsArticleCount/model/index.ts b/app/api/cms/cmsArticleCount/model/index.ts
new file mode 100644
index 0000000..9cfbc24
--- /dev/null
+++ b/app/api/cms/cmsArticleCount/model/index.ts
@@ -0,0 +1,25 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 点赞文章
+ */
+export interface CmsArticleCount {
+ // 主键ID
+ id?: number;
+ // 文章ID
+ articleId?: number;
+ // 用户ID
+ userId?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+}
+
+/**
+ * 点赞文章搜索条件
+ */
+export interface CmsArticleCountParam extends PageParam {
+ id?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsArticleLike/index.ts b/app/api/cms/cmsArticleLike/index.ts
new file mode 100644
index 0000000..ab3dd0f
--- /dev/null
+++ b/app/api/cms/cmsArticleLike/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsArticleLike, CmsArticleLikeParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询点赞文章
+ */
+export async function pageCmsArticleLike(params: CmsArticleLikeParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-article-like/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询点赞文章列表
+ */
+export async function listCmsArticleLike(params?: CmsArticleLikeParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-like',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加点赞文章
+ */
+export async function addCmsArticleLike(data: CmsArticleLike) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-article-like',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改点赞文章
+ */
+export async function updateCmsArticleLike(data: CmsArticleLike) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-article-like',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除点赞文章
+ */
+export async function removeCmsArticleLike(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-like/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除点赞文章
+ */
+export async function removeBatchCmsArticleLike(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-article-like/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询点赞文章
+ */
+export async function getCmsArticleLike(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-article-like/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsArticleLike/model/index.ts b/app/api/cms/cmsArticleLike/model/index.ts
new file mode 100644
index 0000000..ccee27a
--- /dev/null
+++ b/app/api/cms/cmsArticleLike/model/index.ts
@@ -0,0 +1,25 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 点赞文章
+ */
+export interface CmsArticleLike {
+ // 主键ID
+ id?: number;
+ // 文章ID
+ articleId?: number;
+ // 用户ID
+ userId?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+}
+
+/**
+ * 点赞文章搜索条件
+ */
+export interface CmsArticleLikeParam extends PageParam {
+ id?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsContactLead/index.ts b/app/api/cms/cmsContactLead/index.ts
new file mode 100644
index 0000000..8135413
--- /dev/null
+++ b/app/api/cms/cmsContactLead/index.ts
@@ -0,0 +1,103 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsContactLead, CmsContactLeadParam, ContactLeadSubmitForm } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+// 公开 CMS 接口路径(无需登录,走 /api/cms/* 代理)
+const CMS_PUBLIC_URL = '/api/cms';
+
+/**
+ * 提交联系表单(公开接口,无需登录)
+ */
+export async function submitContactLead(data: ContactLeadSubmitForm) {
+ const res = await request.post>(
+ CMS_PUBLIC_URL + '/cms-contact-lead/submit',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 分页查询销售线索(后台管理)
+ */
+export async function pageCmsContactLead(params: CmsContactLeadParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-contact-lead/page',
+ { params }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询销售线索列表(后台管理)
+ */
+export async function listCmsContactLead(params?: CmsContactLeadParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-contact-lead',
+ { params }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询销售线索
+ */
+export async function getCmsContactLead(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-contact-lead/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改销售线索(跟进状态等)
+ */
+export async function updateCmsContactLead(data: CmsContactLead) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-contact-lead',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除销售线索
+ */
+export async function removeCmsContactLead(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-contact-lead/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除销售线索
+ */
+export async function removeBatchCmsContactLead(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-contact-lead/batch',
+ { data }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsContactLead/model/index.ts b/app/api/cms/cmsContactLead/model/index.ts
new file mode 100644
index 0000000..345bb92
--- /dev/null
+++ b/app/api/cms/cmsContactLead/model/index.ts
@@ -0,0 +1,54 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 销售线索(联系表单)
+ */
+export interface CmsContactLead {
+ // 线索ID
+ leadId?: number;
+ // 姓名
+ name?: string;
+ // 手机号
+ phone?: string;
+ // 单位名称
+ company?: string;
+ // 交付方式: saas / private / hybrid
+ delivery?: string;
+ // 需求描述
+ need?: string;
+ // 来源页(页面 URL)
+ source?: string;
+ // 客户IP
+ clientIp?: string;
+ // 跟进状态: 0待跟进 1跟进中 2已成单 3已放弃
+ followStatus?: number;
+ // 跟进备注
+ followRemark?: string;
+ // 创建时间
+ createTime?: string;
+ // 更新时间
+ updateTime?: string;
+}
+
+/**
+ * 销售线索搜索条件
+ */
+export interface CmsContactLeadParam extends PageParam {
+ leadId?: number;
+ keywords?: string;
+ followStatus?: number;
+ createTimeStart?: string;
+ createTimeEnd?: string;
+}
+
+/**
+ * 提交联系表单的请求参数
+ */
+export interface ContactLeadSubmitForm {
+ name: string;
+ phone: string;
+ company: string;
+ delivery?: string;
+ need: string;
+ source?: string;
+}
diff --git a/app/api/cms/cmsDesign/index.ts b/app/api/cms/cmsDesign/index.ts
new file mode 100644
index 0000000..eb4736c
--- /dev/null
+++ b/app/api/cms/cmsDesign/index.ts
@@ -0,0 +1,106 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsDesign, CmsDesignParam } from './model';
+import { MODULES_API_URL } from '@/config/setting';
+
+/**
+ * 分页查询页面管理记录表
+ */
+export async function pageCmsDesign(params: CmsDesignParam) {
+ const res = await request.get>>(
+ MODULES_API_URL + '/cms/cms-design/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询页面管理记录表列表
+ */
+export async function listCmsDesign(params?: CmsDesignParam) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-design',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加页面管理记录表
+ */
+export async function addCmsDesign(data: CmsDesign) {
+ const res = await request.post>(
+ MODULES_API_URL + '/cms/cms-design',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改页面管理记录表
+ */
+export async function updateCmsDesign(data: CmsDesign) {
+ const res = await request.put>(
+ MODULES_API_URL + '/cms/cms-design',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除页面管理记录表
+ */
+export async function removeCmsDesign(id?: number) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-design/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除页面管理记录表
+ */
+export async function removeBatchCmsDesign(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ MODULES_API_URL + '/cms/cms-design/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询页面管理记录表
+ */
+export async function getCmsDesign(id: number) {
+ const res = await request.get>(
+ MODULES_API_URL + '/cms/cms-design/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
diff --git a/app/api/cms/cmsDesign/model/index.ts b/app/api/cms/cmsDesign/model/index.ts
new file mode 100644
index 0000000..64c33a3
--- /dev/null
+++ b/app/api/cms/cmsDesign/model/index.ts
@@ -0,0 +1,64 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 页面管理记录表
+ */
+export interface CmsDesign {
+ pageId?: number;
+ name?: string;
+ keywords?: string;
+ description?: string;
+ path?: string;
+ component?: string;
+ photo?: string;
+ content?: string;
+ // 类型
+ type?: string;
+ categoryId?: number;
+ // 宽
+ width?: string;
+ // 高
+ height?: string;
+ // 页面样式
+ style?: string;
+ // 附件
+ images?: string;
+ // 用户ID
+ userId?: number;
+ // 设为首页
+ home?: number;
+ // 排序
+ sortNumber?: number;
+ // 备注
+ description?: string;
+ // 状态
+ status?: number;
+ // 创建时间
+ createTime?: string;
+ // 更新时间
+ updateTime?: string;
+ // 页面布局
+ layout?: string;
+ backgroundColor?: string;
+ // 关联网站导航ID
+ navigationId?: number;
+ showLayout?: boolean;
+ btn?: any[];
+ showBanner?: boolean;
+ showButton?: boolean;
+ // 是否同步翻译其他语言版本
+ translation?: boolean;
+ buyUrl?: string;
+ demoUrl?: string;
+ account?: string;
+ docUrl?: string;
+ parentId?: number;
+}
+
+/**
+ * 页面管理记录表搜索条件
+ */
+export interface CmsDesignParam extends PageParam {
+ pageId?: number;
+ keywords?: string;
+}
diff --git a/app/api/cms/cmsDomain/index.ts b/app/api/cms/cmsDomain/index.ts
new file mode 100644
index 0000000..8ae32ad
--- /dev/null
+++ b/app/api/cms/cmsDomain/index.ts
@@ -0,0 +1,153 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { CmsDomain, CmsDomainParam } from './model';
+import { SERVER_API_URL} from '@/config/setting';
+
+/**
+ * 分页查询网站域名记录表
+ */
+export async function pageCmsDomain(params: CmsDomainParam) {
+ const res = await request.get>>(
+ SERVER_API_URL + '/cms/cms-domain/page',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询网站域名记录表列表
+ */
+export async function listCmsDomain(params?: CmsDomainParam) {
+ const res = await request.get>(
+ SERVER_API_URL + '/cms/cms-domain',
+ {
+ params
+ }
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 添加网站域名记录表
+ */
+export async function addCmsDomain(data: CmsDomain) {
+ const res = await request.post>(
+ SERVER_API_URL + '/cms/cms-domain',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改网站域名记录表
+ */
+export async function updateCmsDomain(data: CmsDomain) {
+ const res = await request.post>(
+ SERVER_API_URL + '/cms/cms-domain/domain',
+ data
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除网站域名记录表
+ */
+export async function removeCmsDomain(id?: number) {
+ const res = await request.delete>(
+ SERVER_API_URL + '/cms/cms-domain/' + id
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 批量删除网站域名记录表
+ */
+export async function removeBatchCmsDomain(data: (number | undefined)[]) {
+ const res = await request.delete>(
+ SERVER_API_URL + '/cms/cms-domain/batch',
+ {
+ data
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据id查询网站域名记录表
+ */
+export async function getCmsDomain(id: number) {
+ const res = await request.get>(
+ SERVER_API_URL + '/cms/cms-domain/' + id
+ );
+ if (res.data.code === 0 && res.data.data) {
+ return res.data.data;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 检查IP是否存在
+ */
+export async function checkExistence(
+ field: string,
+ value: string,
+ id?: number
+) {
+ const res = await request.get>(
+ SERVER_API_URL + '/cms/cms-domain/existence',
+ {
+ params: { field, value, id }
+ }
+ );
+ if (res.data.code === 0) {
+ return res.data.message;
+ }
+ return Promise.reject(new Error(res.data.message));
+}
+
+export async function resolvable(id: number) {
+ const res = await request.get