diff --git a/.trae/documents/后端一致性问题清单(Java-vs-NestV1).md b/.trae/documents/后端一致性问题清单(Java-vs-NestV1).md new file mode 100644 index 00000000..1f4ea4fb --- /dev/null +++ b/.trae/documents/后端一致性问题清单(Java-vs-NestV1).md @@ -0,0 +1,56 @@ +# 后端一致性问题清单(Java vs Nest v1) + +仅列出不一致项,供后端开发 AI 按 Java 源码核实与修复。每条均给出 Java 具体文件位置(含行号范围)。 + +## 1)缺失控制器:API 任务接口 +- 差异:Nest v1 缺少 `GET /api/task/growth` 与 `GET /api/task/point` +- Java 基准:`niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/api/sys/TaskController.java:19-27` +- 修复建议:在 v1 增加对应 `api/sys/task` 控制器与端点,实现从 `ITaskService` 获取成长任务与积分任务并返回 `Result.success(...)` + +## 2)缺失端点:小程序消息推送 +- 差异:Nest v1 存在控制器但无方法;Java 端提供 `/api/weapp/serve/{site_id}` +- Java 基准:`niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/api/weapp/ServeController.java:25-29` +- 修复建议:在 v1 的 `/api/weapp` 控制器中添加 `serve/{site_id}` 端点,设置站点 `RequestUtils.setSiteId(siteId)`,调用 `IServeService.service(request, response)` + +## 3)缺失端点:公众号消息推送 +- 差异:Nest v1 存在控制器但无方法;Java 端提供 `/api/wechat/serve/{site_id}` +- Java 基准:`niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/api/wechat/ServeController.java:26-30` +- 修复建议:在 v1 的 `/api/wechat` 控制器中添加 `serve/{site_id}` 端点,设置站点并调用 `IServeService.service(request, response)` + +## 4)错误统一处理:`/error` 响应逻辑缺失 +- 差异:Nest v1 控制器存在但无方法;Java 端实现了状态码分支并返回 `Result.fail(...)` +- Java 基准:`niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/HttpServerErrorController.java:16-33` +- 修复建议:在 v1 的 `/error` 控制器实现 `handleError` 逻辑,按照 500 / 404 / 其他状态返回 `Result.fail(code, message)` 并附 `contextPath` + +## 5)异步任务接口响应语义不一致 +- 差异:`GET /core/task/async` + - Java:仅返回固定消息“异步任务开始” + - Nest v1:返回异步执行结果对象 +- Java 基准:`niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/CoreAsyncTaskController.java:41-45` +- 修复建议:将 v1 的 `/async` 响应改为 `Result.success("异步任务开始")`,与 Java 语义对齐;同步 `/sync` 保持返回执行结果 + +## 6)插件控制器多处行为与权限不一致 +- 差异:`/core/addon/*` + - `/javaSetup`:Java 执行 `AddonInstallJavaTools.installExec("shop")` 后返回空成功;v1 返回检查结果对象 + - `/setup/{id}`:Java 执行 `installCheck` 后还执行 `install("shop", "local")`;v1 仅执行检查 + - `/exception`:Java 抛出运行时异常;v1 返回成功 + - `/auth`:Java 抛出 `AuthException`;v1 返回成功 + - `/saCheckLogin`:Java 需登录(`@SaCheckLogin`);v1 标记公开访问(`@Public`) +- Java 基准: + - `niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/CoreAddonController.java:31-35`(javaSetup) + - `niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/CoreAddonController.java:42-47`(setup/{id}) + - `niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/CoreAddonController.java:54-60`(exception) + - `niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/CoreAddonController.java:67-73`(auth) + - `niucloud-java/niucloud-core/src/main/java/com/niu/core/controller/core/CoreAddonController.java:75-79`(saCheckLogin) +- 修复建议: + - `/javaSetup` 与 `/setup/{id}` 按 Java 流程执行对应安装工具与安装动作,返回空成功 + - `/exception` 与 `/auth` 改为抛出与 Java 对应的异常类型,交由全局异常处理器响应 + - `/saCheckLogin` 取消公开访问,启用登录守卫,语义对齐 Java 的登录校验 + +## 7)多余控制器(Nest v1) +- 差异:`NiuExceptionHandlerController` 在 v1 存在但 Java 端无对应控制器 +- 说明:可保留作为框架级占位;若需对齐 Java,可删除或实现为全局异常处理而非控制器形式 + +--- + +注:以上为截至当前检索的全部不一致项。修复时需严格对齐 Java 业务逻辑与接口契约,完成后建议运行端到端契约测试以验证路由、参数与响应一致性。 diff --git a/admin-vben/.env.development b/admin-vben/.env.development index aecaf56a..bc48035e 100644 --- a/admin-vben/.env.development +++ b/admin-vben/.env.development @@ -1,6 +1,8 @@ -VITE_APP_BASE_URL=http://localhost:3000 +VITE_APP_BASE_URL='/adminapi/' NODE_ENV=development VITE_APP_TIMEOUT=30000 VITE_APP_MOCK=false VITE_REQUEST_HEADER_TOKEN_KEY='token' VITE_REQUEST_HEADER_SITEID_KEY='site-id' +VITE_IMG_DOMAIN='' +VITE_DETAULT_TITLE='WWJCloud Admin' diff --git a/admin-vben/.env.production b/admin-vben/.env.production index 7571ad2c..b625b72c 100644 --- a/admin-vben/.env.production +++ b/admin-vben/.env.production @@ -1,6 +1,8 @@ -VITE_APP_BASE_URL=http://localhost:3000 +VITE_APP_BASE_URL='/adminapi/' NODE_ENV=production VITE_APP_TIMEOUT=30000 VITE_APP_MOCK=false VITE_REQUEST_HEADER_TOKEN_KEY='token' VITE_REQUEST_HEADER_SITEID_KEY='site-id' +VITE_IMG_DOMAIN='' +VITE_DETAULT_TITLE='WWJCloud Admin' diff --git a/admin-vben/apps/web-antd/src/api/core/auth.ts b/admin-vben/apps/web-antd/src/api/core/auth.ts index 8639b577..9baaeee0 100644 --- a/admin-vben/apps/web-antd/src/api/core/auth.ts +++ b/admin-vben/apps/web-antd/src/api/core/auth.ts @@ -1,4 +1,54 @@ -import type { AxiosResponse } from 'axios'; +export interface LoginResponse { + accessToken: string; + refreshToken: string; + tokenType: string; + expiresIn: number; +} + +export interface UserInfo { + id: number; + username: string; + nickname: string; + avatar: string; + email: string; + phone: string; + roles: string[]; + permissions: string[]; + siteId: number; + siteName: string; +} + +export interface MenuItem { + id: number; + name: string; + path: string; + component: string; + icon: string; + sort: number; + status: number; + children?: MenuItem[]; +} + +export interface SiteInfo { + id: number; + siteName: string; + siteLogo: string; + siteDomain: string; + status: number; +} + +export interface LoginConfig { + captchaEnabled: boolean; + defaultUsername: string; + defaultPassword: string; +} + +export interface SystemVersion { + version: string; + buildTime: string; + nodeVersion: string; + system: string; +} import { requestClient } from '#/api/request'; @@ -10,41 +60,41 @@ import { requestClient } from '#/api/request'; export function loginApi( params: { username: string; password: string; captcha_code?: string }, loginType: string, -): Promise> { +): Promise { return requestClient.get(`login/${loginType}`, { params }); } /** * 退出登录 */ -export function logoutApi(): Promise> { +export function logoutApi(): Promise { return requestClient.put('auth/logout', {}, { showErrorMessage: false }); } /** * 获取用户权限菜单 */ -export function getAuthMenusApi(params?: Record): Promise> { +export function getAuthMenusApi(params?: Record): Promise { return requestClient.get('auth/authmenu', { params }); } /** * 获取站点信息 */ -export function getSiteInfoApi(): Promise> { +export function getSiteInfoApi(): Promise { return requestClient.get('auth/site'); } /** * 获取登录配置 */ -export function getLoginConfigApi(): Promise> { +export function getLoginConfigApi(): Promise { return requestClient.get('login/config'); } /** * 获取系统版本信息 */ -export function getVersionsApi(): Promise> { +export function getVersionsApi(): Promise { return requestClient.get('sys/info'); } \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/diy.ts b/admin-vben/apps/web-antd/src/api/core/diy.ts new file mode 100644 index 00000000..f0cb01f2 --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/diy.ts @@ -0,0 +1,145 @@ +import type { DiyPageForm, DiyShareForm } from './model/diyModel'; + +/** + * DIY decoration management API + */ + +/** + * Get DIY page list + */ +export const getDiyPageList = (params: any) => { + return request.get('adminapi/diy/diy', { params }); +}; + +/** + * Get DIY page info + */ +export const getDiyPageInfo = (id: number) => { + return request.get(`adminapi/diy/diy/${id}`); +}; + +/** + * Add DIY page + */ +export const addDiyPage = (data: DiyPageForm) => { + return request.post('adminapi/diy/diy', data); +}; + +/** + * Edit DIY page + */ +export const editDiyPage = (id: number, data: DiyPageForm) => { + return request.put(`adminapi/diy/diy/${id}`, data); +}; + +/** + * Delete DIY page + */ +export const deleteDiyPage = (id: number) => { + return request.delete(`adminapi/diy/diy/${id}`); +}; + +/** + * Set use DIY page + */ +export const setUseDiyPage = (id: number) => { + return request.put(`adminapi/diy/diy/${id}/use`); +}; + +/** + * Copy DIY page + */ +export const copyDiyPage = (id: number) => { + return request.post(`adminapi/diy/diy/${id}/copy`); +}; + +/** + * Edit DIY page share settings + */ +export const editDiyPageShare = (id: number, data: DiyShareForm) => { + return request.put(`adminapi/diy/diy/${id}/share`, data); +}; + +/** + * Get DIY template + */ +export const getDiyTemplate = (params: { addon: string }) => { + return request.get('adminapi/diy/template', { params }); +}; + +/** + * Get DIY template pages + */ +export const getDiyTemplatePages = (params: { type: string; mode: string }) => { + return request.get('adminapi/diy/template/pages', { params }); +}; + +/** + * Initialize DIY page + */ +export const initDiyPage = (params: any) => { + return request.post('adminapi/diy/init', params); +}; + +/** + * Get decorate page + */ +export const getDecoratePage = (params: any) => { + return request.get('adminapi/diy/decorate', { params }); +}; + +/** + * Change template + */ +export const changeTemplate = (params: any) => { + return request.post('adminapi/diy/template/change', params); +}; + +/** + * Get DIY bottom list + */ +export const getDiyBottomList = (params: any) => { + return request.get('adminapi/diy/bottom', { params }); +}; + +/** + * Get DIY bottom config + */ +export const getDiyBottomConfig = (key: string) => { + return request.get(`adminapi/diy/bottom/${key}`); +}; + +/** + * Set DIY bottom config + */ +export const setDiyBottomConfig = (key: string, data: any) => { + return request.put(`adminapi/diy/bottom/${key}`, data); +}; + +/** + * Get DIY route list + */ +export const getDiyRouteList = (params: any) => { + return request.get('adminapi/diy/route', { params }); +}; + +/** + * Get DIY route apps + */ +export const getDiyRouteAppList = () => { + return request.get('adminapi/diy/route/apps'); +}; + +/** + * Edit DIY route share + */ +export const editDiyRouteShare = (id: number, data: DiyShareForm) => { + return request.put(`adminapi/diy/route/${id}/share`, data); +}; + +/** + * Get installed addon list + */ +export const getInstalledAddonList = () => { + return request.get('adminapi/addon/installed'); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/menu.ts b/admin-vben/apps/web-antd/src/api/core/menu.ts index 9ef60b11..9cab41d5 100644 --- a/admin-vben/apps/web-antd/src/api/core/menu.ts +++ b/admin-vben/apps/web-antd/src/api/core/menu.ts @@ -1,10 +1,57 @@ -import type { RouteRecordStringComponent } from '@vben/types'; - -import { requestClient } from '#/api/request'; +import type { MenuListQuery, MenuForm } from './model/menuModel'; /** - * 获取用户所有菜单 + * 获取菜单列表 */ -export async function getAllMenusApi() { - return requestClient.get('/menu/all'); -} +export const getMenusApi = (appType: string) => { + return request.get(`/adminapi/sys/menu/${appType}`); +}; + +/** + * 获取菜单详情 + */ +export const getMenuInfoApi = (appType: string, menuKey: string) => { + return request.get(`/adminapi/sys/menu/${appType}/${menuKey}`); +}; + +/** + * 添加菜单 + */ +export const addMenuApi = (data: MenuForm) => { + return request.post('/adminapi/sys/menu', data); +}; + +/** + * 编辑菜单 + */ +export const editMenuApi = (data: MenuForm) => { + return request.put('/adminapi/sys/menu', data); +}; + +/** + * 删除菜单 + */ +export const deleteMenuApi = (appType: string, menuKey: string) => { + return request.delete(`/adminapi/sys/menu/${appType}/${menuKey}`); +}; + +/** + * 刷新菜单 + */ +export const menuRefreshApi = (data: any) => { + return request.post('/adminapi/sys/menu/refresh', data); +}; + +/** + * 获取系统菜单 + */ +export const getSystemMenuApi = () => { + return request.get('/adminapi/sys/menu/system'); +}; + +/** + * 获取插件菜单 + */ +export const getAddonMenuApi = (addon: string) => { + return request.get(`/adminapi/sys/menu/addon/${addon}`); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/model/diyModel.ts b/admin-vben/apps/web-antd/src/api/core/model/diyModel.ts new file mode 100644 index 00000000..c9e3448c --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/model/diyModel.ts @@ -0,0 +1,90 @@ +/** + * DIY page form interface + */ +export interface DiyPageForm { + title: string; + type: string; + addon_name?: string; + value?: any; + global?: any; +} + +/** + * DIY share form interface + */ +export interface DiyShareForm { + wechat?: { + share_title: string; + share_desc: string; + share_image?: string; + }; + weapp?: { + share_title: string; + share_desc: string; + share_image?: string; + }; +} + +/** + * DIY page interface + */ +export interface DiyPage { + id: number; + title: string; + type: string; + type_name: string; + addon_name: string; + addon_title: string; + is_use: number; + share: any; + create_time: string; + update_time: string; +} + +/** + * DIY template interface + */ +export interface DiyTemplate { + key: string; + title: string; + type: string; + type_name: string; + icon: string; + support_app: string[]; +} + +/** + * DIY bottom config interface + */ +export interface DiyBottomConfig { + list: DiyBottomItem[]; + style: { + type: string; + backgroundColor: string; + textColor: string; + activeTextColor: string; + }; +} + +/** + * DIY bottom item interface + */ +export interface DiyBottomItem { + text: string; + link: string; + iconPath: string; + selectedIconPath: string; +} + +/** + * DIY route interface + */ +export interface DiyRoute { + id: number; + title: string; + addon_name: string; + addon_title: string; + wap_url: string; + weapp_path: string; + share: any; +} \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/model/menuModel.ts b/admin-vben/apps/web-antd/src/api/core/model/menuModel.ts new file mode 100644 index 00000000..03058982 --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/model/menuModel.ts @@ -0,0 +1,68 @@ +/** + * 菜单列表查询参数 + */ +export interface MenuListQuery { + app_type?: string; +} + +/** + * 菜单表单数据 + */ +export interface MenuForm { + id?: number; + menu_name: string; + menu_key?: string; + menu_type: number; + parent_key?: string; + icon?: string; + api_url?: string; + router_path?: string; + view_path?: string; + methods?: string; + sort?: number; + status: number; + is_show: number; + app_type?: string; + addon?: string; + menu_short_name?: string; +} + +/** + * 菜单项 + */ +export interface MenuItem { + menu_key: string; + menu_name: string; + menu_type: number; + parent_key: string; + icon?: string; + api_url?: string; + router_path?: string; + view_path?: string; + methods?: string; + sort: number; + status: number; + is_show: number; + menu_short_name?: string; + children?: MenuItem[]; +} + +/** + * 菜单树 + */ +export interface MenuTree { + menu_key: string; + menu_name: string; + menu_type: number; + parent_key: string; + icon?: string; + api_url?: string; + router_path?: string; + view_path?: string; + methods?: string; + sort: number; + status: number; + is_show: number; + menu_short_name?: string; + children?: MenuTree[]; +} \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/model/siteModel.ts b/admin-vben/apps/web-antd/src/api/core/model/siteModel.ts new file mode 100644 index 00000000..fa408300 --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/model/siteModel.ts @@ -0,0 +1,74 @@ +/** + * 站点列表查询参数 + */ +export interface SiteListQuery { + page?: number; + limit?: number; + keywords?: string; + site_domain?: string; + app?: string; + group_id?: string; + status?: string; + create_time?: string[]; + expire_time?: string[]; +} + +/** + * 站点表单数据 + */ +export interface SiteForm { + site_id?: number; + site_name: string; + site_domain: string; + logo?: string; + group_id: number; + status: number; + expire_time?: string; + admin?: { + username: string; + password: string; + }; +} + +/** + * 站点分组表单数据 + */ +export interface SiteGroupForm { + group_id?: number; + group_name: string; + remark?: string; + sort: number; + status: number; +} + +/** + * 站点项 + */ +export interface SiteItem { + site_id: number; + site_name: string; + site_domain: string; + logo: string; + group_id: number; + group_name: string; + status: number; + status_name: string; + create_time: string; + expire_time: string; + admin: { + username: string; + }; +} + +/** + * 站点分组项 + */ +export interface SiteGroupItem { + group_id: number; + group_name: string; + remark: string; + sort: number; + status: number; + status_name: string; + create_time: string; +} \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/role.ts b/admin-vben/apps/web-antd/src/api/core/role.ts new file mode 100644 index 00000000..90aaded5 --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/role.ts @@ -0,0 +1,63 @@ +import type { AxiosResponse } from 'axios'; + +import { requestClient } from '#/api/request'; + +/** + * 获取角色列表 + */ +export function getRoleListApi(params: { + page: number; + limit: number; + role_name?: string; +}): Promise> { + return requestClient.get('sys/role', { params }); +} + +/** + * 获取角色详情 + */ +export function getRoleInfoApi(roleId: number): Promise> { + return requestClient.get(`sys/role/${roleId}`); +} + +/** + * 添加角色 + */ +export function addRoleApi(params: Record): Promise> { + return requestClient.post('sys/role', params, { showSuccessMessage: true }); +} + +/** + * 编辑角色 + */ +export function editRoleApi(params: Record): Promise> { + return requestClient.put(`sys/role/${params.role_id}`, params, { showSuccessMessage: true }); +} + +/** + * 删除角色 + */ +export function deleteRoleApi(roleId: number): Promise> { + return requestClient.delete(`sys/role/${roleId}`, { showSuccessMessage: true }); +} + +/** + * 修改角色状态 + */ +export function modifyRoleStatusApi(params: { role_id: number; status: number }): Promise> { + return requestClient.put('sys/role/status', params, { showSuccessMessage: true }); +} + +/** + * 获取全部角色 + */ +export function getAllRoleApi(): Promise> { + return requestClient.get('sys/role/all'); +} + +/** + * 获取站点菜单 + */ +export function getSiteMenusApi(): Promise> { + return requestClient.get('site/site/menu'); +} \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/site.ts b/admin-vben/apps/web-antd/src/api/core/site.ts new file mode 100644 index 00000000..b5ec8f78 --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/site.ts @@ -0,0 +1,125 @@ +import type { SiteListQuery, SiteForm, SiteGroupForm } from './model/siteModel'; +import { requestClient as request } from '#/api/request'; + +/** + * Site management API + */ + +/** + * Get site list + */ +export const getSiteList = (params: SiteListQuery) => { + return request.get('adminapi/site/site', { params }); +}; + +/** + * Get site info + */ +export const getSiteInfo = (siteId: number) => { + return request.get(`adminapi/site/site/${siteId}`); +}; + +/** + * Add site + */ +export const addSite = (data: SiteForm) => { + return request.post('adminapi/site/site', data); +}; + +/** + * Edit site + */ +export const editSite = (siteId: number, data: SiteForm) => { + return request.put(`adminapi/site/site/${siteId}`, data); +}; + +/** + * Delete site + */ +export const deleteSite = (siteId: number, captchaCode: string) => { + return request.delete(`adminapi/site/site/${siteId}?captcha_code=${captchaCode}`); +}; + +/** + * Modify site status + */ +export const modifySiteStatus = (siteId: number, status: number) => { + return request.put(`adminapi/site/site/${siteId}/status`, { status }); +}; + +/** + * Initialize site + */ +export const initSite = (siteId: number, captchaCode: string) => { + return request.post('adminapi/site/init', { site_id: siteId, captcha_code: captchaCode }); +}; + +/** + * Get site captcha + */ +export const getSiteCaptcha = () => { + return request.get('adminapi/site/captcha'); +}; + +/** + * Switch site + */ +export const switchSite = (siteId: number) => { + return request.post('adminapi/site/switch', { site_id: siteId }); +}; + +/** + * Get site group list + */ +export const getSiteGroupList = (params: SiteListQuery) => { + return request.get('adminapi/site/group', { params }); +}; + +/** + * Get all site groups + */ +export const getSiteGroupAll = () => { + return request.get('adminapi/site/group/all'); +}; + +/** + * Add site group + */ +export const addSiteGroup = (data: SiteGroupForm) => { + return request.post('adminapi/site/group', data); +}; + +/** + * Edit site group + */ +export const editSiteGroup = (groupId: number, data: SiteGroupForm) => { + return request.put(`adminapi/site/group/${groupId}`, data); +}; + +/** + * Delete site group + */ +export const deleteSiteGroup = (groupId: number) => { + return request.delete(`adminapi/site/group/${groupId}`); +}; + +/** + * Get site users + */ +export const getSiteUsers = (siteId: number) => { + return request.get(`adminapi/site/${siteId}/users`); +}; + +/** + * Add site user + */ +export const addSiteUser = (siteId: number, data: { user_id: number; role_id: number }) => { + return request.post(`adminapi/site/${siteId}/user`, data); +}; + +/** + * Remove site user + */ +export const removeSiteUser = (siteId: number, userId: number) => { + return request.delete(`adminapi/site/${siteId}/user/${userId}`); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/tools.ts b/admin-vben/apps/web-antd/src/api/core/tools.ts new file mode 100644 index 00000000..5040e521 --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/tools.ts @@ -0,0 +1,76 @@ +/** + * 获取插件列表 + */ +export const getAddonDevelopApi = (params: Record) => { + return request.get('adminapi/tools/addon_develop', { params }); +}; + +/** + * 获取插件类型配置 + */ +export const getAddontypeApi = () => { + return request.get('adminapi/tools/addontype'); +}; + +/** + * 获取插件详情 + */ +export const getAddonDevelopInfoApi = (key: string) => { + return request.get(`adminapi/tools/addon_develop/${key}`); +}; + +/** + * 获取插件key是否存在 + */ +export const getAddonDevelopCheckApi = (key: string) => { + return request.get(`adminapi/tools/addon_develop/check/${key}`); +}; + +/** + * 获取插件key黑名单 + */ +export const getAddonKeyBlackListApi = () => { + return request.get('adminapi/tools/addon_develop/key/blacklist'); +}; + +/** + * 添加插件 + */ +export const addAddonDevelopApi = (key: string, params: Record) => { + return request.post(`adminapi/tools/addon_develop/${key}`, params); +}; + +/** + * 编辑插件 + */ +export const editAddonDevelopApi = (key: string, params: Record) => { + return request.put(`adminapi/tools/addon_develop/${key}`, params); +}; + +/** + * 删除插件 + */ +export const deleteAddonDevelopApi = (key: string) => { + return request.delete(`adminapi/tools/addon_develop/${key}`); +}; + +/** + * 安装插件 + */ +export const installAddonDevelopApi = (key: string) => { + return request.post(`adminapi/tools/addon_develop/${key}/install`); +}; + +/** + * 卸载插件 + */ +export const uninstallAddonDevelopApi = (key: string) => { + return request.post(`adminapi/tools/addon_develop/${key}/uninstall`); +}; + +/** + * 设计插件 + */ +export const designAddonDevelopApi = (key: string) => { + return request.post(`adminapi/tools/addon_develop/${key}/design`); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/user.ts b/admin-vben/apps/web-antd/src/api/core/user.ts index 64ed4e6f..5dc5d818 100644 --- a/admin-vben/apps/web-antd/src/api/core/user.ts +++ b/admin-vben/apps/web-antd/src/api/core/user.ts @@ -1,4 +1,37 @@ -import type { AxiosResponse } from 'axios'; +export interface User { + uid: number; + username: string; + nickname: string; + email: string; + phone: string; + avatar: string; + user_type: string; + status: number; + create_time: number; + last_login_time: number; + site_id: number; + role_ids: number[]; +} + +export interface UserListResponse { + list: User[]; + total: number; + page: number; + limit: number; +} + +export interface UserForm { + uid?: number; + username: string; + nickname: string; + email: string; + phone: string; + password?: string; + user_type: string; + status: number; + role_ids: number[]; + avatar?: string; +} import { requestClient } from '#/api/request'; @@ -10,48 +43,62 @@ export function getUserListApi(params: { limit: number; username?: string; user_type?: string; -}): Promise> { +}): Promise { return requestClient.get('site/user', { params }); } /** * 获取用户详情 */ -export function getUserInfoApi(userId: number): Promise> { +export function getUserInfoApi(userId: number): Promise { return requestClient.get(`site/user/${userId}`); } /** * 添加用户 */ -export function addUserApi(params: Record): Promise> { +export function addUserApi(params: UserForm): Promise { return requestClient.post('site/user', params, { showSuccessMessage: true }); } /** * 编辑用户 */ -export function editUserApi(params: Record): Promise> { +export function editUserApi(params: UserForm): Promise { return requestClient.put(`site/user/${params.uid}`, params, { showSuccessMessage: true }); } /** * 锁定用户 */ -export function lockUserApi(userId: number): Promise> { +export function lockUserApi(userId: number): Promise { return requestClient.put(`site/user/lock/${userId}`); } /** * 解锁用户 */ -export function unlockUserApi(userId: number): Promise> { +export function unlockUserApi(userId: number): Promise { return requestClient.put(`site/user/unlock/${userId}`); } +/** + * 获取用户列表选择器 + */ +export function getUserListSelect(params: { keyword?: string }): Promise { + return requestClient.get('site/user_select', { params }); +} + /** * 删除用户 */ -export function deleteUserApi(userId: number): Promise> { +export function deleteUserApi(userId: number): Promise { return requestClient.delete(`site/user/${userId}`); +} + +/** + * 获取当前用户信息 + */ +export function getCurrentUserApi(): Promise { + return requestClient.get('auth/user'); } \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/weapp.ts b/admin-vben/apps/web-antd/src/api/core/weapp.ts new file mode 100644 index 00000000..44d4263b --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/weapp.ts @@ -0,0 +1,156 @@ +import { requestClient as request } from '#/api/request'; + +enum Api { + GetWeappConfig = '/weapp/config', + SetWeappConfig = '/weapp/config', + GetTemplateList = '/weapp/template', + GetBatchAcquisition = '/weapp/template/batch', + SetWeappVersion = '/weapp/version', + GetWeappVersionList = '/weapp/version', + GetWeappUploadLog = '/weapp/upload/log', + GetWeappPreview = '/weapp/preview', + UploadVersion = '/weapp/version/upload', + SetWeappDomain = '/weapp/domain', + GetWeappPrivacySetting = '/weapp/privacy', + SetWeappPrivacySetting = '/weapp/privacy', + GetIsTradeManaged = '/weapp/trade/managed', +} + +export interface WeappConfig { + weapp_name: string; + weapp_original: string; + app_id: string; + app_secret: string; + qr_code: string; + serve_url: string; + token: string; + encoding_aes_key: string; + encryption_type: number; + is_authorization: number; + domain: string; + request_domain: string; + ws_request_domain: string; + upload_domain: string; + download_domain: string; + udp_domain: string; + tcp_domain: string; +} + +export interface TemplateItem { + id: number; + site_id: number; + addon: string; + template_id: string; + title: string; + content: string; + example: string; + status: number; + create_time: number; +} + +export interface VersionItem { + id: number; + site_id: number; + version: string; + version_desc: string; + upload_time: number; + audit_time: number; + audit_result: string; + audit_id: string; + status: number; + task_key: string; + create_time: number; +} + +export interface UploadLog { + task_key: string; + status: number; + percent: number; + message: string; + create_time: number; +} + +export interface PrivacySetting { + owner_setting: { + contact_email: string; + contact_phone: string; + contact_qq: string; + contact_weixin: string; + store_expire_timestamp: string; + }; + setting_list: Array<{ + privacy_key: string; + privacy_text: string; + }>; + sdk_privacy_info_list: Array<{ + sdk_name: string; + sdk_biz: string; + privacy_key_list: string[]; + }>; +} + +export const getWeappConfig = () => { + return request.get(Api.GetWeappConfig); +}; + +export const setWeappConfig = (data: Partial) => { + return request.post(Api.SetWeappConfig, data); +}; + +export const getTemplateList = (params: { addon?: string }) => { + return request.get(Api.GetTemplateList, { params }); +}; + +export const getBatchAcquisition = (params: { addon?: string }) => { + return request.post(Api.GetBatchAcquisition, params); +}; + +export const setWeappVersion = (data: { version: string; version_desc: string; authorization_code?: string }) => { + return request.post(Api.SetWeappVersion, data); +}; + +export const getWeappVersionList = (params: { page?: number; limit?: number }) => { + return request.get<{ list: VersionItem[]; total: number }>(Api.GetWeappVersionList, { params }); +}; + +export const getWeappUploadLog = (params: { task_key: string }) => { + return request.get(Api.GetWeappUploadLog, { params }); +}; + +export const getWeappPreview = () => { + return request.get<{ preview_url: string }>(Api.GetWeappPreview); +}; + +export const uploadVersion = (data: { file: File; version: string }) => { + const formData = new FormData(); + formData.append('file', data.file); + formData.append('version', data.version); + return request.post(Api.UploadVersion, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +}; + +export const setWeappDomain = (data: { + request_domain: string; + ws_request_domain: string; + upload_domain: string; + download_domain: string; + udp_domain: string; + tcp_domain: string; +}) => { + return request.post(Api.SetWeappDomain, data); +}; + +export const getWeappPrivacySetting = () => { + return request.get(Api.GetWeappPrivacySetting); +}; + +export const setWeappPrivacySetting = (data: PrivacySetting) => { + return request.post(Api.SetWeappPrivacySetting, data); +}; + +export const getIsTradeManaged = () => { + return request.get<{ is_trade_managed: number }>(Api.GetIsTradeManaged); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/wechat.ts b/admin-vben/apps/web-antd/src/api/core/wechat.ts new file mode 100644 index 00000000..00aeaa5d --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/wechat.ts @@ -0,0 +1,275 @@ +import { requestClient as request } from '#/api/request'; + +/** + * 微信公众号配置接口 + */ + +// 获取公众号配置 +export const getWechatConfigApi = () => { + return request.get('/adminapi/wechat/config'); +}; + +// 保存公众号配置 +export const saveWechatConfigApi = (data: { + app_id: string; + app_secret: string; + token: string; + encoding_aes_key: string; + encryption_type: number; + qr_code: string; +}) => { + return request.post('/adminapi/wechat/config', data); +}; + +// 获取服务器配置 +export const getServerConfigApi = () => { + return request.get('/adminapi/wechat/server'); +}; + +// 保存服务器配置 +export const saveServerConfigApi = (data: { + serve_url: string; + token: string; + encoding_aes_key: string; + encryption_type: number; +}) => { + return request.post('/adminapi/wechat/server', data); +}; + +// 获取域名配置 +export const getDomainConfigApi = () => { + return request.get('/adminapi/wechat/domain'); +}; + +// 保存域名配置 +export const saveDomainConfigApi = (data: { + request_domain: string[]; + ws_request_domain: string[]; + upload_domain: string[]; + download_domain: string[]; +}) => { + return request.post('/adminapi/wechat/domain', data); +}; + +// 获取隐私配置 +export const getPrivacyConfigApi = () => { + return request.get('/adminapi/wechat/privacy'); +}; + +// 保存隐私配置 +export const savePrivacyConfigApi = (data: { + owner_setting: { + contact_email: string; + contact_phone: string; + contact_qq: string; + contact_weixin: string; + store_expire_timestamp: string; + }; + setting_list: Array<{ + privacy_key: string; + privacy_text: string; + }>; +}) => { + return request.post('/adminapi/wechat/privacy', data); +}; + +// 获取消息模板列表 +export const getTemplateListApi = (params: { page?: number; limit?: number }) => { + return request.get('/adminapi/wechat/template', { params }); +}; + +// 同步消息模板 +export const syncTemplateApi = () => { + return request.post('/adminapi/wechat/template/sync'); +}; + +// 启用/禁用消息模板 +export const modifyTemplateStatusApi = (id: number, status: number) => { + return request.put(`/adminapi/wechat/template/status/${id}`, { status }); +}; + +// 获取自定义菜单 +export const getCustomMenuApi = () => { + return request.get('/adminapi/wechat/menu'); +}; + +// 保存自定义菜单 +export const saveCustomMenuApi = (data: { + button: Array<{ + type: string; + name: string; + key?: string; + url?: string; + media_id?: string; + sub_button?: Array; + }>; +}) => { + return request.post('/adminapi/wechat/menu', data); +}; + +// 获取二维码列表 +export const getQrcodeListApi = (params: { page?: number; limit?: number }) => { + return request.get('/adminapi/wechat/qrcode', { params }); +}; + +// 创建二维码 +export const createQrcodeApi = (data: { + action_name: string; + action_info: { + scene: { + scene_id?: number; + scene_str?: string; + }; + }; + expire_seconds?: number; +}) => { + return request.post('/adminapi/wechat/qrcode', data); +}; + +// 删除二维码 +export const deleteQrcodeApi = (id: number) => { + return request.delete(`/adminapi/wechat/qrcode/${id}`); +}; + +// 获取用户标签列表 +export const getUserTagListApi = (params: { page?: number; limit?: number }) => { + return request.get('/adminapi/wechat/user/tag', { params }); +}; + +// 同步用户标签 +export const syncUserTagApi = () => { + return request.post('/adminapi/wechat/user/tag/sync'); +}; + +// 获取用户列表 +export const getUserListApi = (params: { page?: number; limit?: number; nickname?: string; tag_id?: number }) => { + return request.get('/adminapi/wechat/user', { params }); +}; + +// 同步用户 +export const syncUserApi = () => { + return request.post('/adminapi/wechat/user/sync'); +}; + +// 获取用户详情 +export const getUserDetailApi = (openid: string) => { + return request.get(`/adminapi/wechat/user/${openid}`); +}; + +// 获取素材列表 +export const getMaterialListApi = (params: { page?: number; limit?: number; type: string }) => { + return request.get('/adminapi/wechat/material', { params }); +}; + +// 同步素材 +export const syncMaterialApi = () => { + return request.post('/adminapi/wechat/material/sync'); +}; + +// 上传素材 +export const uploadMaterialApi = (data: FormData) => { + return request.post('/adminapi/wechat/material/upload', data); +}; + +// 删除素材 +export const deleteMaterialApi = (id: number) => { + return request.delete(`/adminapi/wechat/material/${id}`); +}; + +// 获取图文消息列表 +export const getNewsListApi = (params: { page?: number; limit?: number }) => { + return request.get('/adminapi/wechat/news', { params }); +}; + +// 创建图文消息 +export const createNewsApi = (data: { + title: string; + author: string; + digest: string; + show_cover_pic: number; + content: string; + content_source_url: string; + thumb_media_id: string; +}) => { + return request.post('/adminapi/wechat/news', data); +}; + +// 编辑图文消息 +export const editNewsApi = (id: number, data: { + title: string; + author: string; + digest: string; + show_cover_pic: number; + content: string; + content_source_url: string; + thumb_media_id: string; +}) => { + return request.put(`/adminapi/wechat/news/${id}`, data); +}; + +// 删除图文消息 +export const deleteNewsApi = (id: number) => { + return request.delete(`/adminapi/wechat/news/${id}`); +}; + +// 获取自动回复规则 +export const getAutoReplyApi = () => { + return request.get('/adminapi/wechat/reply'); +}; + +// 保存自动回复规则 +export const saveAutoReplyApi = (data: { + is_open: number; + reply_type: string; + reply_content: string; + media_id?: string; +}) => { + return request.post('/adminapi/wechat/reply', data); +}; + +// 获取关键词回复列表 +export const getKeywordReplyListApi = (params: { page?: number; limit?: number }) => { + return request.get('/adminapi/wechat/keyword', { params }); +}; + +// 添加关键词回复 +export const addKeywordReplyApi = (data: { + rule_name: string; + keyword_list: Array<{ + keyword: string; + match_type: string; + }>; + reply_list: Array<{ + reply_type: string; + reply_content: string; + media_id?: string; + }>; +}) => { + return request.post('/adminapi/wechat/keyword', data); +}; + +// 编辑关键词回复 +export const editKeywordReplyApi = (id: number, data: { + rule_name: string; + keyword_list: Array<{ + keyword: string; + match_type: string; + }>; + reply_list: Array<{ + reply_type: string; + reply_content: string; + media_id?: string; + }>; +}) => { + return request.put(`/adminapi/wechat/keyword/${id}`, data); +}; + +// 删除关键词回复 +export const deleteKeywordReplyApi = (id: number) => { + return request.delete(`/adminapi/wechat/keyword/${id}`); +}; + +// 修改关键词回复状态 +export const modifyKeywordReplyStatusApi = (id: number, status: number) => { + return request.put(`/adminapi/wechat/keyword/status/${id}`, { status }); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/core/wxoplatform.ts b/admin-vben/apps/web-antd/src/api/core/wxoplatform.ts new file mode 100644 index 00000000..6e1e6a6b --- /dev/null +++ b/admin-vben/apps/web-antd/src/api/core/wxoplatform.ts @@ -0,0 +1,38 @@ +import request from '@/utils/request'; + +enum Api { + GetAuthorizationUrl = '/wxoplatform/authorization/url', + SiteWeappCommit = '/wxoplatform/site/weapp/commit', + UndoAudit = '/wxoplatform/site/weapp/undo/audit', +} + +export interface AuthorizationUrlParams { + site_id?: number; +} + +export interface AuthorizationUrlResponse { + url: string; +} + +export interface SiteWeappCommitParams { + site_id: number; + version: string; + version_desc: string; +} + +export interface UndoAuditParams { + site_id: number; + audit_id: string; +} + +export const getAuthorizationUrl = (params: AuthorizationUrlParams) => { + return request.get(Api.GetAuthorizationUrl, { params }); +}; + +export const siteWeappCommit = (data: SiteWeappCommitParams) => { + return request.post(Api.SiteWeappCommit, data); +}; + +export const undoAudit = (data: UndoAuditParams) => { + return request.post(Api.UndoAudit, data); +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/index.ts b/admin-vben/apps/web-antd/src/api/index.ts index 6af38a47..6fa5bae0 100644 --- a/admin-vben/apps/web-antd/src/api/index.ts +++ b/admin-vben/apps/web-antd/src/api/index.ts @@ -1,2 +1,6 @@ export * from './core/auth'; -export * from './core/user'; \ No newline at end of file +export * from './core/user'; +export * from './core/role'; +export * from './core/menu'; +export * from './core/site'; +export * from './core/diy'; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/api/request.ts b/admin-vben/apps/web-antd/src/api/request.ts index e741552d..345af4d8 100644 --- a/admin-vben/apps/web-antd/src/api/request.ts +++ b/admin-vben/apps/web-antd/src/api/request.ts @@ -27,7 +27,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ ...options, baseURL, - transformResponse: (data: any, header: AxiosResponseHeaders) => { + transformResponse: (data: string | object, header: AxiosResponseHeaders) => { // storeAsString指示将BigInt存储为字符串,设为false则会存储为内置的BigInt类型 return header.getContentType()?.toString().includes('application/json') ? cloneDeep( @@ -123,7 +123,7 @@ export const requestClient = createRequestClient(apiURL, { export const baseRequestClient = new RequestClient({ baseURL: apiURL }); export interface PageFetchParams { - [key: string]: any; + [key: string]: string | number | boolean | undefined; pageNo?: number; pageSize?: number; } diff --git a/admin-vben/apps/web-antd/src/locales/langs/en-US/system.json b/admin-vben/apps/web-antd/src/locales/langs/en-US/system.json index 003dfbbe..ceeaf2c5 100644 --- a/admin-vben/apps/web-antd/src/locales/langs/en-US/system.json +++ b/admin-vben/apps/web-antd/src/locales/langs/en-US/system.json @@ -1,5 +1,18 @@ { "title": "System Management", + "role": { + "title": "Role Management", + "list": "Role List", + "name": "Role", + "roleName": "Role Name", + "id": "Role ID", + "status": "Status", + "remark": "Remark", + "createTime": "Creation Time", + "operation": "Operation", + "permissions": "Permissions", + "setPermissions": "Permissions" + }, "dept": { "name": "Department", "title": "Department Management", @@ -12,22 +25,39 @@ }, "menu": { "title": "Menu Management", + "addMenu": "Add Menu", + "editMenu": "Edit Menu", "parent": "Parent Menu", "menuTitle": "Title", "menuName": "Menu Name", + "menuNamePlaceholder": "Please enter menu name", + "menuKey": "Menu Key", + "menuKeyPlaceholder": "Please enter menu key", + "menuKeyValidata": "Menu key format is incorrect", "name": "Menu", "type": "Type", + "menuType": "Menu Type", + "menuTypeDir": "Directory", + "menuTypeMenu": "Menu", + "menuTypeButton": "Button", "typeCatalog": "Catalog", "typeMenu": "Menu", "typeButton": "Button", "typeLink": "Link", "typeEmbedded": "Embedded", "icon": "Icon", + "menuIcon": "Menu Icon", "activeIcon": "Active Icon", "activePath": "Active Path", "path": "Route Path", + "routePath": "Route Path", + "routePathPlaceholder": "Please enter route path", + "viewPath": "View Path", + "viewPathPlaceholder": "Please enter view path", "component": "Component", "status": "Status", + "authId": "Auth ID", + "authIdPlaceholder": "Please enter auth ID", "authCode": "Auth Code", "badge": "Badge", "operation": "Operation", @@ -47,19 +77,455 @@ "normal": "Text", "none": "None" }, - "badgeVariants": "Badge Style" + "badgeVariants": "Badge Style", + "addon": "Addon", + "parentMenu": "Parent Menu", + "menuShortName": "Short Name", + "menuShortNamePlaceholder": "Please enter menu short name", + "isShow": "Show", + "show": "Show", + "hide": "Hide", + "sort": "Sort", + "initializeMenu": "Initialize Menu", + "initializeMenuTipsOne": "This operation will reset all menu data", + "initializeMenuTipsTwo": "Are you sure you want to continue?", + "initializeSuccess": "Menu initialized successfully", + "initializeError": "Failed to initialize menu", + "loadError": "Failed to load menu list", + "loadMenuError": "Failed to load menu data", + "loadAddonError": "Failed to load addon data", + "loadMenuInfoError": "Failed to load menu information", + "saveError": "Failed to save menu", + "addSuccess": "Menu added successfully", + "editSuccess": "Menu updated successfully", + "deleteSuccess": "Menu deleted successfully", + "deleteError": "Failed to delete menu", + "deleteConfirm": "Are you sure you want to delete this menu?" }, - "role": { - "title": "Role Management", - "list": "Role List", - "name": "Role", - "roleName": "Role Name", - "id": "Role ID", - "status": "Status", - "remark": "Remark", + "group": { + "title": "Site Group", + "groupId": "Group ID", + "groupName": "Group Name", + "groupDesc": "Group Description", + "mainApp": "Main Application", + "containAddon": "Include Addons", + "appName": "Application Name", + "addonName": "Addon Name", "createTime": "Creation Time", - "operation": "Operation", - "permissions": "Permissions", - "setPermissions": "Permissions" + "addGroup": "Add Group", + "editGroup": "Edit Group", + "deleteConfirm": "Are you sure you want to delete this group?", + "deleteSuccess": "Group deleted successfully", + "deleteError": "Failed to delete group", + "saveSuccess": "Group saved successfully", + "saveError": "Failed to save group", + "groupNamePlaceholder": "Please enter group name", + "groupDescPlaceholder": "Please enter group description", + "mainAppPlaceholder": "Please select main application", + "containAddonPlaceholder": "Please select included addons", + "appListEmpty": "No applications", + "addonListEmpty": "No addons", + "moreApps": "+{count} more applications", + "moreAddons": "+{count} more addons", + "selectAppFirst": "Please select the corresponding main application first", + "addonRemovedDueToAppDependency": "Some addons have been automatically removed due to dependency relationships" + }, + "diy": { + "title": "DIY Decoration", + "decorating": "Decorating", + "list": { + "title": "Custom Pages", + "pageId": "Page ID", + "pageTitle": "Page Title", + "pageTitlePlaceholder": "Please enter page title", + "addonName": "Belonging App", + "pageType": "Page Type", + "pageTypePlaceholder": "Please select page type", + "status": "Status", + "updateTime": "Update Time", + "addPage": "Add Page", + "preview": "Preview", + "shareSetting": "Share Settings", + "copy": "Copy", + "copySuccess": "Page copied successfully", + "copyError": "Failed to copy page", + "deleteConfirm": "Are you sure you want to delete this page?", + "deleteSuccess": "Page deleted successfully", + "deleteError": "Failed to delete page", + "setUseSuccess": "Set successfully", + "setUseError": "Failed to set", + "cannotSetUseDiyPage": "DIY_PAGE type pages cannot be set as in use", + "shareTitle": "Share Title", + "shareTitlePlaceholder": "Please enter share title", + "shareDesc": "Share Description", + "shareDescPlaceholder": "Please enter share description", + "shareImage": "Share Image", + "wechatShare": "WeChat Official Account Share", + "weappShare": "WeChat Mini Program Share" + }, + "design": { + "templatePagePlaceholder": "Select Template Page", + "templatePageEmpty": "Empty Template", + "pageSet": "Page Settings", + "moveUpComponent": "Move Up Component", + "moveDownComponent": "Move Down Component", + "copyComponent": "Copy Component", + "delComponent": "Delete Component", + "resetComponent": "Reset Component", + "delComponentTips": "Are you sure you want to delete this component?", + "changeTemplatePageTips": "Switching template page will clear current page content, are you sure you want to continue?", + "developTitle": "Development Mode Configuration", + "wapDomain": "WAP Domain", + "wapDomainPlaceholder": "Please enter WAP domain, e.g.: https://www.example.com", + "settingTips": "Configuration Instructions", + "initPageError": "Failed to initialize page", + "tabEditContent": "Content", + "tabEditStyle": "Style", + "pageSettings": "Page Settings", + "pageName": "Page Name", + "pageNamePlaceholder": "Please enter page name", + "pageMode": "Page Mode", + "style1": "Single Column Layout", + "style2": "Left-Right Layout", + "alignment": "Alignment", + "alignLeft": "Left Align", + "alignRight": "Right Align", + "borderControl": "Border Control", + "pageBackground": "Page Background", + "bgColor": "Background Color", + "bgColorTips": "Left is start color, right is end color", + "gradientAngle": "Gradient Angle", + "topToBottom": "Top to Bottom", + "leftToRight": "Left to Right", + "bgImage": "Background Image", + "bgHeightScale": "Background Height Scale", + "bgHeightScaleTips": "0 for auto height", + "topStatusBar": "Top Status Bar", + "showStatusBar": "Show Status Bar", + "statusBarStyle": "Status Bar Style", + "style1Text": "Text Only", + "style2ImageText": "Image + Text", + "style3ImageSearch": "Image + Search", + "style4Location": "Location", + "textAlign": "Text Alignment", + "alignCenter": "Center", + "logoImage": "Logo Image", + "searchPlaceholder": "Search Placeholder", + "searchPlaceholderTips": "Please enter search placeholder text", + "link": "Jump Link", + "linkPlaceholder": "Please select jump link", + "popWindow": "Popup Settings", + "showPopWindow": "Show Popup", + "neverShow": "Never Show", + "showOnce": "Show Once", + "showAlways": "Show Always", + "popImage": "Popup Image", + "popLink": "Popup Link", + "popLinkPlaceholder": "Please select popup jump link", + "leavePageContentTips": "Page content has been modified, are you sure you want to leave?", + "diyPageTitlePlaceholder": "Please enter page title", + "componentCanOnlyAdd": "Can only add up to", + "piece": "pieces", + "componentNotMoved": "This component cannot be moved", + "notCopy": "Cannot copy" + }, + "bottomNav": { + "title": "Bottom Navigation", + "name": "Navigation Name", + "namePlaceholder": "Please enter navigation name", + "navigationItems": "Navigation Items", + "item": "Item", + "itemCount": "Item Count", + "text": "Text", + "textPlaceholder": "Please enter navigation text", + "link": "Link", + "linkPlaceholder": "Please enter navigation link", + "selectedIcon": "Selected Icon", + "unselectedIcon": "Unselected Icon", + "addItem": "Add Navigation Item", + "addBottomNav": "Add Bottom Navigation", + "editBottomNav": "Edit Bottom Navigation", + "deleteConfirm": "Are you sure you want to delete this bottom navigation?", + "deleteSuccess": "Bottom navigation deleted successfully", + "deleteError": "Failed to delete bottom navigation" + }, + "route": { + "title": "Route Management", + "name": "Route Name", + "namePlaceholder": "Please enter route name", + "path": "Route Path", + "pathPlaceholder": "Please enter route path", + "component": "Component Path", + "componentPlaceholder": "Please enter component path", + "title": "Page Title", + "titlePlaceholder": "Please enter page title", + "icon": "Icon", + "iconPlaceholder": "Please enter icon class name", + "keepAlive": "Cache Page", + "requireAuth": "Require Login", + "addRoute": "Add Route", + "editRoute": "Edit Route", + "deleteConfirm": "Are you sure you want to delete this route?", + "deleteSuccess": "Route deleted successfully", + "deleteError": "Failed to delete route" + } + }, + "channel": { + "weapp": { + "access": { + "title": "Mini Program Access", + "tip": "Please follow the steps below to complete WeChat Mini Program access configuration", + "qrCodeTip": "WeChat Mini Program QR Code", + "bindAuth": "Bind Now", + "refreshAuth": "Refresh Authorization", + "viewTutorial": "View Tutorial" + }, + "config": { + "title": "Mini Program Configuration", + "basicTab": "Basic Configuration", + "serverTab": "Server Configuration", + "domainTab": "Domain Configuration", + "privacyTab": "Privacy Policy", + "domainTip": "Please configure the business domain for the mini program, separate multiple domains with semicolons", + "privacyTip": "Please configure the privacy policy information for the mini program", + "modifyDomain": "Modify Domain", + "modifyPrivacy": "Modify Privacy Policy" + }, + "template": { + "title": "Subscription Message Templates", + "batchSync": "Batch Sync", + "sync": "Sync" + }, + "code": { + "title": "Version Release", + "cloudRelease": "Cloud Release", + "cloudReleaseTip": "Release mini program version directly through the cloud", + "localRelease": "Local Upload", + "localReleaseTip": "Upload locally packaged mini program code", + "preview": "Preview Code", + "previewTip": "Scan code to preview mini program", + "uploadLog": "Upload Log", + "uploadProgress": "Upload Progress" + }, + "course": { + "title": "Mini Program Access Tutorial", + "subtitle": "Follow the steps below to complete WeChat Mini Program access configuration", + "start": "Start Access", + "step1": { + "title": "Bind WeChat Mini Program", + "desc1": "First, you need to bind the WeChat Mini Program account to get the AppID and AppSecret of the mini program.", + "desc2": "Log in to the WeChat public platform, enter the mini program management backend, and get the relevant configuration information.", + "note": "Notes:", + "note1": "Ensure the mini program has completed certification", + "note2": "Get the correct AppID and AppSecret", + "note3": "Configure the server domain for the mini program" + }, + "step2": { + "title": "Configure Message Server", + "desc1": "Configure the message server for the mini program to ensure normal reception of message pushes from the WeChat server.", + "desc2": "Set parameters such as Token, EncodingAESKey, and choose the appropriate encryption method.", + "config": "Configuration Items:", + "config1": "Token: Custom verification token", + "config2": "EncodingAESKey: Message encryption key", + "config3": "Encryption Method: Plain text, Compatible, Secure mode" + }, + "step3": { + "title": "Subscription Message Templates", + "desc1": "Synchronize the subscription message templates of the mini program for sending notification messages to users.", + "desc2": "You can sync existing templates from the WeChat public platform or create new templates.", + "tip": "Subscription messages require user active subscription before sending" + }, + "step4": { + "title": "Release Mini Program", + "desc1": "After completing development and testing, submit the mini program version for review and release.", + "desc2": "You can choose cloud release or local upload for version release.", + "cloud": "Cloud Release Features", + "cloud1": "Submit code directly online", + "cloud2": "Automatically complete package upload", + "cloud3": "Support version management", + "local": "Local Upload Features", + "local1": "Upload local packaged files", + "local2": "Support custom packaging process", + "local3": "Suitable for complex projects" + } + }, + "wechat": { + "title": "WeChat Official Account", + "access": { + "title": "Access Guide", + "config": "Configuration Management", + "tip": "Please follow the steps below to complete WeChat Official Account integration", + "step1": { + "title": "Step 1: Register Official Account", + "desc": "Visit WeChat Official Account Platform to register and verify your account" + }, + "step2": { + "title": "Step 2: Get Configuration Info", + "desc": "Get AppID and AppSecret from Official Account backend", + "copyAppId": "Copy AppID", + "copyAppSecret": "Copy AppSecret" + }, + "step3": { + "title": "Step 3: Configure Server", + "desc": "Download certificate and configure server information", + "download": "Download Certificate" + }, + "step4": { + "title": "Step 4: Test Connection", + "desc": "Test if the connection between Official Account and system is working", + "test": "Test Connection" + }, + "next": "Next", + "prev": "Previous", + "complete": "Complete", + "success": "Integration configuration completed", + "copySuccess": "Copy successful" + }, + "config": { + "title": "Official Account Configuration", + "basic": "Basic Configuration", + "server": "Server Configuration", + "domain": "Domain Configuration", + "privacy": "Privacy Configuration", + "appId": "AppID", + "appIdPlaceholder": "Please enter Official Account AppID", + "appIdRequired": "Please enter AppID", + "appSecret": "AppSecret", + "appSecretPlaceholder": "Please enter Official Account AppSecret", + "appSecretRequired": "Please enter AppSecret", + "token": "Token", + "tokenPlaceholder": "Please enter Token", + "tokenRequired": "Please enter Token", + "aesKey": "AES Key", + "aesKeyPlaceholder": "Please enter AES key", + "originalId": "Original ID", + "originalIdPlaceholder": "Please enter Official Account original ID", + "qrcode": "QR Code", + "upload": "Upload QR Code", + "uploadSuccess": "Upload successful", + "uploadError": "Upload failed", + "imageOnly": "Only image files can be uploaded", + "imageSize": "Image size cannot exceed 2MB", + "save": "Save", + "reset": "Reset", + "saveSuccess": "Save successful", + "saveError": "Save failed", + "loadError": "Failed to load configuration", + "serverUrl": "Server URL", + "serverUrlPlaceholder": "Automatically generated by system", + "encodingAesKey": "Message Encryption Key", + "encodingAesKeyPlaceholder": "Please enter message encryption key", + "encodingAesKeyTip": "43 characters, used for message encryption/decryption", + "encryptType": "Encryption Type", + "encryptType0": "Plain Text Mode", + "encryptType1": "Compatible Mode", + "encryptType2": "Secure Mode", + "businessDomain": "Business Domain", + "businessDomainPlaceholder": "One domain per line, maximum 3", + "businessDomainRequired": "Please enter business domain", + "businessDomainTip": "No security warning when users input on this domain", + "jsDomain": "JS Interface Safe Domain", + "jsDomainPlaceholder": "One domain per line, maximum 3", + "jsDomainTip": "Domain for calling JS interfaces", + "webDomain": "Web Authorization Domain", + "webDomainPlaceholder": "One domain per line, maximum 3", + "webDomainTip": "Domain for web authorization", + "privacyTip": "Please configure privacy settings carefully to ensure compliance with relevant laws", + "privacyPolicy": "Privacy Policy", + "privacyPolicy1": "Privacy protection enabled", + "privacyPolicy0": "Privacy protection disabled", + "privacyPolicyRequired": "Please select privacy policy", + "userPrivacy": "User Privacy Description", + "userPrivacyPlaceholder": "Please enter user privacy description", + "userPrivacyTip": "Explain data collection and usage to users", + "dataRetention": "Data Retention Period", + "retention30": "30 days", + "retention90": "90 days", + "retention180": "180 days", + "retention365": "365 days", + "dataRetentionTip": "User data retention period, automatically deleted after expiration" + }, + "template": { + "title": "Message Templates", + "sync": "Sync Templates", + "edit": "Edit Template", + "templateId": "Template ID", + "primaryIndustry": "Primary Industry", + "deputyIndustry": "Deputy Industry", + "content": "Template Content", + "example": "Example", + "deleteConfirm": "Are you sure to delete template【{title}】?", + "loadError": "Failed to load templates", + "syncSuccess": "Sync successful", + "syncError": "Sync failed" + }, + "menu": { + "title": "Custom Menu", + "preview": "Preview", + "publish": "Publish", + "editor": "Menu Editor", + "selectMenu": "Please select a menu to edit", + "name": "Menu Name", + "namePlaceholder": "Please enter menu name", + "nameRequired": "Please enter menu name", + "type": "Menu Type", + "typeRequired": "Please select menu type", + "typeClick": "Click Event", + "typeView": "Redirect URL", + "typeMiniprogram": "Mini Program", + "typeScancode": "Scan Code Event", + "typeLocation": "Send Location", + "key": "Menu KEY", + "keyPlaceholder": "Please enter menu KEY", + "url": "Web Link", + "urlPlaceholder": "Please enter web link", + "appid": "Mini Program APPID", + "appidPlaceholder": "Please enter mini program APPID", + "pagepath": "Mini Program Path", + "pagepathPlaceholder": "Please enter mini program path", + "addSubMenu": "Add Sub Menu", + "deleteConfirm": "Are you sure to delete this menu?", + "loadError": "Failed to load menu", + "saveSuccess": "Save successful", + "saveError": "Save failed", + "newMenu": "New Menu", + "newSubMenu": "New Sub Menu" + }, + "user": { + "title": "User Management", + "sync": "Sync Users", + "export": "Export Users", + "nickname": "Nickname", + "nicknamePlaceholder": "Please enter nickname", + "subscribe": "Subscription Status", + "subscribed": "Subscribed", + "unsubscribed": "Unsubscribed", + "sex": "Gender", + "male": "Male", + "female": "Female", + "unknown": "Unknown", + "city": "City", + "province": "Province", + "country": "Country", + "subscribeTime": "Subscription Time", + "openid": "OpenID", + "unionid": "UnionID", + "groupid": "Group ID", + "tagidList": "Tag List", + "remark": "Remark", + "language": "Language", + "headimgurl": "Avatar", + "detail": "User Details", + "sendMessage": "Send Message", + "setTag": "Set Tag", + "sendMessageTip": "Send message function under development", + "setTagTip": "Set tag function under development", + "loadError": "Failed to load users", + "syncSuccess": "Sync successful", + "syncError": "Sync failed", + "exporting": "Exporting user data..." + } + } + } } -} +} \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/locales/langs/zh-CN/page.json b/admin-vben/apps/web-antd/src/locales/langs/zh-CN/page.json index ce3f973b..0f4d8d83 100644 --- a/admin-vben/apps/web-antd/src/locales/langs/zh-CN/page.json +++ b/admin-vben/apps/web-antd/src/locales/langs/zh-CN/page.json @@ -17,5 +17,43 @@ "layoutTitle": "布局设置", "appList": "应用列表", "chooseLayout": "选择布局" + }, + "channel": { + "weapp": { + "title": "微信小程序", + "access": "接入指引", + "config": "配置管理", + "template": "模板消息", + "release": "版本发布", + "tutorial": "使用教程" + }, + "wechat": { + "title": "微信公众号", + "access": "接入指引", + "config": "配置管理", + "template": "模板消息", + "menu": "自定义菜单", + "user": "用户管理", + "material": "素材管理", + "tutorial": "使用教程" + } + }, + "setting": { + "system": { + "title": "系统设置", + "config": "配置管理" + }, + "payment": { + "title": "支付设置", + "list": "支付方式" + }, + "sms": { + "title": "短信设置", + "list": "短信配置" + }, + "storage": { + "title": "存储设置", + "list": "存储配置" + } } } diff --git a/admin-vben/apps/web-antd/src/locales/langs/zh-CN/system.json b/admin-vben/apps/web-antd/src/locales/langs/zh-CN/system.json index 8918d09c..0d7da384 100644 --- a/admin-vben/apps/web-antd/src/locales/langs/zh-CN/system.json +++ b/admin-vben/apps/web-antd/src/locales/langs/zh-CN/system.json @@ -39,13 +39,657 @@ "adminDisabled": "系统管理员不可操作" }, "role": { - "title": "角色管理" + "title": "角色管理", + "roleName": "角色名称", + "roleNamePlaceholder": "请输入角色名称", + "addRole": "新增角色", + "editRole": "编辑角色", + "status": "状态", + "createTime": "创建时间", + "permission": "权限", + "selectAll": "全选", + "checkStrictly": "父子联动", + "fold": "收起", + "unfold": "展开", + "deleteConfirm": "确定要删除该角色吗?", + "loadError": "加载角色列表失败", + "loadMenuError": "加载菜单数据失败", + "loadRoleError": "加载角色信息失败", + "saveError": "保存角色失败", + "addSuccess": "角色添加成功", + "editSuccess": "角色更新成功", + "deleteSuccess": "角色删除成功", + "deleteError": "删除角色失败", + "statusChangeSuccess": "角色状态修改成功", + "statusChangeError": "修改角色状态失败", + "rulesPlaceholder": "请至少选择一个权限" }, "menu": { - "title": "菜单管理" + "title": "菜单管理", + "addMenu": "新增菜单", + "editMenu": "编辑菜单", + "menuName": "菜单名称", + "menuNamePlaceholder": "请输入菜单名称", + "menuKey": "菜单标识", + "menuKeyPlaceholder": "请输入菜单标识", + "menuKeyValidata": "菜单标识格式不正确", + "menuType": "菜单类型", + "menuTypeDir": "目录", + "menuTypeMenu": "菜单", + "menuTypeButton": "按钮", + "menuIcon": "菜单图标", + "routePath": "路由地址", + "routePathPlaceholder": "请输入路由地址", + "viewPath": "视图路径", + "viewPathPlaceholder": "请输入视图路径", + "authId": "权限标识", + "authIdPlaceholder": "请输入权限标识", + "addon": "应用", + "parentMenu": "上级菜单", + "menuShortName": "菜单简称", + "menuShortNamePlaceholder": "请输入菜单简称", + "isShow": "是否显示", + "show": "显示", + "hide": "隐藏", + "sort": "排序", + "status": "状态", + "initializeMenu": "初始化菜单", + "initializeMenuTipsOne": "此操作将重置所有菜单数据", + "initializeMenuTipsTwo": "确定要继续吗?", + "initializeSuccess": "菜单初始化成功", + "initializeError": "菜单初始化失败", + "loadError": "加载菜单列表失败", + "loadMenuError": "加载菜单数据失败", + "loadAddonError": "加载应用数据失败", + "loadMenuInfoError": "加载菜单信息失败", + "saveError": "保存菜单失败", + "addSuccess": "菜单添加成功", + "editSuccess": "菜单更新成功", + "deleteSuccess": "菜单删除成功", + "deleteError": "删除菜单失败", + "deleteConfirm": "确定要删除此菜单吗?" }, "dept": { "title": "部门管理" + }, + "group": { + "title": "站点套餐", + "groupId": "套餐ID", + "groupName": "套餐名称", + "groupDesc": "套餐说明", + "mainApp": "主应用", + "containAddon": "包含插件", + "appName": "应用名称", + "addonName": "插件名称", + "createTime": "创建时间", + "addGroup": "添加套餐", + "editGroup": "编辑套餐", + "deleteConfirm": "确定要删除此套餐吗?", + "deleteSuccess": "套餐删除成功", + "deleteError": "套餐删除失败", + "saveSuccess": "套餐保存成功", + "saveError": "套餐保存失败", + "groupNamePlaceholder": "请输入套餐名称", + "groupDescPlaceholder": "请输入套餐说明", + "mainAppPlaceholder": "请选择主应用", + "containAddonPlaceholder": "请选择包含插件", + "appListEmpty": "暂无应用", + "addonListEmpty": "暂无插件", + "moreApps": "还有 {count} 个应用", + "moreAddons": "还有 {count} 个插件", + "selectAppFirst": "请先选择对应的主应用", + "addonRemovedDueToAppDependency": "部分插件因依赖关系已自动移除" + }, + "diy": { + "title": "DIY装修", + "decorating": "装修", + "list": { + "title": "自定义页面", + "pageId": "页面ID", + "pageTitle": "页面标题", + "pageTitlePlaceholder": "请输入页面标题", + "addonName": "所属应用", + "pageType": "页面类型", + "pageTypePlaceholder": "请选择页面类型", + "status": "状态", + "updateTime": "更新时间", + "addPage": "添加页面", + "preview": "预览", + "shareSetting": "分享设置", + "copy": "复制", + "copySuccess": "页面复制成功", + "copyError": "页面复制失败", + "deleteConfirm": "确定要删除此页面吗?", + "deleteSuccess": "页面删除成功", + "deleteError": "页面删除失败", + "setUseSuccess": "设置成功", + "setUseError": "设置失败", + "cannotSetUseDiyPage": "DIY_PAGE类型页面不能设为使用", + "shareTitle": "分享标题", + "shareTitlePlaceholder": "请输入分享标题", + "shareDesc": "分享描述", + "shareDescPlaceholder": "请输入分享描述", + "shareImage": "分享图片", + "wechatShare": "微信公众号分享", + "weappShare": "微信小程序分享" + }, + "design": { + "templatePagePlaceholder": "选择模板页面", + "templatePageEmpty": "空模板", + "pageSet": "页面设置", + "moveUpComponent": "上移组件", + "moveDownComponent": "下移组件", + "copyComponent": "复制组件", + "delComponent": "删除组件", + "resetComponent": "重置组件", + "delComponentTips": "确定要删除该组件吗?", + "changeTemplatePageTips": "切换模板页面将清空当前页面内容,确定要继续吗?", + "developTitle": "开发模式配置", + "wapDomain": "WAP域名", + "wapDomainPlaceholder": "请输入WAP域名,如:https://www.example.com", + "settingTips": "配置说明", + "initPageError": "页面初始化失败", + "tabEditContent": "内容", + "tabEditStyle": "样式", + "pageSettings": "页面设置", + "pageName": "页面名称", + "pageNamePlaceholder": "请输入页面名称", + "pageMode": "页面模式", + "style1": "单列平铺", + "style2": "左右排列", + "alignment": "对齐方式", + "alignLeft": "左对齐", + "alignRight": "右对齐", + "borderControl": "边框控制", + "pageBackground": "页面背景", + "bgColor": "背景颜色", + "bgColorTips": "左侧为开始颜色,右侧为结束颜色", + "gradientAngle": "渐变角度", + "topToBottom": "从上到下", + "leftToRight": "从左到右", + "bgImage": "背景图片", + "bgHeightScale": "背景高度比例", + "bgHeightScaleTips": "0为高度自适应", + "topStatusBar": "顶部状态栏", + "showStatusBar": "显示状态栏", + "statusBarStyle": "状态栏样式", + "style1Text": "纯文字", + "style2ImageText": "图片+文字", + "style3ImageSearch": "图片+搜索", + "style4Location": "定位", + "textAlign": "文字对齐", + "alignCenter": "居中", + "logoImage": "Logo图片", + "searchPlaceholder": "搜索占位符", + "searchPlaceholderTips": "请输入搜索占位符文字", + "link": "跳转链接", + "linkPlaceholder": "请选择跳转链接", + "popWindow": "弹窗设置", + "showPopWindow": "显示弹窗", + "neverShow": "从不显示", + "showOnce": "仅显示一次", + "showAlways": "每次都显示", + "popImage": "弹窗图片", + "popLink": "弹窗链接", + "popLinkPlaceholder": "请选择弹窗跳转链接", + "leavePageContentTips": "页面内容已修改,确定要离开吗?", + "diyPageTitlePlaceholder": "请输入页面标题", + "componentCanOnlyAdd": "最多只能添加", + "piece": "个", + "componentNotMoved": "该组件不能移动", + "notCopy": "不能复制" + }, + "bottomNav": { + "title": "底部导航", + "name": "导航名称", + "namePlaceholder": "请输入导航名称", + "navigationItems": "导航项", + "item": "项", + "itemCount": "项数", + "text": "文字", + "icon": "图标", + "iconPlaceholder": "请选择图标", + "link": "跳转链接", + "linkPlaceholder": "请选择跳转链接", + "addItem": "添加导航项", + "deleteItem": "删除导航项" + }, + "wechat": { + "title": "微信公众号", + "access": { + "title": "接入指南", + "tips": "按照以下步骤完成微信公众号的接入配置", + "step1": { + "title": "注册公众号", + "desc": "首先需要注册一个微信公众号", + "requirements": "注册要求", + "req1": "企业或个体工商户营业执照", + "req2": "运营者身份证信息", + "req3": "邮箱和手机号", + "goRegister": "前往注册" + }, + "step2": { + "title": "获取开发者信息", + "desc": "在公众号后台获取开发者凭据", + "instructions": "获取步骤", + "step1": "登录微信公众号后台", + "step2": "进入开发-基本配置页面", + "step3": "获取AppID和AppSecret", + "appId": "AppID", + "appIdPlaceholder": "请输入AppID", + "appSecret": "AppSecret", + "appSecretPlaceholder": "请输入AppSecret" + }, + "step3": { + "title": "配置服务器", + "desc": "配置服务器地址和Token", + "serverInfo": "服务器配置信息", + "serverUrl": "服务器地址", + "token": "Token", + "instructions": "配置步骤", + "step1": "在基本配置页面填写服务器配置", + "step2": "填写服务器地址和Token", + "step3": "选择消息加解密方式", + "step4": "提交配置并启用" + }, + "step4": { + "title": "完成配置", + "desc": "恭喜!您已完成微信公众号的接入配置", + "success": "配置成功", + "successDesc": "您的微信公众号已成功接入系统", + "nextSteps": "下一步操作", + "next1": "配置消息模板", + "next2": "设置自定义菜单", + "next3": "管理用户标签", + "goConfig": "前往配置", + "goTutorial": "查看教程" + } + }, + "config": { + "title": "公众号配置", + "basic": { + "tab": "基本配置", + "appId": "AppID", + "appIdRequired": "请输入AppID", + "appIdPlaceholder": "请输入微信公众号AppID", + "appSecret": "AppSecret", + "appSecretRequired": "请输入AppSecret", + "appSecretPlaceholder": "请输入微信公众号AppSecret", + "token": "Token", + "tokenRequired": "请输入Token", + "tokenPlaceholder": "请输入Token", + "encodingAesKey": "EncodingAESKey", + "encodingAesKeyPlaceholder": "请输入消息加密密钥", + "encryptionType": "消息加解密方式", + "encryptionType0": "明文模式", + "encryptionType1": "兼容模式", + "encryptionType2": "安全模式", + "qrCode": "二维码" + }, + "server": { + "tab": "服务器配置", + "serveUrl": "服务器地址", + "serveUrlRequired": "请输入服务器地址", + "serveUrlPlaceholder": "请输入服务器地址URL", + "token": "Token", + "tokenRequired": "请输入Token", + "tokenPlaceholder": "请输入Token", + "encodingAesKey": "EncodingAESKey", + "encodingAesKeyPlaceholder": "请输入消息加密密钥", + "encryptionType": "消息加解密方式", + "encryptionType0": "明文模式", + "encryptionType1": "兼容模式", + "encryptionType2": "安全模式" + }, + "domain": { + "tab": "域名配置", + "requestDomain": "请求域名", + "requestDomainRequired": "请输入请求域名", + "requestDomainPlaceholder": "请输入请求域名,多个用回车分隔", + "wsRequestDomain": "WebSocket域名", + "wsRequestDomainRequired": "请输入WebSocket域名", + "wsRequestDomainPlaceholder": "请输入WebSocket域名,多个用回车分隔", + "uploadDomain": "上传域名", + "uploadDomainRequired": "请输入上传域名", + "uploadDomainPlaceholder": "请输入上传域名,多个用回车分隔", + "downloadDomain": "下载域名", + "downloadDomainRequired": "请输入下载域名", + "downloadDomainPlaceholder": "请输入下载域名,多个用回车分隔" + }, + "privacy": { + "tab": "隐私配置", + "contactEmail": "联系邮箱", + "contactEmailRequired": "请输入联系邮箱", + "contactEmailPlaceholder": "请输入联系邮箱", + "contactPhone": "联系电话", + "contactPhoneRequired": "请输入联系电话", + "contactPhonePlaceholder": "请输入联系电话", + "contactQQ": "联系QQ", + "contactQQPlaceholder": "请输入联系QQ", + "contactWeixin": "联系微信", + "contactWeixinPlaceholder": "请输入联系微信", + "storeExpireTimestamp": "存储到期时间", + "storeExpireTimestampPlaceholder": "请选择存储到期时间", + "settingList": "隐私设置列表" + } + }, + "template": { + "title": "消息模板", + "searchPlaceholder": "请输入模板标题搜索", + "sync": "同步模板" + }, + "tutorial": { + "title": "使用教程", + "basic": { + "title": "基础介绍" + }, + "config": { + "title": "配置指南" + }, + "message": { + "title": "消息管理" + }, + "user": { + "title": "用户管理" + }, + "material": { + "title": "素材管理" + }, + "faq": { + "title": "常见问题" + } + } + } + "textPlaceholder": "请输入导航文字", + "link": "链接", + "linkPlaceholder": "请输入导航链接", + "selectedIcon": "选中图标", + "unselectedIcon": "未选中图标", + "addItem": "添加导航项", + "addBottomNav": "添加底部导航", + "editBottomNav": "编辑底部导航", + "deleteConfirm": "确定要删除此底部导航吗?", + "deleteSuccess": "底部导航删除成功", + "deleteError": "底部导航删除失败" + }, + "route": { + "title": "路由管理", + "name": "路由名称", + "namePlaceholder": "请输入路由名称", + "path": "路由路径", + "pathPlaceholder": "请输入路由路径", + "component": "组件路径", + "componentPlaceholder": "请输入组件路径", + "title": "页面标题", + "titlePlaceholder": "请输入页面标题", + "icon": "图标", + "iconPlaceholder": "请输入图标类名", + "keepAlive": "缓存页面", + "requireAuth": "需要登录", + "addRoute": "添加路由", + "editRoute": "编辑路由", + "deleteConfirm": "确定要删除此路由吗?", + "deleteSuccess": "路由删除成功", + "deleteError": "路由删除失败" + } + }, + "channel": { + "weapp": { + "access": { + "title": "小程序接入", + "tip": "请按照以下步骤完成微信小程序的接入配置", + "qrCodeTip": "微信小程序二维码", + "bindAuth": "立即绑定", + "refreshAuth": "刷新授权", + "viewTutorial": "查看教程" + }, + "config": { + "title": "小程序配置", + "basicTab": "基础配置", + "serverTab": "服务器配置", + "domainTab": "域名配置", + "privacyTab": "隐私协议", + "domainTip": "请配置小程序的业务域名,多个域名用分号分隔", + "privacyTip": "请配置小程序的隐私协议信息", + "modifyDomain": "修改域名", + "modifyPrivacy": "修改隐私协议" + }, + "template": { + "title": "订阅消息模板", + "batchSync": "批量同步", + "sync": "同步" + }, + "code": { + "title": "版本发布", + "cloudRelease": "云端发布", + "cloudReleaseTip": "通过云端直接发布小程序版本", + "localRelease": "本地上传", + "localReleaseTip": "上传本地打包的小程序代码", + "preview": "预览码", + "previewTip": "扫码预览小程序", + "uploadLog": "上传日志", + "uploadProgress": "上传进度" + }, + "course": { + "title": "小程序接入教程", + "subtitle": "按照以下步骤完成微信小程序的接入配置", + "start": "开始接入", + "step1": { + "title": "绑定微信小程序", + "desc1": "首先需要绑定微信小程序账号,获取小程序的AppID和AppSecret。", + "desc2": "登录微信公众平台,进入小程序管理后台,获取相关配置信息。", + "note": "注意事项:", + "note1": "确保小程序已完成认证", + "note2": "获取正确的AppID和AppSecret", + "note3": "配置小程序的服务器域名" + }, + "step2": { + "title": "配置消息服务器", + "desc1": "配置小程序的消息服务器,确保能够正常接收微信服务器的消息推送。", + "desc2": "设置Token、EncodingAESKey等参数,选择合适的加密方式。", + "config": "配置项说明:", + "config1": "Token:自定义的验证令牌", + "config2": "EncodingAESKey:消息加密密钥", + "config3": "加密方式:明文、兼容、安全模式" + }, + "step3": { + "title": "订阅消息模板", + "desc1": "同步小程序的订阅消息模板,用于向用户发送通知消息。", + "desc2": "可以从微信公众平台同步已有的模板,也可以创建新的模板。", + "tip": "订阅消息需要用户主动订阅后才能发送" + }, + "step4": { + "title": "发布小程序", + "desc1": "完成开发和测试后,提交小程序版本进行审核发布。", + "desc2": "可以选择云端发布或本地上传的方式进行版本发布。", + "cloud": "云端发布特点", + "cloud1": "直接在线提交代码", + "cloud2": "自动完成打包上传", + "cloud3": "支持版本管理", + "local": "本地上传特点", + "local1": "上传本地打包文件", + "local2": "支持自定义打包流程", + "local3": "适合复杂项目" + } + } + }, + "wechat": { + "title": "微信公众号", + "access": { + "title": "接入指南", + "config": "配置管理", + "tip": "请按照以下步骤完成微信公众号的接入配置", + "step1": { + "title": "第一步:注册公众号", + "desc": "访问微信公众平台注册并认证您的公众号" + }, + "step2": { + "title": "第二步:获取配置信息", + "desc": "在公众号后台获取AppID和AppSecret", + "copyAppId": "复制AppID", + "copyAppSecret": "复制AppSecret" + }, + "step3": { + "title": "第三步:配置服务器", + "desc": "下载证书并配置服务器信息", + "download": "下载证书" + }, + "step4": { + "title": "第四步:测试连接", + "desc": "测试公众号与系统的连接是否正常", + "test": "测试连接" + }, + "next": "下一步", + "prev": "上一步", + "complete": "完成", + "success": "接入配置完成", + "copySuccess": "复制成功" + }, + "config": { + "title": "公众号配置", + "basic": "基本配置", + "server": "服务器配置", + "domain": "域名配置", + "privacy": "隐私配置", + "appId": "AppID", + "appIdPlaceholder": "请输入公众号AppID", + "appIdRequired": "请输入AppID", + "appSecret": "AppSecret", + "appSecretPlaceholder": "请输入公众号AppSecret", + "appSecretRequired": "请输入AppSecret", + "token": "Token", + "tokenPlaceholder": "请输入Token", + "tokenRequired": "请输入Token", + "aesKey": "AES密钥", + "aesKeyPlaceholder": "请输入AES密钥", + "originalId": "原始ID", + "originalIdPlaceholder": "请输入公众号原始ID", + "qrcode": "二维码", + "upload": "上传二维码", + "uploadSuccess": "上传成功", + "uploadError": "上传失败", + "imageOnly": "只能上传图片文件", + "imageSize": "图片大小不能超过2MB", + "save": "保存", + "reset": "重置", + "saveSuccess": "保存成功", + "saveError": "保存失败", + "loadError": "加载配置失败", + "serverUrl": "服务器URL", + "serverUrlPlaceholder": "系统自动生成", + "encodingAesKey": "消息加解密密钥", + "encodingAesKeyPlaceholder": "请输入消息加解密密钥", + "encodingAesKeyTip": "43位字符,用于消息加解密", + "encryptType": "加密方式", + "encryptType0": "明文模式", + "encryptType1": "兼容模式", + "encryptType2": "安全模式", + "businessDomain": "业务域名", + "businessDomainPlaceholder": "每行一个域名,最多3个", + "businessDomainRequired": "请输入业务域名", + "businessDomainTip": "用户在该域名上进行输入时,不出现安全提示", + "jsDomain": "JS接口安全域名", + "jsDomainPlaceholder": "每行一个域名,最多3个", + "jsDomainTip": "用于调用JS接口的域名", + "webDomain": "网页授权域名", + "webDomainPlaceholder": "每行一个域名,最多3个", + "webDomainTip": "用于网页授权的域名", + "privacyTip": "请谨慎配置隐私设置,确保符合相关法律法规", + "privacyPolicy": "隐私政策", + "privacyPolicy1": "已启用隐私保护", + "privacyPolicy0": "未启用隐私保护", + "privacyPolicyRequired": "请选择隐私政策", + "userPrivacy": "用户隐私说明", + "userPrivacyPlaceholder": "请输入用户隐私说明", + "userPrivacyTip": "向用户说明数据收集和使用情况", + "dataRetention": "数据保留期限", + "retention30": "30天", + "retention90": "90天", + "retention180": "180天", + "retention365": "365天", + "dataRetentionTip": "用户数据保留期限,到期后自动删除" + }, + "template": { + "title": "消息模板", + "sync": "同步模板", + "edit": "编辑模板", + "templateId": "模板ID", + "primaryIndustry": "主行业", + "deputyIndustry": "副行业", + "content": "模板内容", + "example": "示例", + "deleteConfirm": "确定删除模板【{title}】吗?", + "loadError": "加载模板失败", + "syncSuccess": "同步成功", + "syncError": "同步失败" + }, + "menu": { + "title": "自定义菜单", + "preview": "预览", + "publish": "发布", + "editor": "菜单编辑器", + "selectMenu": "请选择要编辑的菜单", + "name": "菜单名称", + "namePlaceholder": "请输入菜单名称", + "nameRequired": "请输入菜单名称", + "type": "菜单类型", + "typeRequired": "请选择菜单类型", + "typeClick": "点击推事件", + "typeView": "跳转URL", + "typeMiniprogram": "小程序", + "typeScancode": "扫码推事件", + "typeLocation": "发送位置", + "key": "菜单KEY值", + "keyPlaceholder": "请输入菜单KEY值", + "url": "网页链接", + "urlPlaceholder": "请输入网页链接", + "appid": "小程序APPID", + "appidPlaceholder": "请输入小程序APPID", + "pagepath": "小程序路径", + "pagepathPlaceholder": "请输入小程序路径", + "addSubMenu": "添加子菜单", + "deleteConfirm": "确定删除该菜单吗?", + "loadError": "加载菜单失败", + "saveSuccess": "保存成功", + "saveError": "保存失败", + "newMenu": "新菜单", + "newSubMenu": "新子菜单" + }, + "user": { + "title": "用户管理", + "sync": "同步用户", + "export": "导出用户", + "nickname": "昵称", + "nicknamePlaceholder": "请输入昵称", + "subscribe": "关注状态", + "subscribed": "已关注", + "unsubscribed": "未关注", + "sex": "性别", + "male": "男", + "female": "女", + "unknown": "未知", + "city": "城市", + "province": "省份", + "country": "国家", + "subscribeTime": "关注时间", + "openid": "OpenID", + "unionid": "UnionID", + "groupid": "分组ID", + "tagidList": "标签列表", + "remark": "备注", + "language": "语言", + "headimgurl": "头像", + "detail": "用户详情", + "sendMessage": "发送消息", + "setTag": "设置标签", + "sendMessageTip": "发送消息功能开发中", + "setTagTip": "设置标签功能开发中", + "loadError": "加载用户失败", + "syncSuccess": "同步成功", + "syncError": "同步失败", + "exporting": "正在导出用户数据..." + } + } } }, "common": { @@ -64,6 +708,8 @@ "total": "共 {total} 条", "enable": "启用", "disable": "禁用", + "inUse": "使用中", + "notInUse": "未使用", "warning": "提示" }, "authentication": { diff --git a/admin-vben/apps/web-antd/src/router/index.ts b/admin-vben/apps/web-antd/src/router/index.ts index 405db94f..41e4b399 100644 --- a/admin-vben/apps/web-antd/src/router/index.ts +++ b/admin-vben/apps/web-antd/src/router/index.ts @@ -1,3 +1,4 @@ +import type { RouteLocationRaw } from 'vue-router'; import { createRouter, createWebHashHistory, @@ -35,13 +36,13 @@ const resetRoutes = () => resetStaticRoutes(router, routes); function getAppType(): 'admin' | 'site' | 'home' { const path = location.pathname.replace(/^\/+/, ''); const first = path.split('/')[0]; - if (first === 'site' || first === 'home' || first === 'admin') return first as any; + if (first === 'site' || first === 'home' || first === 'admin') return first as 'admin' | 'site' | 'home'; return 'admin'; } // 重写 push,自动补齐 app 前缀 const originPush = router.push.bind(router); -router.push = (to: any) => { +router.push = (to: RouteLocationRaw) => { const route = typeof to === 'string' ? { path: to } : { ...to }; if (route.path) { const parts = route.path.split('/').filter(Boolean); @@ -54,7 +55,7 @@ router.push = (to: any) => { // 重写 resolve,保证解析时也有 app 前缀 const originResolve = router.resolve.bind(router); -router.resolve = (to: any, currentLocation?: any) => { +router.resolve = (to: RouteLocationRaw, currentLocation?: RouteLocationRaw) => { const route = typeof to === 'string' ? { path: to } : { ...to }; if (route.path) { const parts = route.path.split('/').filter(Boolean); diff --git a/admin-vben/apps/web-antd/src/store/auth.ts b/admin-vben/apps/web-antd/src/store/auth.ts index 4adeb76e..efd53296 100644 --- a/admin-vben/apps/web-antd/src/store/auth.ts +++ b/admin-vben/apps/web-antd/src/store/auth.ts @@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { notification } from 'ant-design-vue'; import { defineStore } from 'pinia'; -import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; +import { getAccessCodesApi, getCurrentUserApi, loginApi, logoutApi } from '#/api'; import { $t } from '#/locales'; export const useAuthStore = defineStore('auth', () => { @@ -27,7 +27,7 @@ export const useAuthStore = defineStore('auth', () => { * @param onSuccess 成功之后的回调函数 */ async function authLogin( - params: Recordable, + params: { username: string; password: string; captcha_code?: string }, onSuccess?: () => Promise | void, ) { // 异步处理用户登录操作并获取 accessToken @@ -101,7 +101,7 @@ export const useAuthStore = defineStore('auth', () => { async function fetchUserInfo() { let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); + userInfo = await getCurrentUserApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/admin-vben/apps/web-antd/src/stores/diy.ts b/admin-vben/apps/web-antd/src/stores/diy.ts new file mode 100644 index 00000000..635a3bc8 --- /dev/null +++ b/admin-vben/apps/web-antd/src/stores/diy.ts @@ -0,0 +1,501 @@ +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { message, Modal } from 'ant-design-vue'; + +import type { GlobalConfig } from '@/views/diy/design/data'; + +export interface ComponentItem { + id: string; + componentName: string; + componentTitle: string; + title: string; + icon?: string; + path: string; + uses: number; + position?: string; + ignore?: string[]; + [key: string]: any; +} + +export const useDiyStore = defineStore('diy', () => { + // Basic page info + const id = ref(0); + const name = ref(''); + const pageTitle = ref(''); + const type = ref(''); + const typeName = ref(''); + const templateName = ref(''); + const isDefault = ref(0); + const pageMode = ref('diy'); + + // Loading state + const load = ref(false); + + // Current editing index + const currentIndex = ref(-99); // -99 for page settings + + // Edit tab + const editTab = ref<'content' | 'style'>('content'); + + // Global configuration + const global = ref({ + title: '页面', + completeLayout: 'style-1', + completeAlign: 'left', + borderControl: true, + pageStartBgColor: '', + pageEndBgColor: '', + pageGradientAngle: 'to bottom', + bgUrl: '', + bgHeightScale: 0, + imgWidth: '', + imgHeight: '', + topStatusBar: { + control: true, + isShow: true, + bgColor: '#ffffff', + rollBgColor: '#ffffff', + style: 'style-1', + styleName: '风格1', + textColor: '#333333', + rollTextColor: '#333333', + textAlign: 'center', + inputPlaceholder: '请输入搜索关键词', + imgUrl: '', + link: { name: '' }, + }, + bottomTabBar: { + control: true, + isShow: true, + }, + popWindow: { + imgUrl: '', + imgWidth: '', + imgHeight: '', + count: 'once', + show: 0, + link: { name: '' }, + }, + template: { + textColor: '#303133', + pageStartBgColor: '', + pageEndBgColor: '', + pageGradientAngle: 'to bottom', + componentBgUrl: '', + componentBgAlpha: 2, + componentStartBgColor: '', + componentEndBgColor: '', + componentGradientAngle: 'to bottom', + topRounded: 0, + bottomRounded: 0, + elementBgColor: '', + topElementRounded: 0, + bottomElementRounded: 0, + margin: { + top: 0, + bottom: 0, + both: 0, + }, + isHidden: false, + }, + }); + + // Component values + const value = ref([]); + + // Available components + const components = ref([]); + + // Position types + const positionTypes = ['top_fixed', 'right_fixed', 'bottom_fixed', 'left_fixed', 'fixed']; + + // Current component + const currentComponent = computed(() => { + if (currentIndex.value === -99) { + return 'page-settings'; + } + return value.value[currentIndex.value]?.path || ''; + }); + + // Edit component + const editComponent = computed(() => { + if (currentIndex.value === -99) { + return global.value; + } + return value.value[currentIndex.value]; + }); + + // Initialize store + const init = () => { + id.value = 0; + name.value = ''; + pageTitle.value = ''; + type.value = ''; + typeName.value = ''; + templateName.value = ''; + isDefault.value = 0; + pageMode.value = 'diy'; + load.value = false; + currentIndex.value = -99; + editTab.value = 'content'; + + // Reset global config + global.value = { + title: '页面', + completeLayout: 'style-1', + completeAlign: 'left', + borderControl: true, + pageStartBgColor: '', + pageEndBgColor: '', + pageGradientAngle: 'to bottom', + bgUrl: '', + bgHeightScale: 100, + imgWidth: '', + imgHeight: '', + topStatusBar: { + control: true, + isShow: true, + bgColor: '#ffffff', + rollBgColor: '#ffffff', + style: 'style-1', + styleName: '风格1', + textColor: '#333333', + rollTextColor: '#333333', + textAlign: 'center', + inputPlaceholder: '请输入搜索关键词', + imgUrl: '', + link: { name: '' }, + }, + bottomTabBar: { + control: true, + isShow: true, + }, + popWindow: { + imgUrl: '', + imgWidth: '', + imgHeight: '', + count: 'once', + show: 0, + link: { name: '' }, + }, + template: { + textColor: '#303133', + pageStartBgColor: '', + pageEndBgColor: '', + pageGradientAngle: 'to bottom', + componentBgUrl: '', + componentBgAlpha: 2, + componentStartBgColor: '', + componentEndBgColor: '', + componentGradientAngle: 'to bottom', + topRounded: 0, + bottomRounded: 0, + elementBgColor: '', + topElementRounded: 0, + bottomElementRounded: 0, + margin: { + top: 0, + bottom: 0, + both: 0, + }, + isHidden: false, + }, + }; + + value.value = []; + components.value = []; + }; + + // Generate random ID + const generateRandom = (len: number = 5) => { + return Number(Math.random().toString().substr(3, len) + Date.now()).toString(36); + }; + + // Add component + const addComponent = (key: string, data: any) => { + if (!load.value) return; + + let component = cloneDeep(data); + component.id = generateRandom(); + component.componentName = key; + component.componentTitle = component.title; + component.ignore = component.ignore || []; + + // Apply template properties + let template = cloneDeep(global.value.template); + Object.assign(component, template); + + if (component.template) { + Object.assign(component, component.template); + delete component.template; + } + + // Check if component can be added + if (!checkComponentIsAdd(component)) { + message.warning(`${component.componentTitle}最多只能添加${component.uses}个`); + return; + } + + // Handle position-based components + if (component.position && positionTypes.includes(component.position)) { + if (component.position === 'top_fixed') { + value.value.splice(0, 0, component); + currentIndex.value = 0; + } else if (component.position === 'bottom_fixed') { + value.value.splice(value.value.length, 0, component); + currentIndex.value = value.value.length - 1; + } else { + value.value.splice(0, 0, component); + currentIndex.value = 0; + } + } else if (currentIndex.value === -99) { + // Add to end + let index = value.value.length; + for (let i = value.value.length - 1; i >= 0; i--) { + if (value.value[i].position === 'bottom_fixed') { + index = i; + break; + } + } + + if (index === value.value.length) { + value.value.push(component); + currentIndex.value = value.value.length - 1; + } else { + value.value.splice(index, 0, component); + currentIndex.value = index; + } + } else { + // Insert after current + let index = currentIndex.value + 1; + for (let i = value.value.length - 1; i >= 0; i--) { + if (value.value[i].position === 'bottom_fixed') { + if (i === currentIndex.value || (i - currentIndex.value) === 1) { + index = i; + } + break; + } + } + + value.value.splice(index, 0, component); + currentIndex.value = index; + } + + currentComponent.value = component.path; + }; + + // Check if component can be added + const checkComponentIsAdd = (component: ComponentItem) => { + if (component.uses === 0) return true; + + let count = 0; + for (let i in value.value) { + if (value.value[i].componentName === component.componentName) count++; + } + + return count < component.uses; + }; + + // Delete component + const delComponent = () => { + if (currentIndex.value === -99) return; + + Modal.confirm({ + title: '删除组件', + content: '确定要删除该组件吗?', + onOk: () => { + value.value.splice(currentIndex.value, 1); + + if (value.value.length === 0) { + currentIndex.value = -99; + } else if (currentIndex.value === value.value.length) { + currentIndex.value--; + } + + let component = cloneDeep(value.value[currentIndex.value]); + changeCurrentIndex(currentIndex.value, component); + }, + }); + }; + + // Move component up + const moveUpComponent = () => { + if (currentIndex.value <= 0) return; + + const temp = cloneDeep(value.value[currentIndex.value]); + let prevIndex = currentIndex.value - 1; + const temp2 = cloneDeep(value.value[prevIndex]); + + if (prevIndex < 0 || (temp2.position && positionTypes.includes(temp2.position))) return; + + if (temp.position && positionTypes.includes(temp.position)) { + message.warning('该组件不能移动'); + return; + } + + temp.id = generateRandom(); + temp2.id = generateRandom(); + + value.value[currentIndex.value] = temp2; + value.value[prevIndex] = temp; + + changeCurrentIndex(prevIndex, temp); + }; + + // Move component down + const moveDownComponent = () => { + if (currentIndex.value >= value.value.length - 1) return; + + const nextIndex = currentIndex.value + 1; + const temp = cloneDeep(value.value[currentIndex.value]); + temp.id = generateRandom(); + + const temp2 = cloneDeep(value.value[nextIndex]); + temp2.id = generateRandom(); + + if (temp2.position && positionTypes.includes(temp2.position)) return; + + if (temp.position && positionTypes.includes(temp.position)) { + message.warning('该组件不能移动'); + return; + } + + value.value[currentIndex.value] = temp2; + value.value[nextIndex] = temp; + + changeCurrentIndex(nextIndex, temp); + }; + + // Copy component + const copyComponent = () => { + if (currentIndex.value < 0) return; + + let component = cloneDeep(value.value[currentIndex.value]); + component.id = generateRandom(); + + if (!checkComponentIsAdd(component)) { + message.warning(`不能复制,${component.componentTitle}最多只能添加${component.uses}个`); + return; + } + + if (component.position && positionTypes.includes(component.position)) { + message.warning(`不能复制,${component.componentTitle}只能添加1个`); + return; + } + + const index = currentIndex.value + 1; + value.value.splice(index, 0, component); + changeCurrentIndex(index, component); + }; + + // Reset component + const resetComponent = () => { + if (currentIndex.value < 0) return; + + Modal.confirm({ + title: '重置组件', + content: '确定要重置该组件吗?', + onOk: () => { + for (let i = 0; i < components.value.length; i++) { + if (components.value[i].componentName === editComponent.value.componentName) { + Object.assign(editComponent.value, components.value[i]); + break; + } + } + }, + }); + }; + + // Change current index + const changeCurrentIndex = (index: number, component?: any) => { + currentIndex.value = index; + if (index === -99) { + currentComponent.value = 'page-settings'; + } else if (component) { + currentComponent.value = component.path; + } + }; + + // Post message to iframe + const postMessage = () => { + const diyData = { + pageMode: pageMode.value, + currentIndex: currentIndex.value, + global: global.value, + value: value.value, + }; + + const iframe = document.getElementById('previewIframe') as HTMLIFrameElement; + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage(JSON.stringify(diyData), '*'); + } + }; + + // Validate + const verify = () => { + if (pageTitle.value === '') { + message.warning('请输入页面名称'); + changeCurrentIndex(-99); + return false; + } + + if (global.value.popWindow.show && !global.value.popWindow.imgUrl) { + message.warning('请上传弹窗图片'); + return false; + } + + for (let i = 0; i < value.value.length; i++) { + try { + if (value.value[i].verify) { + const res = value.value[i].verify(i); + if (!res.code) { + changeCurrentIndex(i, value.value[i]); + message.warning(res.message); + return false; + } + } + } catch (e) { + console.log('verify Error:', e, i, value.value[i]); + } + } + + return true; + }; + + return { + // State + id, + name, + pageTitle, + type, + typeName, + templateName, + isDefault, + pageMode, + load, + currentIndex, + editTab, + global, + value, + components, + + // Computed + currentComponent, + editComponent, + + // Actions + init, + generateRandom, + addComponent, + checkComponentIsAdd, + delComponent, + moveUpComponent, + moveDownComponent, + copyComponent, + resetComponent, + changeCurrentIndex, + postMessage, + verify, + }; +}); \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/access/data.ts b/admin-vben/apps/web-antd/src/views/channel/weapp/access/data.ts new file mode 100644 index 00000000..a791291e --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/access/data.ts @@ -0,0 +1,51 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; + +export interface WeappAccessApi { + getWeappConfig: () => Promise; + getAuthorizationUrl: (params: { site_id?: number }) => Promise; + getWxoplatform: () => Promise; +} + +export interface WeappAccessItem { + step: number; + title: string; + description: string; + status: 'completed' | 'current' | 'pending'; + action: string; + route?: string; +} + +export const accessSteps: WeappAccessItem[] = [ + { + step: 1, + title: '绑定微信小程序', + description: '绑定微信小程序账号,获取小程序相关信息', + status: 'current', + action: '立即绑定', + route: '/channel/weapp/config', + }, + { + step: 2, + title: '配置消息服务器', + description: '配置小程序消息服务器,确保消息正常接收', + status: 'pending', + action: '去配置', + route: '/channel/weapp/config', + }, + { + step: 3, + title: '订阅消息模板', + description: '同步订阅消息模板,开启消息通知功能', + status: 'pending', + action: '去同步', + route: '/channel/weapp/template', + }, + { + step: 4, + title: '发布小程序', + description: '发布小程序版本,提交审核并上线', + status: 'pending', + action: '去发布', + route: '/channel/weapp/code', + }, +]; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/access/list.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/access/list.vue new file mode 100644 index 00000000..2e3019de --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/access/list.vue @@ -0,0 +1,180 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/code/data.ts b/admin-vben/apps/web-antd/src/views/channel/weapp/code/data.ts new file mode 100644 index 00000000..bd5abebf --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/code/data.ts @@ -0,0 +1,120 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; + +export interface WeappCodeApi { + setWeappVersion: (data: any) => Promise; + getWeappVersionList: (params: any) => Promise; + getWeappUploadLog: (params: { task_key: string }) => Promise; + getWeappPreview: () => Promise; + uploadVersion: (data: any) => Promise; + siteWeappCommit: (data: any) => Promise; + undoAudit: (data: any) => Promise; +} + +export interface VersionItem { + id: number; + site_id: number; + version: string; + version_desc: string; + upload_time: number; + audit_time: number; + audit_result: string; + audit_id: string; + status: number; + task_key: string; + create_time: number; +} + +export const gridSchema: VxeGridProps = { + stripe: true, + showHeaderOverflow: true, + showOverflow: true, + height: 'auto', + rowConfig: { + isHover: true, + isCurrent: true, + }, + columnConfig: { + resizable: true, + }, + columns: [ + { + type: 'seq', + width: 50, + title: '序号', + }, + { + field: 'version', + title: '版本号', + minWidth: 120, + }, + { + field: 'version_desc', + title: '版本描述', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + width: 100, + slots: { + default: 'status', + }, + }, + { + field: 'upload_time', + title: '上传时间', + width: 180, + formatter: ({ cellValue }) => { + return cellValue ? new Date(cellValue * 1000).toLocaleString() : '-'; + }, + }, + { + field: 'audit_time', + title: '审核时间', + width: 180, + formatter: ({ cellValue }) => { + return cellValue ? new Date(cellValue * 1000).toLocaleString() : '-'; + }, + }, + { + field: 'audit_result', + title: '审核结果', + minWidth: 200, + slots: { + default: 'auditResult', + }, + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { + default: 'actions', + }, + }, + ], + toolbarConfig: { + custom: true, + refresh: true, + zoom: true, + }, + proxyConfig: { + ajax: { + query: async ({ page }, formValues) => { + // This will be implemented in the component + return { result: [], total: 0 }; + }, + }, + }, +}; + +export const statusMap = { + 0: { text: '草稿', color: 'default' }, + 1: { text: '上传中', color: 'processing' }, + 2: { text: '上传成功', color: 'success' }, + 3: { text: '审核中', color: 'processing' }, + 4: { text: '审核成功', color: 'success' }, + 5: { text: '审核失败', color: 'error' }, + 6: { text: '发布成功', color: 'success' }, + 7: { text: '发布失败', color: 'error' }, +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/code/list.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/code/list.vue new file mode 100644 index 00000000..7c5c1fdc --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/code/list.vue @@ -0,0 +1,392 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/config/data.ts b/admin-vben/apps/web-antd/src/views/channel/weapp/config/data.ts new file mode 100644 index 00000000..f37a3626 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/config/data.ts @@ -0,0 +1,155 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +export interface WeappConfigApi { + getWeappConfig: () => Promise; + setWeappConfig: (data: any) => Promise; + setWeappDomain: (data: any) => Promise; + getWeappPrivacySetting: () => Promise; + setWeappPrivacySetting: (data: any) => Promise; + getIsTradeManaged: () => Promise; +} + +export interface DomainForm { + request_domain: string; + ws_request_domain: string; + upload_domain: string; + download_domain: string; + udp_domain: string; + tcp_domain: string; +} + +export const basicFormSchema: VbenFormSchema[] = [ + { + fieldName: 'weapp_name', + label: '小程序名称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'weapp_original', + label: '小程序原始ID', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'app_id', + label: 'AppID', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'app_secret', + label: 'AppSecret', + component: 'InputPassword', + rules: 'required', + }, + { + fieldName: 'qr_code', + label: '小程序码', + component: 'Input', + componentProps: { + placeholder: '请输入小程序码图片地址', + }, + }, +]; + +export const serverFormSchema: VbenFormSchema[] = [ + { + fieldName: 'serve_url', + label: '服务器地址', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请以http://或https://开头', + }, + }, + { + fieldName: 'token', + label: '令牌(Token)', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '必须为3-32字符', + }, + }, + { + fieldName: 'encoding_aes_key', + label: '消息加密密钥', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '43位字符', + }, + }, + { + fieldName: 'encryption_type', + label: '加密方式', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: [ + { label: '明文模式', value: 1 }, + { label: '兼容模式', value: 2 }, + { label: '安全模式(推荐)', value: 3 }, + ], + }, + }, +]; + +export const domainFormSchema: VbenFormSchema[] = [ + { + fieldName: 'request_domain', + label: 'request合法域名', + component: 'Textarea', + componentProps: { + placeholder: '多个域名以;分隔,如:https://api.example.com;https://api2.example.com', + rows: 3, + }, + }, + { + fieldName: 'ws_request_domain', + label: 'socket合法域名', + component: 'Textarea', + componentProps: { + placeholder: '多个域名以;分隔,如:wss://ws.example.com;wss://ws2.example.com', + rows: 3, + }, + }, + { + fieldName: 'upload_domain', + label: 'uploadFile合法域名', + component: 'Textarea', + componentProps: { + placeholder: '多个域名以;分隔,如:https://upload.example.com', + rows: 3, + }, + }, + { + fieldName: 'download_domain', + label: 'downloadFile合法域名', + component: 'Textarea', + componentProps: { + placeholder: '多个域名以;分隔,如:https://download.example.com', + rows: 3, + }, + }, + { + fieldName: 'udp_domain', + label: 'udp合法域名', + component: 'Textarea', + componentProps: { + placeholder: '多个域名以;分隔,如:udp://udp.example.com', + rows: 3, + }, + }, + { + fieldName: 'tcp_domain', + label: 'tcp合法域名', + component: 'Textarea', + componentProps: { + placeholder: '多个域名以;分隔,如:tcp://tcp.example.com', + rows: 3, + }, + }, +]; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/config/list.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/config/list.vue new file mode 100644 index 00000000..a7f9f4a8 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/config/list.vue @@ -0,0 +1,271 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/modify-domain.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/modify-domain.vue new file mode 100644 index 00000000..5590006b --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/modify-domain.vue @@ -0,0 +1,254 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/modify-privacy.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/modify-privacy.vue new file mode 100644 index 00000000..2e0171ba --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/modify-privacy.vue @@ -0,0 +1,246 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/privacy-setting-form.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/privacy-setting-form.vue new file mode 100644 index 00000000..4d12dec5 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/config/modules/privacy-setting-form.vue @@ -0,0 +1,79 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/course/list.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/course/list.vue new file mode 100644 index 00000000..7709adbf --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/course/list.vue @@ -0,0 +1,114 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/template/data.ts b/admin-vben/apps/web-antd/src/views/channel/weapp/template/data.ts new file mode 100644 index 00000000..ec89a102 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/template/data.ts @@ -0,0 +1,105 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; + +export interface WeappTemplateApi { + getTemplateList: (params: { addon?: string }) => Promise; + getBatchAcquisition: (params: { addon?: string }) => Promise; + editNoticeStatus: (params: any) => Promise; +} + +export interface TemplateItem { + id: number; + site_id: number; + addon: string; + template_id: string; + title: string; + content: string; + example: string; + status: number; + create_time: number; +} + +export const gridSchema: VxeGridProps = { + stripe: true, + showHeaderOverflow: true, + showOverflow: true, + height: 'auto', + rowConfig: { + isHover: true, + isCurrent: true, + }, + columnConfig: { + resizable: true, + }, + columns: [ + { + type: 'seq', + width: 50, + title: '序号', + }, + { + field: 'addon', + title: '应用', + minWidth: 120, + formatter: ({ cellValue }) => { + return cellValue || '系统'; + }, + }, + { + field: 'title', + title: '模板标题', + minWidth: 200, + }, + { + field: 'template_id', + title: '模板ID', + minWidth: 200, + }, + { + field: 'content', + title: '模板内容', + minWidth: 300, + }, + { + field: 'example', + title: '示例', + minWidth: 250, + }, + { + field: 'status', + title: '状态', + width: 100, + slots: { + default: 'status', + }, + }, + { + field: 'create_time', + title: '创建时间', + width: 180, + formatter: ({ cellValue }) => { + return cellValue ? new Date(cellValue * 1000).toLocaleString() : '-'; + }, + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { + default: 'actions', + }, + }, + ], + toolbarConfig: { + custom: true, + refresh: true, + zoom: true, + }, + proxyConfig: { + ajax: { + query: async ({ page }, formValues) => { + // This will be implemented in the component + return { result: [], total: 0 }; + }, + }, + }, +}; \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/weapp/template/list.vue b/admin-vben/apps/web-antd/src/views/channel/weapp/template/list.vue new file mode 100644 index 00000000..75ffd5fe --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/weapp/template/list.vue @@ -0,0 +1,140 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/wechat/access/list.vue b/admin-vben/apps/web-antd/src/views/channel/wechat/access/list.vue new file mode 100644 index 00000000..d7c3a0a1 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/wechat/access/list.vue @@ -0,0 +1,220 @@ + + + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/wechat/config/list.vue b/admin-vben/apps/web-antd/src/views/channel/wechat/config/list.vue new file mode 100644 index 00000000..e9e712b1 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/wechat/config/list.vue @@ -0,0 +1,40 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/basic-config.vue b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/basic-config.vue new file mode 100644 index 00000000..95ab52ec --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/basic-config.vue @@ -0,0 +1,207 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/basic-form.vue b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/basic-form.vue new file mode 100644 index 00000000..57b5aa31 --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/basic-form.vue @@ -0,0 +1,177 @@ + + + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/domain-config.vue b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/domain-config.vue new file mode 100644 index 00000000..98ac5b8c --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/domain-config.vue @@ -0,0 +1,129 @@ + + + \ No newline at end of file diff --git a/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/domain-form.vue b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/domain-form.vue new file mode 100644 index 00000000..565582df --- /dev/null +++ b/admin-vben/apps/web-antd/src/views/channel/wechat/config/modules/domain-form.vue @@ -0,0 +1,123 @@ +