初始版本

This commit is contained in:
2026-04-23 17:14:29 +08:00
parent 0d0683a6e6
commit 6dca87b988
204 changed files with 3894 additions and 52759 deletions

View File

@@ -0,0 +1,17 @@
{
"version": 2,
"sessions": {
"f4c6db2b9dcf4c329722a3d2d50b4d44": [
{
"expertId": "SeniorDeveloper",
"name": "吴八哥",
"profession": "高级开发工程师",
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
"usedAt": 1776933111908,
"industryId": "02-Engineering"
}
]
},
"lastUpdated": 1776933527123
}

View File

@@ -0,0 +1,44 @@
# 2026-04-23 工作日志
## 项目框架整理
### 决策咨询网 (jczxw-pc)
从 websopy 模板复制过来的项目,完成框架整理:
**删除内容:**
- developer 相关(开发者中心、小程序/App开发平台
- console 相关(企业控制台)
- 无关页面ai-agent、miniapp、openclaw、apps、products、shop、deploy、flow、platform、create-app、qr-confirm、wx-scan、oa
- market 和 invite 模块
- public 中无关资源docs、icons、videos、wx-scan
- 无关 APIdeveloper、miniprogram、payment、ticket、shop
**保留内容:**
- admin 管理后台(重新规划功能)
- 核心框架和基础组件
**新建内容:**
1. 导航配置更新 (`app/config/nav.ts`):政策要闻、决策咨询、决策参考、专家资讯、智库观察、建言献策、会员服务、翰墨文谈、关于我们
2. SiteHeader 简化:移除开发者相关菜单,更新 Logo
3. Admin 管理后台:
- `admin/index.vue`:仪表盘(文章统计、专家待审、会员待审、建言待处理)
- `admin/articles/`:文章管理
- `admin/experts/`:专家管理(含审核)
- `admin/members/`:会员管理(企业/个人,含审核)
- `admin/suggestions/`:建言管理
4. 前台栏目页面:
- `news/`:政策要闻
- `consultation/`:决策咨询
- `reference/`决策参考含VIP数据服务
- `expert/`:专家资讯 + apply.vue 申请
- `think-tank/`:智库观察
- `suggestions/`:建言献策
- `membership/`:会员服务
- `hanmo/`:翰墨文谈
- `about/`:关于我们 + join/ 子页面(企业会员、个人会员申请)
**配置文件更新:**
- nuxt.config.ts移除 market SSR 规则,更新 titleTemplate

View File

View File

@@ -1,259 +0,0 @@
import request from '@/utils/request'
// ========== 权限申请接口 ==========
/**
* 获取权限申请列表
*/
export function getPermissionRequests(params?: {
page?: number
size?: number
status?: 'pending' | 'approved' | 'rejected'
}) {
return request.get<{
code: number
message: string
data: {
records: Array<{
id: string | number
repo: string
repoName: string
reason: string
status: 'pending' | 'approved' | 'rejected'
gitUsername: string
createdAt: string
reviewedAt?: string
reviewerName?: string
rejectReason?: string
}>
total: number
}
}>('/api/app/developer/permission-requests', { params })
}
/**
* 提交权限申请
*/
export function createPermissionRequest(data: {
repo: string
reason: string
gitUsername?: string
}) {
return request.post<{
code: number
message: string
data: {
id: string | number
repo: string
repoName: string
reason: string
status: 'pending'
createdAt: string
}
}>('/api/app/developer/permission-requests', data)
}
/**
* 获取权限申请统计
*/
export function getPermissionRequestStats() {
return request.get<{
code: number
message: string
data: {
pending: number
approved: number
rejected: number
total: number
}
}>('/api/app/developer/permission-requests/stats')
}
/**
* 获取可申请的仓库列表
*/
export function getAvailableRepositories() {
return request.get<{
code: number
message: string
data: Array<{
value: string
label: string
description: string
accessLevel?: 'read' | 'write' | 'admin'
isAccessible: boolean
}>
}>('/api/app/developer/permission-requests/available-repos')
}
// ========== Git账号绑定接口 ==========
/**
* 保存Git账号绑定信息
*/
export function saveGitAccount(data: {
username: string
email?: string
remark?: string
}) {
return request.post<{
code: number
message: string
data: {
userId: number
gitUsername: string
email?: string
remark?: string
savedAt: string
status: 'pending' | 'verified' | 'rejected'
}
}>('/api/app/developer/git-account', data)
}
/**
* 获取Git账号绑定状态
*/
export function getGitAccountStatus() {
return request.get<{
code: number
message: string
data: {
username?: string
email?: string
remark?: string
status: 'pending' | 'verified' | 'rejected' | 'not_bound'
lastUpdatedAt?: string
verificationNote?: string
}
}>('/api/app/developer/git-account/status')
}
/**
* 获取Gitea服务器信息
*/
export function getGiteaServerInfo() {
return request.get<{
code: number
message: string
data: {
url: string
version: string
registrationEnabled: boolean
requireEmailConfirmation: boolean
maxRepoCreation: number
}
}>('/api/app/developer/gitea-info')
}
// ========== 管理端 - Git账号审核接口 ==========
export interface GitAccountItem {
id: number
userId: number
username: string
email?: string
remark?: string
status: 'pending' | 'verified' | 'rejected'
verificationNote?: string
createTime: string
updateTime: string
tenantId: number
}
/**
* 分页查询Git账号绑定列表管理端
*/
export function pageGitAccounts(params?: {
page?: number
size?: number
status?: string
keyword?: string
}) {
return request.get<{
code: number
message: string
data: {
records: GitAccountItem[]
total: number
current: number
size: number
}
}>('/api/app/developer/git-account/list', { params })
}
/**
* 审核Git账号绑定 - 通过
*/
export function approveGitAccount(id: number, note?: string) {
return request.put<{
code: number
message: string
}>(`/api/app/developer/git-account/${id}/approve`, { note })
}
/**
* 审核Git账号绑定 - 拒绝
*/
export function rejectGitAccount(id: number, reason: string) {
return request.put<{
code: number
message: string
}>(`/api/app/developer/git-account/${id}/reject`, { reason })
}
// ========== 管理端 - 权限申请审核接口 ==========
export interface PermissionRequestItem {
id: number
userId: number
gitUsername: string
repo: string
repoName: string
reason: string
status: 'pending' | 'approved' | 'rejected'
createdAt: string
reviewedAt?: string
reviewerName?: string
rejectReason?: string
}
/**
* 分页查询权限申请列表(管理端)
*/
export function pagePermissionRequestsAdmin(params?: {
page?: number
size?: number
status?: string
keyword?: string
}) {
return request.get<{
code: number
message: string
data: {
records: PermissionRequestItem[]
total: number
current: number
size: number
}
}>('/api/app/developer/permission-requests/page', { params })
}
/**
* 审核权限申请 - 通过
*/
export function approvePermissionRequest(id: number, note?: string) {
return request.put<{
code: number
message: string
}>(`/api/app/developer/permission-requests/${id}/approve`, { note })
}
/**
* 审核权限申请 - 拒绝
*/
export function rejectPermissionRequest(id: number, reason: string) {
return request.put<{
code: number
message: string
}>(`/api/app/developer/permission-requests/${id}/reject`, { reason })
}

View File

@@ -1,50 +0,0 @@
import {MODULES_API_URL} from '@/config/setting';
/**
* 小程序码参数
*/
export interface MiniProgramCodeParam {
page?: string;
scene: string;
width?: number;
checkPath?: boolean;
envVersion?: 'release' | 'trial' | 'develop';
}
/**
* 生成小程序码
*/
export async function generateMiniProgramCode(data: MiniProgramCodeParam) {
try {
const url = '/wx-login/getOrderQRCodeUnlimited/' + data.scene;
const fullUrl = MODULES_API_URL + `${url}`;
console.log('生成小程序码URL:', fullUrl);
console.log('小程序码参数:', data);
console.log('scene 参数:', data.scene);
// 直接返回URL让浏览器处理图片加载
// scene 参数中包含了租户ID信息
return fullUrl;
} catch (error: any) {
console.error('生成小程序码失败:', error);
throw new Error(error.message || '生成小程序码失败');
}
}
/**
* 生成邀请小程序码
*/
export async function generateInviteCode(inviterId: number) {
const scene = `uid_${inviterId}`;
console.log('生成邀请小程序码 scene:', scene);
return generateMiniProgramCode({
page: 'pages/index/index',
scene: scene,
width: 180,
checkPath: true,
envVersion: 'trial'
});
}

View File

@@ -1,344 +0,0 @@
/**
* 支付模块 API 封装
* 支持:微信 JSAPI / 支付宝 / Native / 余额
*/
import request from '@/utils/request'
import type { ApiResult } from '@/api'
import { SERVER_API_URL } from '@/config/setting'
/** 支付方式 */
export type PayType = 'wechat' | 'alipay' | 'native' | 'balance' | number
/** 支付渠道 */
export type PayChannel = 'wechat_jsapi' | 'alipay_wap' | 'wechat_native' | 'balance'
/** 支付参数 */
export interface PayParams {
orderNo: string
orderId?: number
payType: PayChannel
/** 微信 JSAPI 必填:用户 openid */
openId?: string
/** 微信 JSAPI 必填:支付主体类型 */
subject?: string
/** 支付主体描述 */
body?: string
/** 支付金额(分) */
totalAmount?: number
/** 前端回调 URL */
returnUrl?: string
}
/** 微信 JSAPI 支付参数 */
export interface WechatJsapiParams {
orderNo: string
openId: string
subject: string
body?: string
totalAmount?: number
returnUrl?: string
}
/** 支付宝 WAP/Web 支付参数 */
export interface AlipayParams {
orderNo: string
subject: string
body?: string
totalAmount?: number
returnUrl?: string
}
/** Native 支付参数 */
export interface NativeParams {
orderNo: string
subject: string
body?: string
totalAmount?: number
}
/** 支付结果 */
export interface PayResult {
codeUrl?: string // Native 支付二维码链接
qrcode?: string // 二维码内容
paymentUrl?: string // 跳转支付链接
payUrl?: string // 支付链接
prepayId?: string // 预支付订单号
mwebUrl?: string // 微信 H5 支付链接
tradeNo?: string // 交易流水号
orderNo?: string // 订单号
orderId?: number // 订单ID
}
/** 支付状态 */
export interface PayStatus {
paid: boolean
payStatus: number // 0 未支付1 已支付
orderStatus: number // 订单状态
payTime?: string // 支付时间
transactionId?: string // 微信/支付宝交易号
}
/**
* 统一支付接口
* @deprecated 使用具体渠道接口
*/
export async function createPayment(data: Record<string, unknown>): Promise<PayResult> {
const res = await request.post<ApiResult<PayResult>>(
SERVER_API_URL + '/system/payment/create',
data
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 微信 JSAPI 支付
* 适用于微信内置浏览器 / 公众号 / 小程序环境
*/
export async function createWechatJsapiPay(params: WechatJsapiParams): Promise<PayResult> {
const res = await request.post<ApiResult<PayResult>>(
SERVER_API_URL + '/system/wx-jsapi-pay/unified-order',
params
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 微信 H5 支付
* 适用于非微信浏览器
*/
export async function createWechatH5Pay(params: { orderNo: string; subject: string; body?: string; totalAmount?: number; returnUrl?: string }): Promise<PayResult> {
const res = await request.post<ApiResult<PayResult>>(
SERVER_API_URL + '/system/wx-h5-pay/unified-order',
params
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 微信 Native 支付(扫码支付)
*/
export async function createWechatNativePay(params: NativeParams): Promise<PayResult> {
const res = await request.post<ApiResult<PayResult>>(
SERVER_API_URL + '/system/wx-native-pay/unified-order',
params
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 支付宝 WAP/Web 支付
*/
export async function createAlipayPay(params: AlipayParams): Promise<PayResult> {
const res = await request.post<ApiResult<PayResult>>(
SERVER_API_URL + '/system/alipay/unified-order',
params
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 查询支付状态
*/
export async function queryPayStatus(orderNo: string): Promise<PayStatus> {
const res = await request.get<ApiResult<PayStatus>>(
SERVER_API_URL + '/system/payment/query-status',
{ params: { orderNo } }
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 查询充值状态(专门用于充值订单)
*/
export async function queryRechargeStatus(orderNo: string): Promise<{
paid: boolean
payStatus: number
balance?: number
}> {
const res = await request.get<ApiResult<{
paid: boolean
payStatus: number
balance?: number
}>>(
SERVER_API_URL + '/system/wx-native-pay/recharge-status',
{ params: { orderNo } }
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 手动确认充值(测试用,生产环境由支付回调触发)
*/
export async function confirmRecharge(orderNo: string): Promise<{
orderNo: string
paid: boolean
payPrice: number
giftMoney: number
actualMoney: number
balance: number
}> {
const res = await request.post<ApiResult<{
orderNo: string
paid: boolean
payPrice: number
giftMoney: number
actualMoney: number
balance: number
}>>(
SERVER_API_URL + '/system/wx-native-pay/confirm-recharge',
null,
{ params: { orderNo } }
)
if (res.data.code === 0) {
return res.data.data!
}
return Promise.reject(new Error(res.data.message))
}
/**
* 取消订单
*/
export async function cancelOrder(orderId: number): Promise<void> {
const res = await request.post<ApiResult<void>>(
SERVER_API_URL + '/system/order/cancel',
{ orderId }
)
if (res.data.code !== 0) {
return Promise.reject(new Error(res.data.message))
}
}
/**
* 申请退款
*/
export async function applyRefund(orderId: number, reason?: string): Promise<void> {
const res = await request.post<ApiResult<void>>(
SERVER_API_URL + '/system/order/refund',
{ orderId, reason }
)
if (res.data.code !== 0) {
return Promise.reject(new Error(res.data.message))
}
}
/**
* 判断当前环境
*/
export function detectPayEnvironment(): 'wechat' | 'alipay' | 'desktop' | 'mobile' {
const ua = navigator.userAgent.toLowerCase()
// 微信环境
if (/micromessenger/.test(ua)) {
return 'wechat'
}
// 支付宝环境
if (/alipayclient/.test(ua)) {
return 'alipay'
}
// 移动端
if (/mobile|android|iphone|ipad|tablet/i.test(ua)) {
return 'mobile'
}
// 桌面端
return 'desktop'
}
/**
* 检测是否在微信内置浏览器
*/
export function isWechatBrowser(): boolean {
return /micromessenger/.test(navigator.userAgent.toLowerCase())
}
/**
* 检测是否在支付宝内置浏览器
*/
export function isAlipayBrowser(): boolean {
return /alipayclient/.test(navigator.userAgent.toLowerCase())
}
/**
* 调起微信 JSAPI 支付
* 需要先引入微信 JSSDK 并完成签名
*/
export function callWechatJsapi(params: {
appId: string
timestamp: string
nonceStr: string
package: string
signType?: string
paySign: string
}): Promise<'ok'> {
return new Promise((resolve, reject) => {
if (typeof window.WeixinJSBridge === 'undefined') {
// 尝试通过 WeChat JSSDK 调用
if (typeof window.jWeixin !== 'undefined') {
window.jWeixin.chooseWXPay({
...params,
success: () => resolve('ok'),
fail: (err: unknown) => reject(err)
})
} else {
reject(new Error('微信 JSSDK 未加载'))
}
return
}
window.WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
appId: params.appId,
timeStamp: params.timestamp,
nonceStr: params.nonceStr,
package: params.package,
signType: params.signType || 'MD5',
paySign: params.paySign
},
(res: { err_msg: string }) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
resolve('ok')
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
reject(new Error('用户取消支付'))
} else {
reject(new Error(res.err_msg))
}
}
)
})
}
// 扩展 Window 类型
declare global {
interface Window {
WeixinJSBridge?: {
invoke: (api: string, params: Record<string, unknown>, callback: (res: { err_msg: string }) => void) => void
}
jWeixin?: {
chooseWXPay: (params: Record<string, unknown> & { success?: () => void; fail?: (err: unknown) => void }) => void
}
}
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopCommissionRole, ShopCommissionRoleParam } from './model';
/**
* 分页查询分红角色
*/
export async function pageShopCommissionRole(params: ShopCommissionRoleParam) {
const res = await request.get<ApiResult<PageResult<ShopCommissionRole>>>(
'/shop/shop-commission-role/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分红角色列表
*/
export async function listShopCommissionRole(params?: ShopCommissionRoleParam) {
const res = await request.get<ApiResult<ShopCommissionRole[]>>(
'/shop/shop-commission-role',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分红角色
*/
export async function addShopCommissionRole(data: ShopCommissionRole) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-commission-role',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分红角色
*/
export async function updateShopCommissionRole(data: ShopCommissionRole) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-commission-role',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分红角色
*/
export async function removeShopCommissionRole(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-commission-role/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分红角色
*/
export async function removeBatchShopCommissionRole(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-commission-role/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分红角色
*/
export async function getShopCommissionRole(id: number) {
const res = await request.get<ApiResult<ShopCommissionRole>>(
'/shop/shop-commission-role/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,35 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分红角色
*/
export interface ShopCommissionRole {
//
id?: number;
//
title?: string;
//
provinceId?: number;
//
cityId?: number;
//
regionId?: number;
// 状态, 0正常, 1异常
status?: number;
// 备注
description?: string;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
//
sortNumber?: number;
}
/**
* 分红角色搜索条件
*/
export interface ShopCommissionRoleParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopCoupon, ShopCouponParam } from './model';
/**
* 分页查询优惠券
*/
export async function pageShopCoupon(params: ShopCouponParam) {
const res = await request.get<ApiResult<PageResult<ShopCoupon>>>(
'/shop/shop-coupon/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询优惠券列表
*/
export async function listShopCoupon(params?: ShopCouponParam) {
const res = await request.get<ApiResult<ShopCoupon[]>>(
'/shop/shop-coupon',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加优惠券
*/
export async function addShopCoupon(data: ShopCoupon) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改优惠券
*/
export async function updateShopCoupon(data: ShopCoupon) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除优惠券
*/
export async function removeShopCoupon(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-coupon/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除优惠券
*/
export async function removeBatchShopCoupon(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-coupon/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询优惠券
*/
export async function getShopCoupon(id: number) {
const res = await request.get<ApiResult<ShopCoupon>>(
'/shop/shop-coupon/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,71 +0,0 @@
import type { PageParam } from '@/api/index';
import {ShopCouponApplyCate} from "@/api/shop/shopCouponApplyCate/model";
import {ShopCouponApplyItem} from "@/api/shop/shopCouponApplyItem/model";
/**
* 优惠券
*/
export interface ShopCoupon {
// id
id?: number;
// 优惠券名称
name?: string;
// 优惠券描述
description?: string;
// 优惠券类型(10满减券 20折扣券 30免费劵)
type?: number;
// 满减券-减免金额
reducePrice?: string;
// 折扣券-折扣率(0-100)
discount?: number;
// 最低消费金额
minPrice?: string;
// 到期类型(10领取后生效 20固定时间)
expireType?: number;
// 领取后生效-有效天数
expireDay?: number;
// 有效期开始时间
startTime?: string | Date;
// 有效期结束时间
endTime?: string | Date;
// 适用范围(10全部商品 20指定商品 30指定分类)
applyRange?: number;
// 适用范围配置(json格式)
applyRangeConfig?: string;
// 是否过期(0未过期 1已过期)
isExpire?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1禁用
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 创建用户ID
userId?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string | Date;
// 修改时间
updateTime?: string | Date;
// 发放总数量(-1表示无限制)
totalCount?: number;
// 已发放数量
issuedCount?: number;
// 每人限领数量(-1表示无限制)
limitPerUser?: number;
// 是否启用(0禁用 1启用)
enabled?: string;
couponApplyCateList?: ShopCouponApplyCate[];
couponApplyItemList?: ShopCouponApplyItem[];
}
/**
* 优惠券搜索条件
*/
export interface ShopCouponParam extends PageParam {
id?: number;
name?: string;
type?: number;
keywords?: string;
}

View File

@@ -1,11 +0,0 @@
/**
* 优惠券
*/
export interface ShopCouponApplyCate {
id?: number;
couponId?: number;
cateId?: number;
cateLevel?: number;
}

View File

@@ -1,11 +0,0 @@
/**
* 优惠券
*/
export interface ShopCouponApplyItem {
id?: number;
couponId?: number;
type?: number;
pk?: number;
}

View File

@@ -1,158 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerApply, ShopDealerApplyParam } from './model';
/**
* 分页查询分销商申请记录表
*/
export async function pageShopDealerApply(params: ShopDealerApplyParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerApply>>>(
'/shop/shop-dealer-apply/page',
{params}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商申请记录表列表
*/
export async function listShopDealerApply(params?: ShopDealerApplyParam) {
const res = await request.get<ApiResult<ShopDealerApply[]>>(
'/shop/shop-dealer-apply',
{params}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商申请记录表
*/
export async function addShopDealerApply(data: ShopDealerApply) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-apply',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商申请记录表
*/
export async function updateShopDealerApply(data: ShopDealerApply) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-apply',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商申请记录表
*/
export async function removeShopDealerApply(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-apply/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商申请记录表
*/
export async function removeBatchShopDealerApply(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-apply/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商申请记录表
*/
export async function getShopDealerApply(id: number) {
const res = await request.get<ApiResult<ShopDealerApply>>(
'/shop/shop-dealer-apply/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 审核通过分销商申请
*/
export async function approveShopDealerApply(id: number) {
const res = await request.put<ApiResult<unknown>>(
`/shop/shop-dealer-apply/${id}/approve`
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 驳回分销商申请
*/
export async function rejectShopDealerApply(id: number, data: { rejectReason: string }) {
const res = await request.put<ApiResult<unknown>>(
`/shop/shop-dealer-apply/${id}/reject`,
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量审核通过分销商申请
*/
export async function batchApproveShopDealerApply(ids: number[]) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-apply/batch-approve',
{ ids }
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 导入经销商申请
*/
export async function importShopDealerApplies(file: File) {
const formData = new FormData();
formData.append('file', file);
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-apply/import',
formData
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,61 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商申请记录表
*/
export interface ShopDealerApply {
// 主键ID
applyId?: number;
// 类型
type?: number;
// 用户ID
userId?: number;
// 昵称
nickName?: string;
// 姓名
realName?: string;
// 经销商名称
dealerName?: string;
// 手机号
mobile?: string;
// 分销比例
rate?: number;
// 推荐人用户ID
refereeId?: number;
// 推荐人姓名
refereeName?: string;
// 申请方式(10需后台审核 20无需审核)
applyType?: number;
// 申请时间
applyTime?: string | number | Date;
// 审核状态 (10待审核 20审核通过 30驳回)
applyStatus?: number;
// 审核时间
auditTime?: string | number | Date;
// 驳回原因
rejectReason?: string;
description?: string;
// 商城ID
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 分销商申请记录表搜索条件
*/
export interface ShopDealerApplyParam extends PageParam {
applyId?: number;
userId?: number;
realName?: string;
dealerName?: string;
mobile?: string;
refereeId?: number;
applyType?: number;
applyStatus?: number;
startTime?: string;
endTime?: string;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerCapital, ShopDealerCapitalParam } from './model';
/**
* 分页查询分销商资金明细表
*/
export async function pageShopDealerCapital(params: ShopDealerCapitalParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerCapital>>>(
'/shop/shop-dealer-capital/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商资金明细表列表
*/
export async function listShopDealerCapital(params?: ShopDealerCapitalParam) {
const res = await request.get<ApiResult<ShopDealerCapital[]>>(
'/shop/shop-dealer-capital',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商资金明细表
*/
export async function addShopDealerCapital(data: ShopDealerCapital) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-capital',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商资金明细表
*/
export async function updateShopDealerCapital(data: ShopDealerCapital) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-capital',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商资金明细表
*/
export async function removeShopDealerCapital(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-capital/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商资金明细表
*/
export async function removeBatchShopDealerCapital(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-capital/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商资金明细表
*/
export async function getShopDealerCapital(id: number) {
const res = await request.get<ApiResult<ShopDealerCapital>>(
'/shop/shop-dealer-capital/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,39 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商资金明细表
*/
export interface ShopDealerCapital {
// 主键ID
id?: number;
// 分销商用户ID
userId?: number;
// 订单ID
orderId?: number;
// 订单编号
orderNo?: string;
// 资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)
flowType?: number;
// 金额
money?: string;
// 描述
description?: string;
// 对方用户ID
toUserId?: number;
// 商城ID
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 分销商资金明细表搜索条件
*/
export interface ShopDealerCapitalParam extends PageParam {
id?: number;
userId?: number;
toUserId?: number;
keywords?: string;
}

View File

@@ -1,221 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerOrder, ShopDealerOrderParam } from './model';
import { utils, writeFile } from 'xlsx';
import { message } from 'ant-design-vue';
import { getTenantId } from '@/utils/domain';
/**
* 分页查询分销商订单记录表
*/
export async function pageShopDealerOrder(params: ShopDealerOrderParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerOrder>>>(
'/shop/shop-dealer-order/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商订单记录表列表
*/
export async function listShopDealerOrder(params?: ShopDealerOrderParam) {
const res = await request.get<ApiResult<ShopDealerOrder[]>>(
'/shop/shop-dealer-order',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商订单记录表
*/
export async function addShopDealerOrder(data: ShopDealerOrder) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-order',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商订单记录表
*/
export async function updateShopDealerOrder(data: ShopDealerOrder) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-order',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商订单记录表
*/
export async function removeShopDealerOrder(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-order/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商订单记录表
*/
export async function removeBatchShopDealerOrder(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-order/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商订单记录表
*/
export async function getShopDealerOrder(id: number) {
const res = await request.get<ApiResult<ShopDealerOrder>>(
'/shop/shop-dealer-order/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 导入分销商订单
*/
export async function importShopDealerOrder(file: File) {
const formData = new FormData();
formData.append('file', file);
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-order/import',
formData
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 导出分销商订单
*/
export async function exportShopDealerOrder(params?: ShopDealerOrderParam) {
// 显示导出加载提示
message.loading('正在准备导出数据...', 0);
try {
// 获取数据
const list = await listShopDealerOrder(params);
if (!list || list.length === 0) {
message.destroy();
message.warning('没有数据可以导出');
return;
}
// 构建导出数据
const array: (string | number)[][] = [
[
'订单ID',
'买家用户ID',
'订单总金额',
'一级分销商ID',
'一级佣金',
'二级分销商ID',
'二级佣金',
'三级分销商ID',
'三级佣金',
'订单状态',
'结算状态',
'结算时间',
'创建时间'
]
];
list.forEach((order: ShopDealerOrder) => {
array.push([
order.orderId || '',
order.userId || '',
order.orderPrice || '0',
order.firstUserId || '',
order.firstMoney || '0',
order.secondUserId || '',
order.secondMoney || '0',
order.thirdUserId || '',
order.thirdMoney || '0',
order.isInvalid === 0 ? '有效' : '失效',
order.isSettled === 0 ? '未结算' : '已结算',
order.settleTime ? new Date(order.settleTime).toLocaleString() : '',
order.createTime || ''
]);
});
// 创建工作簿
const sheetName = `shop_dealer_order_${getTenantId()}`;
const workbook = {
SheetNames: [sheetName],
Sheets: {}
};
const sheet = utils.aoa_to_sheet(array);
workbook.Sheets[sheetName] = sheet;
// 设置列宽
sheet['!cols'] = [
{ wch: 15 }, // 订单ID
{ wch: 12 }, // 买家用户ID
{ wch: 12 }, // 订单总金额
{ wch: 15 }, // 一级分销商ID
{ wch: 12 }, // 一级佣金
{ wch: 15 }, // 二级分销商ID
{ wch: 12 }, // 二级佣金
{ wch: 15 }, // 三级分销商ID
{ wch: 12 }, // 三级佣金
{ wch: 10 }, // 订单状态
{ wch: 10 }, // 结算状态
{ wch: 20 }, // 结算时间
{ wch: 20 } // 创建时间
];
message.destroy();
message.loading('正在生成Excel文件...', 0);
// 延迟写入文件,确保消息提示显示
setTimeout(() => {
writeFile(workbook, `${sheetName}.xlsx`);
message.destroy();
message.success(`成功导出 ${list.length} 条记录`);
}, 1000);
} catch (error: any) {
message.destroy();
message.error(error.message || '导出失败,请重试');
}
}

View File

@@ -1,77 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商订单记录表
*/
export interface ShopDealerOrder {
// 主键ID
id?: number;
// 买家用户ID
userId?: number;
// 商品名称
title?: string;
// 买家用户昵称
nickname?: string;
// 订单编号
orderNo?: string;
// 订单总金额(不含运费)
orderPrice?: string;
// 结算金额
settledPrice?: string;
// 换算成度
degreePrice?: string;
// 支付金额
payPrice?: string;
// 分销商用户id(一级)
firstUserId?: number;
// 分销商用户id(二级)
secondUserId?: number;
// 分销商用户id(三级)
thirdUserId?: number;
// 分销佣金(一级)
firstMoney?: string;
// 分销佣金(二级)
secondMoney?: string;
// 分销佣金(三级)
thirdMoney?: string;
// 一级分销商昵称
firstNickname?: string;
// 二级分销商昵称
secondNickname?: string;
// 三级分销商昵称
thirdNickname?: string;
// 分销比例
rate?: number;
// 商品单价
price?: string;
// 订单月份
month?: string;
// 订单是否失效(0未失效 1已失效)
isInvalid?: number;
// 佣金结算(0未结算 1已结算)
isSettled?: number;
// 结算时间
settleTime?: number;
// 订单备注
description?: string;
// 商城ID
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 分销商订单记录表搜索条件
*/
export interface ShopDealerOrderParam extends PageParam {
id?: number;
orderNo?: string;
productName?: string;
userId?: number;
isInvalid?: number;
isSettled?: number;
myOrder?: number;
keywords?: string;
}

View File

@@ -1,131 +0,0 @@
import request from '@/utils/request';
import type { ShopDealerPoster, ShopDealerPosterParam } from './model';
/**
* 分页查询分销商海报设置
*/
export async function pageShopDealerPoster(params: ShopDealerPosterParam) {
const res = await request.get('/shop/dealer/poster/page', { params });
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商海报设置列表
*/
export async function listShopDealerPoster(params?: ShopDealerPosterParam) {
const res = await request.get('/shop/dealer/poster/list', { params });
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商海报设置
*/
export async function getShopDealerPoster(id: number) {
const res = await request.get('/shop/dealer/poster/' + id);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取当前海报配置
*/
export async function getCurrentPosterConfig() {
const res = await request.get('/shop/dealer/poster/config');
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商海报设置
*/
export async function addShopDealerPoster(data: ShopDealerPoster) {
const res = await request.post('/shop/dealer/poster', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商海报设置
*/
export async function updateShopDealerPoster(data: ShopDealerPoster) {
const res = await request.put('/shop/dealer/poster', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 保存海报配置
*/
export async function savePosterConfig(data: any) {
const res = await request.post('/shop/dealer/poster/config', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商海报设置
*/
export async function removeShopDealerPoster(id: number) {
const res = await request.delete('/shop/dealer/poster/' + id);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商海报设置
*/
export async function removeBatchShopDealerPoster(ids: (number | undefined)[]) {
const res = await request.delete('/shop/dealer/poster/batch', { data: ids });
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 生成海报
*/
export async function generatePoster(userId: number, config?: any) {
const res = await request.post('/shop/dealer/poster/generate', { userId, config });
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 上传海报背景图片
*/
export async function uploadPosterBackground(file: File) {
const formData = new FormData();
formData.append('file', file);
const res = await request.post('/shop/dealer/poster/upload/background', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,93 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商海报设置
*/
export interface ShopDealerPoster {
// 主键ID
id?: number;
// 海报名称
name?: string;
// 背景图片URL
backgroundImage?: string;
// 海报配置(JSON格式)
config?: string;
// 是否启用
enabled?: boolean;
// 是否默认
isDefault?: boolean;
// 排序
sort?: number;
// 商城ID
tenantId?: number;
// 创建时间
createTime?: string | Date;
// 修改时间
updateTime?: string | Date;
}
/**
* 海报配置
*/
export interface PosterConfig {
// 背景图片
backgroundImage?: string;
// 海报尺寸
width?: number;
height?: number;
// 是否显示头像
showAvatar?: boolean;
// 头像URL
avatarUrl?: string;
// 头像宽度
avatarWidth?: number;
// 头像形状 circle|square
avatarShape?: string;
// 是否显示昵称
showNickname?: boolean;
// 昵称
nickname?: string;
// 昵称字体大小
nicknameFontSize?: number;
// 昵称颜色
nicknameColor?: string;
// 是否显示二维码
showQrcode?: boolean;
// 二维码URL
qrcodeUrl?: string;
// 二维码宽度
qrcodeWidth?: number;
// 元素位置配置
elements?: {
avatar?: { x: number; y: number };
nickname?: { x: number; y: number };
qrcode?: { x: number; y: number };
[key: string]: { x: number; y: number } | undefined;
};
}
/**
* 分销商海报设置搜索条件
*/
export interface ShopDealerPosterParam extends PageParam {
id?: number;
name?: string;
enabled?: boolean;
keywords?: string;
}
/**
* 海报生成参数
*/
export interface PosterGenerateParam {
// 用户ID
userId: number;
// 海报配置
config?: PosterConfig;
// 用户信息
userInfo?: {
nickname?: string;
avatar?: string;
qrcode?: string;
};
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerRecord, ShopDealerRecordParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询客户跟进情况
*/
export async function pageShopDealerRecord(params: ShopDealerRecordParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerRecord>>>(
MODULES_API_URL + '/shop/shop-dealer-record/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询客户跟进情况列表
*/
export async function listShopDealerRecord(params?: ShopDealerRecordParam) {
const res = await request.get<ApiResult<ShopDealerRecord[]>>(
MODULES_API_URL + '/shop/shop-dealer-record',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加客户跟进情况
*/
export async function addShopDealerRecord(data: ShopDealerRecord) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-dealer-record',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改客户跟进情况
*/
export async function updateShopDealerRecord(data: ShopDealerRecord) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-dealer-record',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除客户跟进情况
*/
export async function removeShopDealerRecord(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-dealer-record/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除客户跟进情况
*/
export async function removeBatchShopDealerRecord(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-dealer-record/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询客户跟进情况
*/
export async function getShopDealerRecord(id: number) {
const res = await request.get<ApiResult<ShopDealerRecord>>(
MODULES_API_URL + '/shop/shop-dealer-record/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,39 +0,0 @@
import type { PageParam } from '@/api';
/**
* 客户跟进情况
*/
export interface ShopDealerRecord {
// ID
id?: number;
// 上级id, 0是顶级
parentId?: number;
// 客户ID
dealerId?: number;
// 内容
content?: string;
// 用户ID
userId?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 备注
description?: string;
// 状态, 0待处理, 1已完成
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 客户跟进情况搜索条件
*/
export interface ShopDealerRecordParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerReferee, ShopDealerRefereeParam } from './model';
/**
* 分页查询分销商推荐关系表
*/
export async function pageShopDealerReferee(params: ShopDealerRefereeParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerReferee>>>(
'/shop/shop-dealer-referee/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商推荐关系表列表
*/
export async function listShopDealerReferee(params?: ShopDealerRefereeParam) {
const res = await request.get<ApiResult<ShopDealerReferee[]>>(
'/shop/shop-dealer-referee',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商推荐关系表
*/
export async function addShopDealerReferee(data: ShopDealerReferee) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-referee',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商推荐关系表
*/
export async function updateShopDealerReferee(data: ShopDealerReferee) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-referee',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商推荐关系表
*/
export async function removeShopDealerReferee(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-referee/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商推荐关系表
*/
export async function removeBatchShopDealerReferee(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-referee/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商推荐关系表
*/
export async function getShopDealerReferee(id: number) {
const res = await request.get<ApiResult<ShopDealerReferee>>(
'/shop/shop-dealer-referee/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,48 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商推荐关系表
*/
export interface ShopDealerReferee {
// 主键ID
id?: number;
// 分销商用户ID
dealerId?: number;
// 分销商名称
dealerName?: string;
// 分销商头像
dealerAvatar?: string;
// 分销商手机号
dealerPhone?: string;
// 用户id(被推荐人)
userId?: number;
// 昵称
nickname?: string;
// 头像
avatar?: string;
// 别名
alias?: string;
// 手机号
phone?: string;
// 推荐关系层级(1,2,3)
level?: number;
// 商城ID
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 分销商推荐关系表搜索条件
*/
export interface ShopDealerRefereeParam extends PageParam {
id?: number;
dealerId?: number;
userId?: number;
level?: number;
startTime?: string;
endTime?: string;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerSetting, ShopDealerSettingParam } from './model';
/**
* 分页查询分销商设置表
*/
export async function pageShopDealerSetting(params: ShopDealerSettingParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerSetting>>>(
'/shop/shop-dealer-setting/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商设置表列表
*/
export async function listShopDealerSetting(params?: ShopDealerSettingParam) {
const res = await request.get<ApiResult<ShopDealerSetting[]>>(
'/shop/shop-dealer-setting',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商设置表
*/
export async function addShopDealerSetting(data: ShopDealerSetting) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-setting',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商设置表
*/
export async function updateShopDealerSetting(data: ShopDealerSetting) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-setting',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商设置表
*/
export async function removeShopDealerSetting(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-setting/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商设置表
*/
export async function removeBatchShopDealerSetting(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-setting/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商设置表
*/
export async function getShopDealerSetting(id: number) {
const res = await request.get<ApiResult<ShopDealerSetting>>(
'/shop/shop-dealer-setting/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,25 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商设置表
*/
export interface ShopDealerSetting {
// 设置项标示
key?: string;
// 设置项描述
describe?: string;
// 设置内容(json格式)
values?: string;
// 商城ID
tenantId?: number;
// 更新时间
updateTime?: number;
}
/**
* 分销商设置表搜索条件
*/
export interface ShopDealerSettingParam extends PageParam {
key?: number;
keywords?: string;
}

View File

@@ -1,140 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerUser, ShopDealerUserParam } from './model';
/**
* 分页查询分销商用户记录表
*/
export async function pageShopDealerUser(params: ShopDealerUserParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerUser>>>(
'/shop/shop-dealer-user/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商用户记录表列表
*/
export async function listShopDealerUser(params?: ShopDealerUserParam) {
const res = await request.get<ApiResult<ShopDealerUser[]>>(
'/shop/shop-dealer-user',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商用户记录表
*/
export async function addShopDealerUser(data: ShopDealerUser) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-user',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商用户记录表
*/
export async function updateShopDealerUser(data: ShopDealerUser) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-user',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商用户记录表
*/
export async function removeShopDealerUser(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-user/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商用户记录表
*/
export async function removeBatchShopDealerUser(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-user/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商用户记录表
*/
export async function getShopDealerUser(id: number) {
const res = await request.get<ApiResult<ShopDealerUser>>(
'/shop/shop-dealer-user/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 导入分销商用户
*/
export async function importShopDealerUsers(file: File) {
const formData = new FormData();
formData.append('file', file);
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-user/import',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 导出分销商用户
*/
export async function exportShopDealerUsers(params?: ShopDealerUserParam) {
const res = await request.get<Blob>(
'/shop/shop-dealer-user/export',
{
params,
responseType: 'blob'
}
);
return res.data;
}

View File

@@ -1,60 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商用户记录表
*/
export interface ShopDealerUser {
// 主键ID
id?: number;
// 自增ID
userId?: number;
// 姓名
realName?: string;
// 手机号
mobile?: string;
// 支付密码
payPassword?: string;
// 当前可提现佣金
money?: string;
// 已冻结佣金
freezeMoney?: string;
// 累积提现佣金
totalMoney?: string;
// 佣金比例
rate?: string;
// 单价
price?: string;
// 推荐人用户ID
refereeId?: number;
// 成员数量(一级)
firstNum?: number;
// 成员数量(二级)
secondNum?: number;
// 成员数量(三级)
thirdNum?: number;
// 专属二维码
qrcode?: string;
// 是否删除
isDelete?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string | Date;
// 修改时间
updateTime?: string | Date;
// 扩展字段,用于编辑表单
shopDealerUserId?: number;
shopDealerUserName?: string;
status?: number;
description?: string;
sortNumber?: number;
image?: string;
}
/**
* 分销商用户记录表搜索条件
*/
export interface ShopDealerUserParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerWithdraw, ShopDealerWithdrawParam } from './model';
/**
* 分页查询分销商提现明细表
*/
export async function pageShopDealerWithdraw(params: ShopDealerWithdrawParam) {
const res = await request.get<ApiResult<PageResult<ShopDealerWithdraw>>>(
'/shop/shop-dealer-withdraw/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分销商提现明细表列表
*/
export async function listShopDealerWithdraw(params?: ShopDealerWithdrawParam) {
const res = await request.get<ApiResult<ShopDealerWithdraw[]>>(
'/shop/shop-dealer-withdraw',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分销商提现明细表
*/
export async function addShopDealerWithdraw(data: ShopDealerWithdraw) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-dealer-withdraw',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分销商提现明细表
*/
export async function updateShopDealerWithdraw(data: ShopDealerWithdraw) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-dealer-withdraw',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分销商提现明细表
*/
export async function removeShopDealerWithdraw(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-withdraw/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除分销商提现明细表
*/
export async function removeBatchShopDealerWithdraw(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-dealer-withdraw/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询分销商提现明细表
*/
export async function getShopDealerWithdraw(id: number) {
const res = await request.get<ApiResult<ShopDealerWithdraw>>(
'/shop/shop-dealer-withdraw/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,63 +0,0 @@
import type { PageParam } from '@/api';
/**
* 分销商提现明细表
*/
export interface ShopDealerWithdraw {
// 主键ID
id?: number;
// 分销商用户ID
userId?: number;
// 真实姓名
realName?: string;
// 昵称
nickname?: string;
// 手机号码
phone?: string;
// 头像
avatar?: string;
// 提现金额
money?: string;
// 打款方式 (10微信 20支付宝 30银行卡)
payType?: number;
// 支付宝姓名
alipayName?: string;
// 支付宝账号
alipayAccount?: string;
// 微信姓名
wechatAccount?: string;
// 微信账号
wechatName?: string;
// 开户行名称
bankName?: string;
// 银行开户名
bankAccount?: string;
// 银行卡号
bankCard?: string;
// 申请状态 (10待审核 20审核通过 30驳回 40已打款)
applyStatus?: number;
// 审核时间
auditTime?: any;
// 驳回原因
rejectReason?: string;
// 来源客户端(APP、H5、小程序等)
platform?: string;
// 上传支付凭证
image?: string;
// 备注
description?: string;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 分销商提现明细表搜索条件
*/
export interface ShopDealerWithdrawParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopExpress, ShopExpressParam } from './model';
/**
* 分页查询物流公司
*/
export async function pageShopExpress(params: ShopExpressParam) {
const res = await request.get<ApiResult<PageResult<ShopExpress>>>(
'/shop/shop-express/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询物流公司列表
*/
export async function listShopExpress(params?: ShopExpressParam) {
const res = await request.get<ApiResult<ShopExpress[]>>(
'/shop/shop-express',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加物流公司
*/
export async function addShopExpress(data: ShopExpress) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-express',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改物流公司
*/
export async function updateShopExpress(data: ShopExpress) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-express',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除物流公司
*/
export async function removeShopExpress(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-express/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除物流公司
*/
export async function removeBatchShopExpress(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-express/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询物流公司
*/
export async function getShopExpress(id: number) {
const res = await request.get<ApiResult<ShopExpress>>(
'/shop/shop-express/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,35 +0,0 @@
import type { PageParam } from '@/api';
/**
* 物流公司
*/
export interface ShopExpress {
// 物流公司ID
expressId?: number;
// 物流公司名称
expressName?: string;
// 物流公司编码 (微信)
wxCode?: string;
// 物流公司编码 (快递100)
kuaidi100Code?: string;
// 物流公司编码 (快递鸟)
kdniaoCode?: string;
// 排序号
sortNumber?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 物流公司搜索条件
*/
export interface ShopExpressParam extends PageParam {
expressId?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopExpressTemplate, ShopExpressTemplateParam } from './model';
/**
* 分页查询运费模板
*/
export async function pageShopExpressTemplate(params: ShopExpressTemplateParam) {
const res = await request.get<ApiResult<PageResult<ShopExpressTemplate>>>(
'/shop/shop-express-template/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询运费模板列表
*/
export async function listShopExpressTemplate(params?: ShopExpressTemplateParam) {
const res = await request.get<ApiResult<ShopExpressTemplate[]>>(
'/shop/shop-express-template',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加运费模板
*/
export async function addShopExpressTemplate(data: ShopExpressTemplate) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-express-template',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改运费模板
*/
export async function updateShopExpressTemplate(data: ShopExpressTemplate) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-express-template',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除运费模板
*/
export async function removeShopExpressTemplate(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-express-template/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除运费模板
*/
export async function removeBatchShopExpressTemplate(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-express-template/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询运费模板
*/
export async function getShopExpressTemplate(id: number) {
const res = await request.get<ApiResult<ShopExpressTemplate>>(
'/shop/shop-express-template/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,43 +0,0 @@
import type { PageParam } from '@/api';
/**
* 运费模板
*/
export interface ShopExpressTemplate {
//
id?: number;
//
type?: string;
//
title?: string;
// 收件价格
firstAmount?: string;
// 续件价格
extraAmount?: string;
// 状态, 0已发布, 1待审核 2已驳回 3违规内容
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
// 排序
sortNumber?: number;
// 备注
description?: string;
// 首件数量/重量
firstNum?: string;
// 续件数量/重量
extraNum?: string;
}
/**
* 运费模板搜索条件
*/
export interface ShopExpressTemplateParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopExpressTemplateDetail, ShopExpressTemplateDetailParam } from './model';
/**
* 分页查询运费模板
*/
export async function pageShopExpressTemplateDetail(params: ShopExpressTemplateDetailParam) {
const res = await request.get<ApiResult<PageResult<ShopExpressTemplateDetail>>>(
'/shop/shop-express-template-detail/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询运费模板列表
*/
export async function listShopExpressTemplateDetail(params?: ShopExpressTemplateDetailParam) {
const res = await request.get<ApiResult<ShopExpressTemplateDetail[]>>(
'/shop/shop-express-template-detail',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加运费模板
*/
export async function addShopExpressTemplateDetail(data: ShopExpressTemplateDetail) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-express-template-detail',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改运费模板
*/
export async function updateShopExpressTemplateDetail(data: ShopExpressTemplateDetail) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-express-template-detail',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除运费模板
*/
export async function removeShopExpressTemplateDetail(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-express-template-detail/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除运费模板
*/
export async function removeBatchShopExpressTemplateDetail(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-express-template-detail/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询运费模板
*/
export async function getShopExpressTemplateDetail(id: number) {
const res = await request.get<ApiResult<ShopExpressTemplateDetail>>(
'/shop/shop-express-template-detail/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,45 +0,0 @@
import type { PageParam } from '@/api';
/**
* 运费模板
*/
export interface ShopExpressTemplateDetail {
//
id?: number;
//
templateId?: number;
// 0按件
type?: string;
//
provinceId?: number;
//
cityId?: number;
// 首件数量/重量
firstNum?: string;
// 收件价格
firstAmount?: string;
// 续件价格
extraAmount?: string;
// 续件数量/重量
extraNum?: string;
// 状态, 0已发布, 1待审核 2已驳回 3违规内容
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
//
sortNumber?: number;
}
/**
* 运费模板搜索条件
*/
export interface ShopExpressTemplateDetailParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,130 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { ShopGift, ShopGiftParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询礼品卡
*/
export async function pageShopGift(params: ShopGiftParam) {
const res = await request.get<ApiResult<PageResult<ShopGift>>>(
MODULES_API_URL + '/shop/shop-gift/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询礼品卡列表
*/
export async function listShopGift(params?: ShopGiftParam) {
const res = await request.get<ApiResult<ShopGift[]>>(
MODULES_API_URL + '/shop/shop-gift',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加礼品卡
*/
export async function addShopGift(data: ShopGift) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-gift',
data
);
if (res.data.code === 0) {
return res.data.message;
}
}
/**
* 生成礼品卡
*/
export async function makeShopGift(data: ShopGift) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-gift/make',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改礼品卡
*/
export async function updateShopGift(data: ShopGift) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-gift',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除礼品卡
*/
export async function removeShopGift(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-gift/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除礼品卡
*/
export async function removeBatchShopGift(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-gift/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询礼品卡
*/
export async function getShopGift(id: number) {
const res = await request.get<ApiResult<ShopGift>>(
MODULES_API_URL + '/shop/shop-gift/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
export async function exportShopGift(ids?: number[]) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-gift/export',
ids
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,61 +0,0 @@
import type { PageParam } from '@/api/index';
/**
* 礼品卡
*/
export interface ShopGift {
//
id?: number;
//
name?: string;
// 秘钥
code?: string;
// 商品ID
goodsId?: number;
// 商品名称
goodsName?: string;
// 面值
faceValue?: string;
// 领取时间
takeTime?: string;
// 核销时间
verificationTime?: string;
// 操作人ID
operatorUserId?: number;
// 操作人
operatorUserName?: string;
// 操作备注
operatorRemarks?: string;
// 使用地址
useLocation?: string;
// 是否展示
isShow?: boolean;
// 状态, 0上架 1待上架 2待审核 3审核不通过
status?: number;
// 备注
description?: string;
// 排序号
sortNumber?: number;
// 用户ID
userId?: number;
// 昵称
nickName?: string;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
num?: number;
}
/**
* 礼品卡搜索条件
*/
export interface ShopGiftParam extends PageParam {
id?: number;
code?: string;
keywords?: string;
}

View File

@@ -1,116 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopGoods, ShopGoodsParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商品
*/
export async function pageShopGoods(params: ShopGoodsParam) {
const res = await request.get<ApiResult<PageResult<ShopGoods>>>(
MODULES_API_URL + '/shop/shop-goods/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品列表
*/
export async function listShopGoods(params?: ShopGoodsParam) {
const res = await request.get<ApiResult<ShopGoods[]>>(
MODULES_API_URL + '/shop/shop-goods',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品
*/
export async function addShopGoods(data: ShopGoods) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品
*/
export async function updateShopGoods(data: ShopGoods) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品
*/
export async function removeShopGoods(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品
*/
export async function removeBatchShopGoods(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品
*/
export async function getShopGoods(id: number) {
const res = await request.get<ApiResult<ShopGoods>>(
MODULES_API_URL + '/shop/shop-goods/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
export async function getCount(params: ShopGoodsParam) {
const res = await request.get(MODULES_API_URL + '/shop/shop-goods/data', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,148 +0,0 @@
import type { PageParam } from '@/api';
import { ShopGoodsSpec } from '@/api/shop/shopGoodsSpec/model';
import { ShopGoodsSku } from '@/api/shop/shopGoodsSku/model';
import { ShopGoodsRoleCommission } from '@/api/shop/shopGoodsRoleCommission/model';
export interface GoodsCount {
totalNum: number;
totalNum2: number;
totalNum3: number;
totalNum4: number;
}
/**
* 商品记录表
*/
export interface ShopGoods {
// 自增ID
goodsId?: number;
// 类型 1实物商品 2虚拟商品
type?: number;
// 商品编码
code?: string;
// 商品名称
name?: string;
// 商品标题
goodsName?: string;
// 商品封面图
image?: string;
video?: string;
// 商品详情
content?: string;
canExpress?: number;
// 商品分类
category?: string;
// 商品分类ID
categoryId?: number;
parentName?: string;
categoryName?: string;
// 一级分类
categoryParent?: string;
// 二级分类
categoryChildren?: string;
// 商品规格 0单规格 1多规格
specs?: number;
commissionRole?: number;
// 货架
position?: string;
// 进货价
buyingPrice?: string;
// 商品价格
price?: string;
originPrice?: string;
// 销售价格
salePrice?: string;
chainStorePrice?: string;
chainStoreRate?: string;
memberStoreRate?: string;
memberMarketRate?: string;
memberStoreCommission?: string;
supplierCommission?: string;
coopCommission?: string;
memberStorePrice?: string;
memberMarketPrice?: string;
// 经销商价格
dealerPrice?: string;
// 有赠品
buyingGift?: boolean;
// 有赠品
priceGift?: boolean;
// 有赠品
dealerGift?: boolean;
buyingGiftNum?: number;
priceGiftNum?: number;
priceGiftName?: string;
dealerGiftNum?: number;
// 库存计算方式(10下单减库存 20付款减库存)
deductStockType?: number;
// 封面图
files?: string;
// 销量
sales?: number;
isNew?: number;
// 库存
stock?: number;
// 商品重量
goodsWeight?: number;
// 消费赚取积分
gainIntegral?: number;
// 推荐
recommend?: number;
// 商户ID
merchantId?: number;
// 商户名称
merchantName?: string;
supplierMerchantId?: number;
supplierName?: string;
// 状态0未上架1上架
isShow?: number;
// 状态, 0上架 1待上架 2待审核 3审核不通过
status?: number;
// 备注
description?: string;
// 排序号
sortNumber?: number;
// 用户ID
userId?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
// 显示规格名
specName?: string;
// 商品规格
goodsSpecs?: ShopGoodsSpec[];
goodsRoleCommission?: ShopGoodsRoleCommission[];
// 商品sku列表
goodsSkus?: ShopGoodsSku[];
// 单位名称
unitName?: string;
expressTemplateId?: number;
canUseDate?: string;
ensureTag?: string;
expiredDay?: number;
}
export interface BathSet {
price?: number;
salePrice?: number;
stock?: number;
skuNo?: string;
}
/**
* 商品记录表搜索条件
*/
export interface ShopGoodsParam extends PageParam {
parentId?: number;
categoryId?: number;
goodsId?: number;
status?: number;
goodsName?: string;
isShow?: number;
stock?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopGoodsCategory, ShopGoodsCategoryParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商品分类
*/
export async function pageShopGoodsCategory(params: ShopGoodsCategoryParam) {
const res = await request.get<ApiResult<PageResult<ShopGoodsCategory>>>(
MODULES_API_URL + '/shop/shop-goods-category/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品分类列表
*/
export async function listShopGoodsCategory(params?: ShopGoodsCategoryParam) {
const res = await request.get<ApiResult<ShopGoodsCategory[]>>(
MODULES_API_URL + '/shop/shop-goods-category',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品分类
*/
export async function addShopGoodsCategory(data: ShopGoodsCategory) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-category',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品分类
*/
export async function updateShopGoodsCategory(data: ShopGoodsCategory) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-category',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品分类
*/
export async function removeShopGoodsCategory(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-category/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品分类
*/
export async function removeBatchShopGoodsCategory(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-category/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品分类
*/
export async function getShopGoodsCategory(id: number) {
const res = await request.get<ApiResult<ShopGoodsCategory>>(
MODULES_API_URL + '/shop/shop-goods-category/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,64 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商品分类
*/
export interface ShopGoodsCategory {
// 商品分类ID
categoryId?: number;
// 分类标识
categoryCode?: string;
// 分类名称
title?: string;
// 类型 0商城分类 1外卖分类
type?: number;
// 分类图片
image?: string;
// 上级分类ID
parentId?: number;
// 路由/链接地址
path?: string;
// 组件路径
component?: string;
// 绑定的页面
pageId?: number;
// 用户ID
userId?: number;
// 商品数量
count?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 备注
description?: string;
// 是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)
hide?: number;
// 是否推荐
recommend?: number;
// 是否显示在首页
showIndex?: number;
// 商铺ID
merchantId?: number;
// 状态, 0正常, 1禁用
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
// 子菜单
children?: ShopGoodsCategory[];
key?: number;
value?: number;
label?: string;
}
/**
* 商品分类搜索条件
*/
export interface ShopGoodsCategoryParam extends PageParam {
categoryId?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { ShopGoodsCoupon, ShopGoodsCouponParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商品优惠券表
*/
export async function pageShopGoodsCoupon(params: ShopGoodsCouponParam) {
const res = await request.get<ApiResult<PageResult<ShopGoodsCoupon>>>(
MODULES_API_URL + '/shop/shop-goods-coupon/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品优惠券表列表
*/
export async function listShopGoodsCoupon(params?: ShopGoodsCouponParam) {
const res = await request.get<ApiResult<ShopGoodsCoupon[]>>(
MODULES_API_URL + '/shop/shop-goods-coupon',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品优惠券表
*/
export async function addShopGoodsCoupon(data: ShopGoodsCoupon) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品优惠券表
*/
export async function updateShopGoodsCoupon(data: ShopGoodsCoupon) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品优惠券表
*/
export async function removeShopGoodsCoupon(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品优惠券表
*/
export async function removeBatchShopGoodsCoupon(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品优惠券表
*/
export async function getShopGoodsCoupon(id: number) {
const res = await request.get<ApiResult<ShopGoodsCoupon>>(
MODULES_API_URL + '/shop/shop-goods-coupon/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,37 +0,0 @@
import type { PageParam } from '@/api/index';
/**
* 商品优惠券表
*/
export interface ShopGoodsCoupon {
//
id?: number;
// 商品id
goodsId?: number;
// 优惠劵id
issueCouponId?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1冻结
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
// 备注
description?: string;
}
/**
* 商品优惠券表搜索条件
*/
export interface ShopGoodsCouponParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import { MODULES_API_URL } from '@/config/setting';
import { ShopGoodsRoleCommission, ShopGoodsRoleCommissionParam } from '@/api/shop/shopGoodsRoleCommission/model';
/**
* 分页查询商品绑定角色的分润金额
*/
export async function pageShopGoodsRoleCommission(params: ShopGoodsRoleCommissionParam) {
const res = await request.get<ApiResult<PageResult<ShopGoodsRoleCommission>>>(
MODULES_API_URL + '/shop/shop-goods-role-commission/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品绑定角色的分润金额列表
*/
export async function listShopGoodsRoleCommission(params?: ShopGoodsRoleCommissionParam) {
const res = await request.get<ApiResult<ShopGoodsRoleCommission[]>>(
MODULES_API_URL + '/shop/shop-goods-role-commission',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品绑定角色的分润金额
*/
export async function addShopGoodsRoleCommission(data: ShopGoodsRoleCommission) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-role-commission',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品绑定角色的分润金额
*/
export async function updateShopGoodsRoleCommission(data: ShopGoodsRoleCommission) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-role-commission',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品绑定角色的分润金额
*/
export async function removeShopGoodsRoleCommission(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-role-commission/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品绑定角色的分润金额
*/
export async function removeBatchShopGoodsRoleCommission(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-role-commission/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品绑定角色的分润金额
*/
export async function getShopGoodsRoleCommission(id: number) {
const res = await request.get<ApiResult<ShopGoodsRoleCommission>>(
MODULES_API_URL + '/shop/shop-goods-role-commission/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,35 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商品绑定角色的分润金额
*/
export interface ShopGoodsRoleCommission {
//
id?: number;
//
roleId?: number;
//
goodsId?: number;
//
sku?: string;
//
amount?: string;
// 状态, 0正常, 1异常
status?: number;
// 备注
description?: string;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
//
sortNumber?: number;
}
/**
* 商品绑定角色的分润金额搜索条件
*/
export interface ShopGoodsRoleCommissionParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,118 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import { MODULES_API_URL } from '@/config/setting';
import { ShopGoodsSpec } from '@/api/shop/shopGoodsSpec/model';
import { ShopGoodsSku, ShopGoodsSkuParam } from '@/api/shop/shopGoodsSku/model';
export async function generateGoodsSku(data: ShopGoodsSpec) {
const res = await request.post<ApiResult<ShopGoodsSku[]>>(
MODULES_API_URL + '/shop/goods-sku/generateGoodsSku',
data
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 分页查询商品sku列表
*/
export async function pageShopGoodsSku(params: ShopGoodsSkuParam) {
const res = await request.get<ApiResult<PageResult<ShopGoodsSku>>>(
MODULES_API_URL + '/shop/shop-goods-sku/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品sku列表列表
*/
export async function listShopGoodsSku(params?: ShopGoodsSkuParam) {
const res = await request.get<ApiResult<ShopGoodsSku[]>>(
MODULES_API_URL + '/shop/shop-goods-sku',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品sku列表
*/
export async function addShopGoodsSku(data: ShopGoodsSku) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-sku',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品sku列表
*/
export async function updateShopGoodsSku(data: ShopGoodsSku) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-sku',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品sku列表
*/
export async function removeShopGoodsSku(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-sku/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品sku列表
*/
export async function removeBatchShopGoodsSku(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-sku/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品sku列表
*/
export async function getShopGoodsSku(id: number) {
const res = await request.get<ApiResult<ShopGoodsSku>>(
MODULES_API_URL + '/shop/shop-goods-sku/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,50 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商品sku列表
*/
export interface ShopGoodsSku {
// 主键ID
id?: number;
// 商品ID
goodsId?: number;
// 商品属性索引值 (attr_value|attr_value[|....])
sku?: string;
// 商品图片
image?: string;
// 商品价格
price?: string;
// 市场价格
salePrice?: string;
// 成本价
cost?: string;
// 库存
stock?: number;
// sku编码
skuNo?: string;
// 商品条码
barCode?: string;
// 重量
weight?: string;
// 体积
volume?: string;
// 唯一值
uuid?: string;
// 状态, 0正常, 1异常
status?: number;
// 备注
description?: string;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
images?: string;
}
/**
* 商品sku列表搜索条件
*/
export interface ShopGoodsSkuParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopGoodsSpec, ShopGoodsSpecParam } from './model';
/**
* 分页查询商品多规格
*/
export async function pageShopGoodsSpec(params: ShopGoodsSpecParam) {
const res = await request.get<ApiResult<PageResult<ShopGoodsSpec>>>(
'/shop/shop-goods-spec/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品多规格列表
*/
export async function listShopGoodsSpec(params?: ShopGoodsSpecParam) {
const res = await request.get<ApiResult<ShopGoodsSpec[]>>(
'/shop/shop-goods-spec',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品多规格
*/
export async function addShopGoodsSpec(data: ShopGoodsSpec) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-goods-spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品多规格
*/
export async function updateShopGoodsSpec(data: ShopGoodsSpec) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-goods-spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品多规格
*/
export async function removeShopGoodsSpec(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-goods-spec/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品多规格
*/
export async function removeBatchShopGoodsSpec(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-goods-spec/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品多规格
*/
export async function getShopGoodsSpec(id: number) {
const res = await request.get<ApiResult<ShopGoodsSpec>>(
'/shop/shop-goods-spec/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,29 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商品多规格
*/
export interface ShopGoodsSpec {
// 主键
id?: number;
// 商品ID
goodsId?: number;
// 规格ID
specId?: number;
// 规格名称
specName?: string;
// 规格值
specValue?: string;
// 活动类型 0=商品1=秒杀2=砍价3=拼团
type?: string;
// 租户id
tenantId?: number;
}
/**
* 商品多规格搜索条件
*/
export interface ShopGoodsSpecParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { ShopMerchant, ShopMerchantParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商户
*/
export async function pageShopMerchant(params: ShopMerchantParam) {
const res = await request.get<ApiResult<PageResult<ShopMerchant>>>(
MODULES_API_URL + '/shop/shop-merchant/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商户列表
*/
export async function listShopMerchant(params?: ShopMerchantParam) {
const res = await request.get<ApiResult<ShopMerchant[]>>(
MODULES_API_URL + '/shop/shop-merchant',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商户
*/
export async function addShopMerchant(data: ShopMerchant) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商户
*/
export async function updateShopMerchant(data: ShopMerchant) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商户
*/
export async function removeShopMerchant(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商户
*/
export async function removeBatchShopMerchant(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商户
*/
export async function getShopMerchant(id: number) {
const res = await request.get<ApiResult<ShopMerchant>>(
MODULES_API_URL + '/shop/shop-merchant/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,95 +0,0 @@
import type { PageParam } from '@/api/index';
/**
* 商户
*/
export interface ShopMerchant {
// ID
merchantId?: number;
// 商户名称
merchantName?: string;
// 商户编号
merchantCode?: string;
// 商户类型
type?: number;
// 商户图标
image?: string;
// 商户手机号
phone?: string;
// 商户姓名
realName?: string;
// 店铺类型
shopType?: string;
// 项目分类
itemType?: string;
// 商户分类
category?: string;
// 商户经营分类
merchantCategoryId?: number;
// 商户分类
merchantCategoryTitle?: string;
// 经纬度
lngAndLat?: string;
//
lng?: string;
//
lat?: string;
// 所在省份
province?: string;
// 所在城市
city?: string;
// 所在辖区
region?: string;
// 详细地址
address?: string;
// 手续费
commission?: string;
// 关键字
keywords?: string;
// 资质图片
files?: string;
// 营业时间
businessTime?: string;
// 文章内容
content?: string;
// 每小时价格
price?: string;
// 是否自营
ownStore?: number;
// 是否可以快递
canExpress?: string;
// 是否推荐
recommend?: number;
// 是否营业
isOn?: number;
//
startTime?: string;
//
endTime?: string;
// 是否需要审核
goodsReview?: number;
// 管理入口
adminUrl?: string;
// 备注
description?: string;
// 所有人
userId?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 状态
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 商户搜索条件
*/
export interface ShopMerchantParam extends PageParam {
merchantId?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopMerchantAccount, ShopMerchantAccountParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商户账号
*/
export async function pageShopMerchantAccount(params: ShopMerchantAccountParam) {
const res = await request.get<ApiResult<PageResult<ShopMerchantAccount>>>(
MODULES_API_URL + '/shop/shop-merchant-account/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商户账号列表
*/
export async function listShopMerchantAccount(params?: ShopMerchantAccountParam) {
const res = await request.get<ApiResult<ShopMerchantAccount[]>>(
MODULES_API_URL + '/shop/shop-merchant-account',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商户账号
*/
export async function addShopMerchantAccount(data: ShopMerchantAccount) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-account',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商户账号
*/
export async function updateShopMerchantAccount(data: ShopMerchantAccount) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-account',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商户账号
*/
export async function removeShopMerchantAccount(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-account/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商户账号
*/
export async function removeBatchShopMerchantAccount(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-account/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商户账号
*/
export async function getShopMerchantAccount(id: number) {
const res = await request.get<ApiResult<ShopMerchantAccount>>(
MODULES_API_URL + '/shop/shop-merchant-account/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,39 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商户账号
*/
export interface ShopMerchantAccount {
// ID
id?: number;
// 商户手机号
phone?: string;
// 真实姓名
realName?: string;
// 商户ID
merchantId?: number;
// 角色ID
roleId?: number;
// 角色名称
roleName?: string;
// 用户ID
userId?: number;
// 备注
description?: string;
// 状态
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 商户账号搜索条件
*/
export interface ShopMerchantAccountParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,131 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopMerchantApply, ShopMerchantApplyParam } from './model';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询商户入驻申请
*/
export async function pageShopMerchantApply(params: ShopMerchantApplyParam) {
const res = await request.get<ApiResult<PageResult<ShopMerchantApply>>>(
SERVER_API_URL + '/shop/shop-merchant-apply/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商户入驻申请列表
*/
export async function listShopMerchantApply(params?: ShopMerchantApplyParam) {
const res = await request.get<ApiResult<ShopMerchantApply[]>>(
SERVER_API_URL + '/shop/shop-merchant-apply',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商户入驻申请
*/
export async function addShopMerchantApply(data: ShopMerchantApply) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-merchant-apply',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商户入驻申请
*/
export async function updateShopMerchantApply(data: ShopMerchantApply) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-apply',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
// 审核通过
export async function checkShopMerchantApply(data: ShopMerchantApply) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-apply/check',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
// 根据入驻申请创建商户
export async function createMerchantFromApply(applyId: number) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-apply/create-merchant/' + applyId
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商户入驻申请
*/
export async function removeShopMerchantApply(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-apply/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商户入驻申请
*/
export async function removeBatchShopMerchantApply(
data: (number | undefined)[]
) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-apply/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商户入驻申请
*/
export async function getShopMerchantApply(id: number) {
const res = await request.get<ApiResult<ShopMerchantApply>>(
SERVER_API_URL + '/shop/shop-merchant-apply/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,74 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商户入驻申请
*/
export interface ShopMerchantApply {
// ID
applyId?: number;
// 类型
type?: number;
// 证件类型
idType?: string;
// 主体名称
merchantName?: string;
// 证件号码
merchantCode?: string;
// 商户图标
image?: string;
// 商户手机号
phone?: string;
// 商户姓名
realName?: string;
// 身份证号码
idCard?: string;
// 店铺类型
shopType?: string;
// 商户分类
category?: string;
// 手续费
commission?: string;
// 关键字
keywords?: string;
// 营业执照
yyzz?: string;
// 身份证正面
sfz1?: string;
// 身份证反面
sfz2?: string;
// 资质图片
files?: string;
// 所有人
userId?: number;
// 是否自营
ownStore?: number;
// 是否推荐
recommend?: number;
// 是否需要审核
goodsReview?: number;
// 工作负责人
name2?: string;
// 驳回原因
reason?: string;
// 备注
description?: string;
// 状态
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 商户入驻申请搜索条件
*/
export interface ShopMerchantApplyParam extends PageParam {
applyId?: number;
userId?: number;
shopType?: string;
phone?: string;
keywords?: string;
}

View File

@@ -1,108 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopMerchantCount, ShopMerchantCountParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询门店销售统计表
*/
export async function pageShopMerchantCount(params: ShopMerchantCountParam) {
const res = await request.get<ApiResult<PageResult<ShopMerchantCount>>>(
MODULES_API_URL + '/shop/shop-merchant-count/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询门店销售统计表列表
*/
export async function listShopMerchantCount(params?: ShopMerchantCountParam) {
const res = await request.get<ApiResult<ShopMerchantCount[]>>(
MODULES_API_URL + '/shop/shop-merchant-count',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加门店销售统计表
*/
export async function addShopMerchantCount(data: ShopMerchantCount) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-count',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改门店销售统计表
*/
export async function updateShopMerchantCount(data: ShopMerchantCount) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-count',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除门店销售统计表
*/
export async function removeShopMerchantCount(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-count/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除门店销售统计表
*/
export async function removeBatchShopMerchantCount(
data: (number | undefined)[]
) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-merchant-count/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询门店销售统计表
*/
export async function getShopMerchantCount(id: number) {
const res = await request.get<ApiResult<ShopMerchantCount>>(
MODULES_API_URL + '/shop/shop-merchant-count/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,29 +0,0 @@
import type { PageParam } from '@/api';
/**
* 门店销售统计表
*/
export interface ShopMerchantCount {
// ID
id?: number;
// 店铺名称
name?: string;
// 店铺说明
description?: string;
// 状态
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 门店销售统计表搜索条件
*/
export interface ShopMerchantCountParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,108 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopMerchantType, ShopMerchantTypeParam } from './model';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询商户类型
*/
export async function pageShopMerchantType(params: ShopMerchantTypeParam) {
const res = await request.get<ApiResult<PageResult<ShopMerchantType>>>(
SERVER_API_URL + '/shop/shop-merchant-type/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商户类型列表
*/
export async function listShopMerchantType(params?: ShopMerchantTypeParam) {
const res = await request.get<ApiResult<ShopMerchantType[]>>(
SERVER_API_URL + '/shop/shop-merchant-type',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商户类型
*/
export async function addShopMerchantType(data: ShopMerchantType) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-type',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商户类型
*/
export async function updateShopMerchantType(data: ShopMerchantType) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-type',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商户类型
*/
export async function removeShopMerchantType(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-type/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商户类型
*/
export async function removeBatchShopMerchantType(
data: (number | undefined)[]
) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/shop/shop-merchant-type/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商户类型
*/
export async function getShopMerchantType(id: number) {
const res = await request.get<ApiResult<ShopMerchantType>>(
SERVER_API_URL + '/shop/shop-merchant-type/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,30 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商户类型
*/
export interface ShopMerchantType {
// ID
id?: number;
// 店铺类型
name?: string;
// 店铺入驻条件
description?: string;
// 状态
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
value?: string;
}
/**
* 商户类型搜索条件
*/
export interface ShopMerchantTypeParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,137 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopOrder, ShopOrderParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询订单
*/
export async function pageShopOrder(params: ShopOrderParam) {
const res = await request.get<ApiResult<PageResult<ShopOrder>>>(
MODULES_API_URL + '/shop/shop-order/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询订单列表
*/
export async function listShopOrder(params?: ShopOrderParam) {
const res = await request.get<ApiResult<ShopOrder[]>>(
MODULES_API_URL + '/shop/shop-order',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加订单
*/
export async function addShopOrder(data: ShopOrder) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改订单
*/
export async function updateShopOrder(data: ShopOrder) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除订单
*/
export async function removeShopOrder(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除订单
*/
export async function removeBatchShopOrder(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询订单
*/
export async function getShopOrder(id: number) {
const res = await request.get<ApiResult<ShopOrder>>(
MODULES_API_URL + '/shop/shop-order/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改订单
*/
export async function repairOrder(data: ShopOrder) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order/repair',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 统计订单总金额(只统计有效订单)
*/
export async function shopOrderTotal(params?: ShopOrderParam) {
const res = await request.get<ApiResult<ShopOrder[]>>(
MODULES_API_URL + '/shop/shop-order/total',
{
params
}
);
if (res.data.code === 0) {
// 即使没有数据也返回空数组,而不是抛出错误
return res.data.data || [];
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,183 +0,0 @@
import type { PageParam } from '@/api';
import {OrderGoods} from "@/api/system/orderGoods/model";
/**
* 订单
*/
export interface ShopOrder {
// 订单号
orderId?: number;
// 订单编号
orderNo?: string;
// 订单类型0商城订单 1预定订单/外卖 2会员卡
type?: number;
// 快递/自提
deliveryType?: number;
// 下单渠道0小程序预定 1俱乐部训练场 3活动订场
channel?: number;
// 微信支付订单号
transactionId?: string;
// 微信退款订单号
refundOrder?: string;
// 商户ID
merchantId?: number;
// 商户名称
merchantName?: string;
// 商户编号
merchantCode?: string;
// 使用的优惠券id
couponId?: number;
// 使用的会员卡id
cardId?: string;
// 关联管理员id
adminId?: number;
// 核销管理员id
confirmId?: number;
// IC卡号
icCard?: string;
// 头像
avatar?: string;
// 真实姓名
realName?: string;
// 手机号码
phone?: string;
// 手机号码(脱敏)
mobile?: string;
// 收货地址
address?: string;
//
addressLat?: string;
//
addressLng?: string;
// 自提店铺id
selfTakeMerchantId?: number;
// 自提店铺
selfTakeMerchantName?: string;
// 配送开始时间
sendStartTime?: string;
// 配送结束时间
sendEndTime?: string;
// 发货店铺id
expressMerchantId?: number;
// 发货店铺
expressMerchantName?: string;
// 订单总额
totalPrice?: string;
// 减少的金额使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格
reducePrice?: string;
// 实际付款
payPrice?: string;
// 用于统计
price?: string;
// 价钱,用于积分赠送
money?: string;
// 退款金额
refundMoney?: string;
// 教练价格
coachPrice?: string;
// 购买数量
totalNum?: number;
// 教练id
coachId?: number;
// 支付的用户id
payUserId?: number;
// 0余额支付, 1微信支付102微信Native2会员卡支付3支付宝4现金5POS机6VIP月卡7VIP年卡8VIP次卡9IC月卡10IC年卡11IC次卡12免费13VIP充值卡14IC充值卡15积分支付16VIP季卡17IC季卡18代付
payType?: number;
// 代付支付方式,0余额支付, 1微信支付102微信Native2会员卡支付3支付宝4现金5POS机6VIP月卡7VIP年卡8VIP次卡9IC月卡10IC年卡11IC次卡12免费13VIP充值卡14IC充值卡15积分支付16VIP季卡17IC季卡18代付
friendPayType?: number;
// 0未付款1已付款
payStatus?: number;
// 0未使用1已完成2已取消3取消中4退款申请中5退款被拒绝6退款成功7客户端申请退款
orderStatus?: number;
// 发货状态(10未发货 20已发货 30部分发货)
deliveryStatus?: number;
// 发货时间
deliveryTime?: string;
// 发货备注/无需发货备注
deliveryNote?: string;
// 快递公司id
expressId?: number;
// 快递公司名称
expressName?: string;
// 发货人
sendName?: string;
// 发货人联系方式
sendPhone?: string;
// 发货地址
sendAddress?: string;
// 优惠类型0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡
couponType?: number;
// 优惠说明
couponDesc?: string;
// 二维码地址,保存订单号,支付成功后才生成
qrcode?: string;
// vip月卡年卡、ic月卡年卡回退次数
returnNum?: number;
// vip充值回退金额
returnMoney?: string;
// 预约详情开始时间数组
startTime?: string;
// 是否已开具发票0未开发票1已开发票2不能开具发票
isInvoice?: number;
// 发票流水号
invoiceNo?: string;
// 支付时间
payTime?: string;
// 退款时间
refundTime?: string;
// 申请退款时间
refundApplyTime?: string;
// 过期时间
expirationTime?: string;
// 对账情况0=未对账1=已对账3=已对账金额对不上4=未查询到该订单
checkBill?: number;
// 订单是否已结算(0未结算 1已结算)
isSettled?: number;
// 系统版本号 0当前版本 value=其他版本
version?: number;
// 买家备注
buyerRemarks: undefined,
// 商家备注
merchantRemarks: undefined,
// 用户id
userId?: number;
// 备注
description?: string;
// 排序号
sortNumber?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 修改时间
updateTime?: string;
// 创建时间
createTime?: string;
// 自提码
selfTakeCode?: string;
// 是否已收到赠品
hasTakeGift?: string;
// 发货信息
shopOrderDelivery?: any;
// 订单商品
orderGoods?: OrderGoods[];
}
/**
* 订单搜索条件
*/
export interface ShopOrderParam extends PageParam {
orderId?: number;
orderNo?: string;
type?: number;
phone?: string;
userId?: number;
payUserId?: number;
nickname?: string;
payStatus?: number;
orderStatus?: number;
payType?: number;
isInvoice?: boolean;
statusFilter?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopOrderGoods, ShopOrderGoodsParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商品信息
*/
export async function pageShopOrderGoods(params: ShopOrderGoodsParam) {
const res = await request.get<ApiResult<PageResult<ShopOrderGoods>>>(
MODULES_API_URL + '/shop/shop-order-goods/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品信息列表
*/
export async function listShopOrderGoods(params?: ShopOrderGoodsParam) {
const res = await request.get<ApiResult<ShopOrderGoods[]>>(
MODULES_API_URL + '/shop/shop-order-goods',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品信息
*/
export async function addShopOrderGoods(data: ShopOrderGoods) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order-goods',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品信息
*/
export async function updateShopOrderGoods(data: ShopOrderGoods) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order-goods',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品信息
*/
export async function removeShopOrderGoods(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order-goods/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品信息
*/
export async function removeBatchShopOrderGoods(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-order-goods/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品信息
*/
export async function getShopOrderGoods(id: number) {
const res = await request.get<ApiResult<ShopOrderGoods>>(
MODULES_API_URL + '/shop/shop-order-goods/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,71 +0,0 @@
import type { PageParam } from '@/api';
/**
* 商品信息
*/
export interface ShopOrderGoods {
// 自增ID
id?: number;
// 关联订单表id
orderId?: number;
// 订单标识
orderCode?: string;
// 关联商户ID
merchantId?: number;
// 商户名称
merchantName?: string;
// 商品封面图
image?: string;
// 关联商品id
goodsId?: number;
// 商品名称
goodsName?: string;
// 商品规格
spec?: string;
//
skuId?: number;
// 单价
price?: string;
// 购买数量
totalNum?: number;
// 0 未付款 1已付款2无需付款或占用状态
payStatus?: number;
// 0未使用1已完成2已取消3取消中4退款申请中5退款被拒绝6退款成功7客户端申请退款
orderStatus?: number;
// 是否免费0收费、1免费
isFree?: string;
// 系统版本 0当前版本 其他版本
version?: number;
// 预约时间段
timePeriod?: string;
// 预定日期
dateTime?: string;
// 开场时间
startTime?: string;
// 结束时间
endTime?: string;
// 毫秒时间戳
timeFlag?: string;
// 过期时间
expirationTime?: string;
// 备注
description?: string;
// 用户id
userId?: number;
// 租户id
tenantId?: number;
// 更新时间
updateTime?: string;
// 创建时间
createTime?: string;
}
/**
* 商品信息搜索条件
*/
export interface ShopOrderGoodsParam extends PageParam {
id?: number;
orderId?: number;
orderNo?: string;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import { MODULES_API_URL } from '@/config/setting';
import { ShopSpec, ShopSpecParam } from '@/api/shop/shopSpec/model';
/**
* 分页查询规格
*/
export async function pageShopSpec(params: ShopSpecParam) {
const res = await request.get<ApiResult<PageResult<ShopSpec>>>(
MODULES_API_URL + '/shop/shop-spec/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询规格列表
*/
export async function listShopSpec(params?: ShopSpecParam) {
const res = await request.get<ApiResult<ShopSpec[]>>(
MODULES_API_URL + '/shop/shop-spec',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加规格
*/
export async function addShopSpec(data: ShopSpec) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改规格
*/
export async function updateShopSpec(data: ShopSpec) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除规格
*/
export async function removeShopSpec(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-spec/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除规格
*/
export async function removeBatchShopSpec(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-spec/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询规格
*/
export async function getShopSpec(id: number) {
const res = await request.get<ApiResult<ShopSpec>>(
MODULES_API_URL + '/shop/shop-spec/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,38 +0,0 @@
import type { PageParam } from '@/api';
/**
* 规格
*/
export interface ShopSpec {
// 规格ID
specId?: number;
// 规格名称
specName?: string;
// 规格值
specValue?: string;
// 商户ID
merchantId?: number;
// 创建用户
userId?: number;
// 更新者
updater?: number;
// 备注
description?: string;
// 状态, 0正常, 1待修,2异常已修3异常未修
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
value?: string;
}
/**
* 规格搜索条件
*/
export interface ShopSpecParam extends PageParam {
specId?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopSpecValue, ShopSpecValueParam } from './model';
/**
* 分页查询规格值
*/
export async function pageShopSpecValue(params: ShopSpecValueParam) {
const res = await request.get<ApiResult<PageResult<ShopSpecValue>>>(
'/shop/shop-spec-value/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询规格值列表
*/
export async function listShopSpecValue(params?: ShopSpecValueParam) {
const res = await request.get<ApiResult<ShopSpecValue[]>>(
'/shop/shop-spec-value',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加规格值
*/
export async function addShopSpecValue(data: ShopSpecValue) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改规格值
*/
export async function updateShopSpecValue(data: ShopSpecValue) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除规格值
*/
export async function removeShopSpecValue(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-spec-value/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除规格值
*/
export async function removeBatchShopSpecValue(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-spec-value/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询规格值
*/
export async function getShopSpecValue(id: number) {
const res = await request.get<ApiResult<ShopSpecValue>>(
'/shop/shop-spec-value/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,29 +0,0 @@
import type { PageParam } from '@/api';
/**
* 规格值
*/
export interface ShopSpecValue {
// 规格值ID
specValueId?: number;
// 规格组ID
specId?: number;
// 规格值
specValue?: string;
// 备注
description?: string;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 规格值搜索条件
*/
export interface ShopSpecValueParam extends PageParam {
specValueId?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopUser, ShopUserParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询用户记录表
*/
export async function pageShopUser(params: ShopUserParam) {
const res = await request.get<ApiResult<PageResult<ShopUser>>>(
MODULES_API_URL + '/shop/shop-user/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询用户记录表列表
*/
export async function listShopUser(params?: ShopUserParam) {
const res = await request.get<ApiResult<ShopUser[]>>(
MODULES_API_URL + '/shop/shop-user',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加用户记录表
*/
export async function addShopUser(data: ShopUser) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改用户记录表
*/
export async function updateShopUser(data: ShopUser) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除用户记录表
*/
export async function removeShopUser(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除用户记录表
*/
export async function removeBatchShopUser(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询用户记录表
*/
export async function getShopUser(id: number) {
const res = await request.get<ApiResult<ShopUser>>(
MODULES_API_URL + '/shop/shop-user/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,163 +0,0 @@
import type { PageParam } from '@/api';
/**
* 用户记录表
*/
export interface ShopUser {
// 用户id
userId?: number;
// 用户类型 0个人用户 1企业用户 2其他
type?: number;
// 账号
username?: string;
// 密码
password?: string;
// 昵称
nickname?: string;
// 手机号
phone?: string;
// 性别 1男 2女
sex?: number;
// 职务
position?: string;
// 注册来源客户端 (APP、H5、MP-WEIXIN等)
platform?: string;
// 邮箱
email?: string;
// 邮箱是否验证, 0否, 1是
emailVerified?: number;
// 别名
alias?: string;
// 真实姓名
realName?: string;
// 证件号码
idCard?: string;
// 出生日期
birthday?: string;
// 所在国家
country?: string;
// 所在省份
province?: string;
// 所在城市
city?: string;
// 所在辖区
region?: string;
// 街道地址
address?: string;
// 经度
longitude?: string;
// 纬度
latitude?: string;
// 用户可用余额
balance?: string;
// 已提现金额
cashedMoney?: string;
// 用户可用积分
points?: number;
// 用户总支付的金额
payMoney?: string;
// 实际消费的金额(不含退款)
expendMoney?: string;
// 密码
payPassword?: string;
// 会员等级ID
gradeId?: number;
// 行业分类
category?: string;
// 个人简介
introduction?: string;
// 机构id
organizationId?: number;
// 会员分组ID
groupId?: number;
// 头像
avatar?: string;
// 背景图
bgImage?: string;
// 用户编码
userCode?: string;
// 是否已实名认证
certification?: number;
// 年龄
age?: number;
// 是否线下会员
offline?: string;
// 关注数
followers?: number;
// 粉丝数
fans?: number;
// 点赞数
likes?: number;
// 评论数
commentNumbers?: number;
// 是否推荐
recommend?: number;
// 微信openid
openid?: string;
// 微信公众号openid
officeOpenid?: string;
// 微信unionID
unionid?: string;
// 客户端ID
clientId?: string;
// 不允许办卡
notAllowVip?: string;
// 是否管理员
isAdmin?: string;
// 是否企业管理员
isOrganizationAdmin?: string;
// 累计登录次数
loginNum?: number;
// 企业ID
companyId?: number;
// 可管理的场馆
merchants?: string;
// 商户ID
merchantId?: number;
// 商户名称
merchantName?: string;
// 商户头像
merchantAvatar?: string;
// 第三方系统的用户ID
uid?: number;
// 专家角色
expertType?: string;
// 过期时间
expireTime?: number;
// 最后结算时间
settlementTime?: string;
// 资质
aptitude?: string;
// 行业类型(父级)
industryParent?: string;
// 行业类型(子级)
industryChild?: string;
// 头衔
title?: string;
// 安装的产品ID
templateId?: number;
// 插件安装状态(仅对超超管判断) 0未安装 1已安装
installed?: number;
// 特长
speciality?: string;
// 备注
description?: string;
// 状态, 0在线, 1离线
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 用户记录表搜索条件
*/
export interface ShopUserParam extends PageParam {
userId?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopUserAddress, ShopUserAddressParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询收货地址
*/
export async function pageShopUserAddress(params: ShopUserAddressParam) {
const res = await request.get<ApiResult<PageResult<ShopUserAddress>>>(
MODULES_API_URL + '/shop/shop-user-address/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询收货地址列表
*/
export async function listShopUserAddress(params?: ShopUserAddressParam) {
const res = await request.get<ApiResult<ShopUserAddress[]>>(
MODULES_API_URL + '/shop/shop-user-address',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加收货地址
*/
export async function addShopUserAddress(data: ShopUserAddress) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user-address',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改收货地址
*/
export async function updateShopUserAddress(data: ShopUserAddress) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user-address',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除收货地址
*/
export async function removeShopUserAddress(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user-address/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除收货地址
*/
export async function removeBatchShopUserAddress(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-user-address/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询收货地址
*/
export async function getShopUserAddress(id: number) {
const res = await request.get<ApiResult<ShopUserAddress>>(
MODULES_API_URL + '/shop/shop-user-address/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,49 +0,0 @@
import type { PageParam } from '@/api';
/**
* 收货地址
*/
export interface ShopUserAddress {
// 主键ID
id?: number;
// 姓名
name?: string;
// 手机号码
phone?: string;
// 所在国家
country?: string;
// 所在省份
province?: string;
// 所在城市
city?: string;
// 所在辖区
region?: string;
// 收货地址
address?: string;
// 收货地址
fullAddress?: string;
//
lat?: string;
//
lng?: string;
// 1先生 2女士
gender?: number;
// 家、公司、学校
type?: string;
// 默认收货地址
isDefault?: string;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
}
/**
* 收货地址搜索条件
*/
export interface ShopUserAddressParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopUserCoupon, ShopUserCouponParam } from './model';
/**
* 分页查询用户优惠券
*/
export async function pageShopUserCoupon(params: ShopUserCouponParam) {
const res = await request.get<ApiResult<PageResult<ShopUserCoupon>>>(
'/shop/shop-user-coupon/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询用户优惠券列表
*/
export async function listShopUserCoupon(params?: ShopUserCouponParam) {
const res = await request.get<ApiResult<ShopUserCoupon[]>>(
'/shop/shop-user-coupon',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加用户优惠券
*/
export async function addShopUserCoupon(data: ShopUserCoupon) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-user-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改用户优惠券
*/
export async function updateShopUserCoupon(data: ShopUserCoupon) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-user-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除用户优惠券
*/
export async function removeShopUserCoupon(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-user-coupon/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除用户优惠券
*/
export async function removeBatchShopUserCoupon(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-user-coupon/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询用户优惠券
*/
export async function getShopUserCoupon(id: number) {
const res = await request.get<ApiResult<ShopUserCoupon>>(
'/shop/shop-user-coupon/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,61 +0,0 @@
import type { PageParam } from '@/api';
/**
* 用户优惠券
*/
export interface ShopUserCoupon {
// id
id?: string;
// 优惠券模板ID
couponId?: number;
// 用户ID
userId?: number;
// 优惠券名称
name?: string;
// 优惠券描述
description?: string;
// 优惠券类型(10满减券 20折扣券 30免费劵)
type?: number;
// 满减券-减免金额
reducePrice?: string;
// 折扣券-折扣率(0-100)
discount?: number;
// 最低消费金额
minPrice?: string;
// 适用范围(10全部商品 20指定商品 30指定分类)
applyRange?: number;
// 适用范围配置(json格式)
applyRangeConfig?: string;
// 有效期开始时间
startTime?: string;
// 有效期结束时间
endTime?: string;
// 使用状态(0未使用 1已使用 2已过期)
status?: number;
// 使用时间
useTime?: string;
// 使用订单ID
orderId?: string;
// 使用订单号
orderNo?: string;
// 获取方式(10主动领取 20系统发放 30活动赠送)
obtainType?: number;
// 获取来源描述
obtainSource?: string;
// 是否删除, 0否, 1是
deleted?: string;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 用户优惠券搜索条件
*/
export interface ShopUserCouponParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,105 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopUserReferee, ShopUserRefereeParam } from './model';
/**
* 分页查询用户推荐关系表
*/
export async function pageShopUserReferee(params: ShopUserRefereeParam) {
const res = await request.get<ApiResult<PageResult<ShopUserReferee>>>(
'/shop/shop-user-referee/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询用户推荐关系表列表
*/
export async function listShopUserReferee(params?: ShopUserRefereeParam) {
const res = await request.get<ApiResult<ShopUserReferee[]>>(
'/shop/shop-user-referee',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加用户推荐关系表
*/
export async function addShopUserReferee(data: ShopUserReferee) {
const res = await request.post<ApiResult<unknown>>(
'/shop/shop-user-referee',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改用户推荐关系表
*/
export async function updateShopUserReferee(data: ShopUserReferee) {
const res = await request.put<ApiResult<unknown>>(
'/shop/shop-user-referee',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除用户推荐关系表
*/
export async function removeShopUserReferee(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-user-referee/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除用户推荐关系表
*/
export async function removeBatchShopUserReferee(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
'/shop/shop-user-referee/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询用户推荐关系表
*/
export async function getShopUserReferee(id: number) {
const res = await request.get<ApiResult<ShopUserReferee>>(
'/shop/shop-user-referee/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,33 +0,0 @@
import type { PageParam } from '@/api';
/**
* 用户推荐关系表
*/
export interface ShopUserReferee {
// 主键ID
id?: number;
// 推荐人ID
dealerId?: number;
// 用户id(被推荐人)
userId?: number;
// 推荐关系层级(1,2,3)
level?: number;
// 备注
description?: string;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 用户推荐关系表搜索条件
*/
export interface ShopUserRefereeParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Spec, SpecParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询规格
*/
export async function pageSpec(params: SpecParam) {
const res = await request.get<ApiResult<PageResult<Spec>>>(
MODULES_API_URL + '/shop/spec/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询规格列表
*/
export async function listSpec(params?: SpecParam) {
const res = await request.get<ApiResult<Spec[]>>(
MODULES_API_URL + '/shop/spec',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加规格
*/
export async function addSpec(data: Spec) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改规格
*/
export async function updateSpec(data: Spec) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除规格
*/
export async function removeSpec(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除规格
*/
export async function removeBatchSpec(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询规格
*/
export async function getSpec(id: number) {
const res = await request.get<ApiResult<Spec>>(
MODULES_API_URL + '/shop/spec/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,37 +0,0 @@
import type { PageParam } from '@/api';
/**
* 规格
*/
export interface Spec {
// 规格ID
specId?: number;
// 规格名称
specName?: string;
// 规格值
specValue?: string;
// 创建用户
userId?: number;
// 更新者
updater?: number;
// 商户ID
merchantId?: number;
// 备注
description?: string;
// 状态, 0正常, 1待修,2异常已修3异常未修
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 规格搜索条件
*/
export interface SpecParam extends PageParam {
specId?: number;
keywords?: string;
}

View File

@@ -1,106 +0,0 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { SpecValue, SpecValueParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询规格值
*/
export async function pageSpecValue(params: SpecValueParam) {
const res = await request.get<ApiResult<PageResult<SpecValue>>>(
MODULES_API_URL + '/shop/spec-value/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询规格值列表
*/
export async function listSpecValue(params?: SpecValueParam) {
const res = await request.get<ApiResult<SpecValue[]>>(
MODULES_API_URL + '/shop/spec-value',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加规格值
*/
export async function addSpecValue(data: SpecValue) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改规格值
*/
export async function updateSpecValue(data: SpecValue) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除规格值
*/
export async function removeSpecValue(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除规格值
*/
export async function removeBatchSpecValue(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询规格值
*/
export async function getSpecValue(id: number) {
const res = await request.get<ApiResult<SpecValue>>(
MODULES_API_URL + '/shop/spec-value/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,33 +0,0 @@
import type { PageParam } from '@/api';
/**
* 规格值
*/
export interface SpecValue {
// 规格值ID
specValueId?: number;
// 规格值
specValue?: string;
// 规格组ID
specId?: number;
// 描述
description?: string;
// 排序
sortNumber?: number;
// 租户id
tenantId?: number;
key?: string;
label?: string;
value?: string;
detail?: [string];
specName?: string;
}
/**
* 规格值搜索条件
*/
export interface SpecValueParam extends PageParam {
specValueId?: number;
specId?: number;
keywords?: string;
}

View File

@@ -1,68 +0,0 @@
import request from '@/utils/request'
import type {
Ticket,
TicketReply,
TicketSubmitForm,
TicketReplyForm,
TicketAssignForm,
TicketStatusForm,
TicketQueryParams,
TicketStats,
} from './model'
const BASE = '/api/app/ticket'
/** 分页查询工单列表(客户端:查自己的) */
export async function getMyTickets(params: TicketQueryParams) {
return request.get<{ list: Ticket[]; count: number }>(`${BASE}/my`, { params })
}
/** 分页查询工单列表(技术人员:查所有/分配给自己的) */
export async function getAllTickets(params: TicketQueryParams) {
return request.get<{ list: Ticket[]; count: number }>(`${BASE}/list`, { params })
}
/** 获取工单详情 */
export async function getTicketDetail(ticketId: number) {
return request.get<Ticket>(`${BASE}/${ticketId}`)
}
/** 提交工单 */
export async function submitTicket(data: TicketSubmitForm) {
return request.post<Ticket>(`${BASE}/submit`, data)
}
/** 更新工单状态(技术人员) */
export async function updateTicketStatus(data: TicketStatusForm) {
return request.put(`${BASE}/status`, data)
}
/** 分配工单给技术人员(管理员) */
export async function assignTicket(data: TicketAssignForm) {
return request.put(`${BASE}/assign`, data)
}
/** 关闭工单(提交人) */
export async function closeTicket(ticketId: number) {
return request.put(`${BASE}/${ticketId}/close`)
}
/** 获取工单回复列表 */
export async function getTicketReplies(ticketId: number) {
return request.get<TicketReply[]>(`${BASE}/${ticketId}/replies`)
}
/** 提交工单回复 */
export async function replyTicket(data: TicketReplyForm) {
return request.post(`${BASE}/reply`, data)
}
/** 获取工单统计 */
export async function getTicketStats(params?: { productId?: number }) {
return request.get<TicketStats>(`${BASE}/stats`, { params })
}
/** 获取技术人员列表(用于分配) */
export async function getTechStaffList() {
return request.get<{ userId: number; nickname: string; avatar: string }[]>(`${BASE}/staff-list`)
}

View File

@@ -1,103 +0,0 @@
/** 工单优先级 */
export type TicketPriority = 'low' | 'normal' | 'high' | 'urgent'
/** 工单状态 */
export type TicketStatus = 'pending' | 'assigned' | 'processing' | 'resolved' | 'closed' | 'rejected'
/** 工单分类 */
export type TicketCategory = 'bug' | 'feature' | 'consultation' | 'complaint' | 'other'
/** 工单列表项 */
export interface Ticket {
ticketId: number
ticketNo: string // 工单编号 TK-202403xxxxxx
title: string
content: string
productId: number // 关联应用ID
productName?: string // 应用名称(前端展示用)
category: TicketCategory
priority: TicketPriority
status: TicketStatus
attachments?: string[] // 附件URL列表
// 提交方
submitUserId: number
submitUserName?: string
submitUserAvatar?: string
// 分配方
assigneeId?: number
assigneeName?: string
assigneeAvatar?: string
// 时间
createTime: string
updateTime: string
resolvedTime?: string
closedTime?: string
// 回复数
replyCount: number
// 是否已读(对当前用户)
hasUnread?: boolean
}
/** 工单回复 */
export interface TicketReply {
replyId: number
ticketId: number
content: string
attachments?: string[]
userId: number
userName?: string
userAvatar?: string
isStaff: boolean // 是否是客服/技术人员
createTime: string
}
/** 提交工单表单 */
export interface TicketSubmitForm {
title: string
content: string
productId: number
category: TicketCategory
priority: TicketPriority
attachments?: string[]
}
/** 工单回复表单 */
export interface TicketReplyForm {
ticketId: number
content: string
attachments?: string
}
/** 分配工单表单 */
export interface TicketAssignForm {
ticketId: number
assigneeId: number
}
/** 更新状态表单 */
export interface TicketStatusForm {
ticketId: number
status: TicketStatus
remark?: string
}
/** 工单查询参数 */
export interface TicketQueryParams {
productId?: number
status?: TicketStatus
category?: TicketCategory
priority?: TicketPriority
assigneeId?: number
keywords?: string
page?: number
limit?: number
}
/** 工单统计 */
export interface TicketStats {
total: number
pending: number
processing: number
resolved: number
closed: number
}

View File

@@ -6,8 +6,7 @@
<div class="flex items-center gap-8 nav-left"> <div class="flex items-center gap-8 nav-left">
<!-- Logo --> <!-- Logo -->
<NuxtLink to="/" class="flex items-center logo-link cursor-pointer flex-shrink-0"> <NuxtLink to="/" class="flex items-center logo-link cursor-pointer flex-shrink-0">
<img src="/logo.png" alt="websopy" class="logo-img" /> <div class="logo-text">决策咨询网</div>
<div class="site-name mx-2">{{ 'websopy' }}</div>
</NuxtLink> </NuxtLink>
<!-- PC 导航菜单 --> <!-- PC 导航菜单 -->
@@ -63,26 +62,10 @@
<template #overlay> <template #overlay>
<a-menu @click="onUserMenuClick"> <a-menu @click="onUserMenuClick">
<a-menu-item key="profile"><ProfileOutlined style="margin-right: 8px" />个人信息</a-menu-item> <a-menu-item key="profile"><ProfileOutlined style="margin-right: 8px" />个人信息</a-menu-item>
<a-menu-item key="orders"><ShoppingCartOutlined style="margin-right: 8px" />我的订单</a-menu-item> <a-menu-item key="my-suggestions"><MessageOutlined style="margin-right: 8px" />我的建言</a-menu-item>
<a-menu-item key="account-kyc">
<IdcardOutlined style="margin-right: 8px" />
实名认证
</a-menu-item>
<template v-if="isDeveloper">
<a-menu-divider />
<a-menu-item key="developer">🛠 {{ $t('nav.developer') || '开发者中心' }}</a-menu-item>
<a-menu-item key="env-dev" @click.stop="switchEnv('dev')">
<span :class="{ 'font-bold': currentEnv === 'dev' }">🔧 {{ $t('common.devEnv') || '开发环境' }}</span>
<span v-if="currentEnv === 'dev'" class="ml-2 text-green-500"></span>
</a-menu-item>
<a-menu-item key="env-prod" @click.stop="switchEnv('prod')">
<span :class="{ 'font-bold': currentEnv === 'prod' }">🚀 {{ $t('common.prodEnv') || '生产环境' }}</span>
<span v-if="currentEnv === 'prod'" class="ml-2 text-green-500"></span>
</a-menu-item>
</template>
<template v-if="isSuperAdmin"> <template v-if="isSuperAdmin">
<a-menu-divider /> <a-menu-divider />
<a-menu-item key="admin"> 台管理</a-menu-item> <a-menu-item key="admin"> 台管理</a-menu-item>
</template> </template>
<a-menu-divider /> <a-menu-divider />
<a-menu-item key="logout">{{ $t('nav.logout') }}</a-menu-item> <a-menu-item key="logout">{{ $t('nav.logout') }}</a-menu-item>
@@ -141,37 +124,26 @@ import { getUserInfo } from '@/api/layout'
import type { User } from '@/api/system/user/model' import type { User } from '@/api/system/user/model'
import { getToken, removeToken } from '@/utils/token-util' import { getToken, removeToken } from '@/utils/token-util'
import { clearAuthz, setAuthzFromUser } from '@/utils/permission' import { clearAuthz, setAuthzFromUser } from '@/utils/permission'
import { UserOutlined, ProfileOutlined, IdcardOutlined, ShoppingCartOutlined } from '@ant-design/icons-vue' import { UserOutlined, ProfileOutlined, MessageOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { ENV_CONFIG, getCurrentEnv, setCurrentEnv, type EnvKey } from '@/config/setting'
import InviteBell from './invite/InviteBell.vue'
const nav = computed(() => mainNav) const nav = computed(() => mainNav)
const route = useRoute() const route = useRoute()
const open = ref(false) const open = ref(false)
// 环境切换
const currentEnv = ref<EnvKey>(getCurrentEnv())
function switchEnv(env: EnvKey) {
setCurrentEnv(env)
currentEnv.value = env
// 同时设置 Cookie让服务器端代理也能识别环境
const cookieValue = env === 'dev' ? 'dev' : 'prod'
document.cookie = `websopy_api_env=${cookieValue}; path=/; max-age=31536000`
message.success(`已切换到 ${ENV_CONFIG[env].name},正在刷新...`)
setTimeout(() => {
window.location.reload()
}, 500)
}
const selectedKeys = computed(() => { const selectedKeys = computed(() => {
const hit = nav.value.find((n) => n.to === route.path) const hit = nav.value.find((n) => n.to === route.path)
if (hit) return [hit.to] if (hit) return [hit.to]
if (route.path.startsWith('/products')) return ['/products'] if (route.path.startsWith('/news')) return ['/news']
if (route.path.startsWith('/ai-agent')) return ['/ai-agent'] if (route.path.startsWith('/consultation')) return ['/consultation']
if (route.path.startsWith('/reference')) return ['/reference']
if (route.path.startsWith('/expert')) return ['/expert']
if (route.path.startsWith('/think-tank')) return ['/think-tank']
if (route.path.startsWith('/suggestions')) return ['/suggestions']
if (route.path.startsWith('/membership')) return ['/membership']
if (route.path.startsWith('/hanmo')) return ['/hanmo']
if (route.path.startsWith('/about')) return ['/about']
return ['/'] return ['/']
}) })
@@ -194,7 +166,6 @@ const user = ref<User | null>(null)
const isAuthed = computed(() => !!token.value) const isAuthed = computed(() => !!token.value)
const userName = computed(() => String(user.value?.nickname || user.value?.username || '已登录')) const userName = computed(() => String(user.value?.nickname || user.value?.username || '已登录'))
const isSuperAdmin = computed(() => !!(user.value as any)?.isAdmin) const isSuperAdmin = computed(() => !!(user.value as any)?.isAdmin)
const isDeveloper = computed(() => (user.value as any)?.type === 2)
const userAvatar = computed(() => { const userAvatar = computed(() => {
const candidate = const candidate =
user.value?.avatarUrl || user.value?.avatarUrl ||
@@ -232,13 +203,7 @@ async function refreshAuth() {
function goConsoleCenter() { function goConsoleCenter() {
if (!isAuthed.value) return navigateTo('/login') if (!isAuthed.value) return navigateTo('/login')
open.value = false open.value = false
navigateTo('/console') navigateTo('/profile')
}
function goDeveloperCenter() {
if (!isAuthed.value) return navigateTo('/login')
open.value = false
navigateTo('/developer')
} }
function logout() { function logout() {
@@ -257,10 +222,8 @@ function logout() {
} }
function onUserMenuClick(info: { key: string }) { function onUserMenuClick(info: { key: string }) {
if (info.key === 'profile') return navigateTo('/console/account') if (info.key === 'profile') return navigateTo('/profile')
if (info.key === 'orders') return navigateTo('/console/orders') if (info.key === 'my-suggestions') return navigateTo('/my/suggestions')
if (info.key === 'account-kyc') return navigateTo('/console/account/kyc')
if (info.key === 'developer') return navigateTo('/developer')
if (info.key === 'admin') return navigateTo('/admin') if (info.key === 'admin') return navigateTo('/admin')
if (info.key === 'logout') return logout() if (info.key === 'logout') return logout()
} }
@@ -312,16 +275,10 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.logo-img { .logo-text {
height: 22px;
width: auto;
display: block;
transition: opacity 0.2s;
}
.site-name {
color: #fff; color: #fff;
font-family: 'Alimama FangYuanTi VF,sans-serif', sans-serif; font-family: 'Alimama FangYuanTi VF,sans-serif', sans-serif;
font-size: 18px; font-size: 20px;
font-weight: 700; font-weight: 700;
letter-spacing: 0.04em; letter-spacing: 0.04em;
white-space: nowrap; white-space: nowrap;
@@ -332,10 +289,7 @@ onUnmounted(() => {
background-clip: text; background-clip: text;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
.logo-link:hover .site-name { .logo-link:hover .logo-text {
opacity: 0.85;
}
.logo-link:hover .logo-img {
opacity: 0.85; opacity: 0.85;
} }
.nav-item-wrapper { .nav-item-wrapper {

View File

@@ -1,932 +0,0 @@
<template>
<div class="apps-center">
<!-- 工具栏搜索 + 视图切换 -->
<div class="toolbar">
<a-input
v-model:value="keywords"
placeholder="搜索应用名称或标识"
class="search-input"
allow-clear
@press-enter="doSearch"
>
<template #prefix>
<SearchOutlined style="color: #bbb" />
</template>
</a-input>
<div class="toolbar-right">
<a-tooltip title="网格视图">
<a-button
:type="viewMode === 'grid' ? 'primary' : 'default'"
shape="default"
size="small"
class="view-btn"
@click="viewMode = 'grid'"
>
<template #icon><AppstoreOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="列表视图">
<a-button
:type="viewMode === 'list' ? 'primary' : 'default'"
shape="default"
size="small"
class="view-btn"
@click="viewMode = 'list'"
>
<template #icon><UnorderedListOutlined /></template>
</a-button>
</a-tooltip>
</div>
</div>
<!-- 错误提示 -->
<a-alert v-if="error" show-icon type="error" :message="String(error)" class="mb-4" />
<!-- 加载中 -->
<div v-if="pending" class="state-wrap">
<a-spin size="large" tip="加载中..." />
</div>
<!-- 应用列表 -->
<div v-else-if="list.length > 0" class="app-list">
<!-- 网格视图 -->
<div v-if="viewMode === 'grid'" class="grid-view">
<div v-for="app in list" :key="app.productId" class="app-card" @click="handleCardClick(app)">
<div class="card-body">
<div class="card-header">
<div class="app-icon" :style="{ background: iconBgColor(app.productName) }">
<img
v-if="app.icon"
:src="app.icon"
:alt="app.productName"
class="icon-img"
loading="lazy"
/>
<div v-else class="icon-placeholder">
{{ appTypeIcon(app.appType) }}
</div>
</div>
<div class="app-info">
<div class="app-name">{{ app.productName }}</div>
<div class="app-code">{{ app.productCode }}</div>
<div class="app-type">
<span v-if="(app as any)._isOwner" class="owner-badge">创建者</span>
<span v-else class="member-badge">成员</span>
<span class="app-type-tag" :class="appTypeClass(app.appType)">
{{ appTypeName(app.type, app.appType) }}
</span>
</div>
</div>
</div>
<div class="card-content">
<div class="app-desc">{{ app.description || '暂无描述' }}</div>
</div>
<div class="card-footer">
<div class="app-meta">
<span class="meta-item">
<UserOutlined style="color: #999; margin-right: 4px" />
{{ app.developer }}
</span>
<span class="meta-item">
<ClockCircleOutlined style="color: #999; margin-right: 4px" />
{{ formatDateTime(app.updateTime || app.createTime) }}
</span>
</div>
<div class="app-actions">
<template v-for="entry in getAppEntries(app)" :key="entry.type">
<a
v-if="entry.available"
class="enter-link"
:class="{ 'primary-entry': entry.isPrimary }"
@click.stop="handleEntryClick(entry, app)"
>
<component :is="entry.icon" style="margin-right: 3px" />
{{ entry.label }}
</a>
</template>
</div>
</div>
</div>
</div>
</div>
<!-- 列表视图 -->
<div v-else class="list-view">
<div v-for="app in list" :key="app.productId" class="app-row">
<div class="list-left">
<div class="list-icon" :style="{ background: iconBgColor(app.productName) }">
<img
v-if="app.icon"
:src="app.icon"
:alt="app.productName"
class="list-icon-img"
loading="lazy"
/>
<div v-else class="list-icon-placeholder">
{{ appTypeIcon(app.appType) }}
</div>
</div>
<div class="list-info">
<div class="list-name">{{ app.productName }}</div>
<div class="list-meta">
<span>{{ app.productCode }}</span>
<span v-if="(app as any)._isOwner" class="owner-badge-sm">创建者</span>
<span v-else class="member-badge-sm">成员</span>
<span class="list-type-tag" :class="appTypeClass(app.appType)">
{{ appTypeName(app.type, app.appType) }}
</span>
<span>{{ app.description || '暂无描述' }}</span>
</div>
</div>
</div>
<div class="list-right">
<div class="list-time">{{ formatTime(app.createTime) }}</div>
<div class="list-actions">
<template v-for="entry in getAppEntries(app)" :key="entry.type">
<a
v-if="entry.available"
class="enter-link-small"
:class="{ 'primary-entry': entry.isPrimary }"
@click.stop="handleEntryClick(entry, app)"
>
<component :is="entry.icon" style="margin-right: 3px" />
{{ entry.label }}
</a>
</template>
</div>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else-if="list.length === 0" class="state-wrap">
<a-empty description="暂无应用,快来创建你的第一个应用吧">
<template #image>
<div class="empty-icon">📦</div>
</template>
<div class="empty-guide">
<div class="empty-actions">
<a-button type="primary" @click="goToDeveloperCenter">
<template #icon><PlusOutlined /></template>
创建企业自建应用
</a-button>
<a-button @click="goToMarket">
<template #icon><ShopOutlined /></template>
浏览应用商店
</a-button>
</div>
<div class="empty-tips">
<span class="empty-tip">🛠 前往开发者中心创建专属应用</span>
<span class="empty-tip">🛒 从应用商店购买现成应用快速使用</span>
</div>
</div>
</a-empty>
</div>
<!-- 应用详情抽屉放在条件渲染链之外 -->
<AppDetail v-model:open="detailOpen" :app="selectedApp" @deleted="handleDeletedFromDetail" @updated="handleUpdatedFromDetail" />
<!-- 小程序扫码弹窗 -->
<QrCodeModal
v-model:open="qrOpen"
:qrcode-url="qrApp?.qrcode"
:app-name="qrApp?.productName"
:title="qrApp ? (APP_TYPE_NAME[qrApp.appType ?? 10] || '小程序') + '二维码' : ''"
:tip="qrApp ? getScanTip(qrApp.appType ?? 20) : ''"
/>
<!-- 分页 -->
<div v-if="total > limit" class="pagination-wrap">
<a-pagination
:current="page"
:page-size="limit"
:total="total"
show-size-changer
:page-size-options="['10', '20', '50']"
@change="onPageChange"
@show-size-change="onPageSizeChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import {
AppstoreOutlined,
SearchOutlined,
UnorderedListOutlined,
DeleteOutlined,
EllipsisOutlined,
EyeOutlined,
UserOutlined,
ClockCircleOutlined,
PlusOutlined,
ShopOutlined,
GlobalOutlined,
QrcodeOutlined,
DownloadOutlined,
SettingOutlined,
} from '@ant-design/icons-vue'
import { message, Modal } from 'ant-design-vue'
import { removeAppProduct, getMyApps, getJoinedApps } from '@/api/app/appProduct'
import type { AppProduct } from '@/api/app/appProduct/model'
import { APP_TYPE, APP_TYPE_NAME } from '@/api/app/appProduct/model'
import AppDetail from '@/components/developer/AppDetail.vue'
import QrCodeModal from '@/components/QrCodeModal.vue'
import { getAppEntries, executeEntry, getScanTip } from '@/utils/appEntry'
import type { AppEntry } from '@/utils/appEntry'
const props = defineProps<{
userId?: number | string | null
ownerName?: string
}>()
const viewMode = ref<'grid' | 'list'>('grid')
const page = ref(1)
const limit = ref(10)
const keywords = ref('')
const total = ref(0)
// 详情抽屉
const detailOpen = ref(false)
const selectedApp = ref<AppProduct | null>(null)
function openDetail(app: AppProduct) {
selectedApp.value = app
detailOpen.value = true
}
// 我的应用(创建的应用)+ 参与的应用(被邀请的)
const myApps = ref<AppProduct[]>([])
const joinedApps = ref<AppProduct[]>([])
const pending = ref(false)
const error = ref<string | null>(null)
// 计算合并后的应用列表(去重)
const list = computed(() => {
const map = new Map<number, AppProduct>()
// 先加我创建的
myApps.value.forEach(app => {
if (app.productId) map.set(app.productId, { ...app, _isOwner: true })
})
// 再加我参与的(避免重复)
joinedApps.value.forEach(app => {
if (app.productId && !map.has(app.productId)) {
map.set(app.productId, { ...app, _isOwner: false })
}
})
return Array.from(map.values())
})
async function fetchApps() {
pending.value = true
error.value = null
try {
// 并行加载:我创建的应用 + 我参与的应用
const [myResult, joinedResult] = await Promise.all([
getMyApps({ page: 1, limit: 100 }),
getJoinedApps({ page: 1, limit: 100 }),
])
myApps.value = myResult?.list ?? []
joinedApps.value = joinedResult?.list ?? []
total.value = list.value.length
} catch (e: any) {
error.value = e.message || '加载失败'
} finally {
pending.value = false
}
}
function doSearch() {
page.value = 1
fetchApps()
}
function onPageChange(nextPage: number) {
page.value = nextPage
fetchApps()
}
function onPageSizeChange(_current: number, nextSize: number) {
limit.value = nextSize
page.value = 1
fetchApps()
}
function handleCardClick(app: AppProduct) {
openDetail(app)
}
// 菜单操作
function handleMenuAction(key: string, app: AppProduct) {
if (key === 'detail') {
openDetail(app)
} else if (key === 'delete') {
handleDeleteApp(app)
}
}
// 删除应用
const deletingAppId = ref<number | null>(null)
function handleDeletedFromDetail() {
selectedApp.value = null
detailOpen.value = false
refresh()
}
// 更新应用后同步本地数据
function handleUpdatedFromDetail(updatedApp: AppProduct) {
// 更新列表中的应用数据
const index = list.value.findIndex((app) => app.productId === updatedApp.productId)
if (index !== -1) {
list.value[index] = { ...list.value[index], ...updatedApp }
}
// 更新当前选中的应用
if (selectedApp.value?.productId === updatedApp.productId) {
selectedApp.value = { ...selectedApp.value, ...updatedApp }
}
}
function handleDeleteApp(app: AppProduct) {
const name = app.productName || app.productCode || '该应用'
Modal.confirm({
title: '确认删除应用',
content: `确定要删除应用「${name}」吗?删除后所有配置、成员和版本记录将被永久清除,且无法恢复。`,
okText: '确认删除',
cancelText: '取消',
okType: 'danger',
async onOk() {
deletingAppId.value = app.productId ?? null
try {
await removeAppProduct(app.productId)
message.success(`应用「${name}」已删除`)
// 如果详情抽屉打开的是当前应用,关闭它
if (selectedApp.value?.productId === app.productId) {
detailOpen.value = false
selectedApp.value = null
}
// 刷新列表
await refresh()
} catch (e: any) {
message.error(e.message || '删除失败')
} finally {
deletingAppId.value = null
}
},
})
}
// 外部刷新:重新查询应用列表
async function refresh() {
await fetchApps()
}
// 暴露 refresh 方法供父组件调用
defineExpose({ refresh })
function handleEntryClick(entry: AppEntry, app: AppProduct) {
if (entry.type === 'scan-qr') {
// 小程序扫码 → 弹出二维码弹窗
qrApp.value = app
qrOpen.value = true
return
}
executeEntry(entry)
}
// 扫码弹窗
const qrOpen = ref(false)
const qrApp = ref<AppProduct | null>(null)
// 应用类型名称(使用统一枚举)
function appTypeName(type?: number, appType?: string): string {
return APP_TYPE_NAME[type ?? 0] ?? 'Web 应用'
}
function appTypeIcon(appType?: string | number): string {
const numType = typeof appType === 'string' ? Number(appType) : (appType ?? 10)
const iconMap: Record<number, string> = {
[APP_TYPE.WEBSITE]: '🌐',
[APP_TYPE.WECHAT_MP]: '📱',
[APP_TYPE.DOUYIN_MP]: '🎵',
[APP_TYPE.BAIDU_MP]: '🔍',
[APP_TYPE.ALIPAY_MP]: '💎',
[APP_TYPE.ANDROID]: '🤖',
[APP_TYPE.IOS]: '🍎',
[APP_TYPE.MACOS]: '💻',
[APP_TYPE.WINDOWS]: '🪟',
[APP_TYPE.PLUGIN]: '🔌',
}
return iconMap[numType] ?? '🌐'
}
function appTypeClass(appType?: string | number): string {
const numType = typeof appType === 'string' ? Number(appType) : (appType ?? 10)
const classMap: Record<number, string> = {
[APP_TYPE.WEBSITE]: 'type-10',
[APP_TYPE.WECHAT_MP]: 'type-20',
[APP_TYPE.DOUYIN_MP]: 'type-30',
[APP_TYPE.BAIDU_MP]: 'type-40',
[APP_TYPE.ALIPAY_MP]: 'type-50',
[APP_TYPE.ANDROID]: 'type-60',
[APP_TYPE.IOS]: 'type-70',
[APP_TYPE.MACOS]: 'type-80',
[APP_TYPE.WINDOWS]: 'type-90',
[APP_TYPE.PLUGIN]: 'type-100',
}
return classMap[numType] ?? 'type-10'
}
function formatDateTime(dateStr?: string) {
if (!dateStr) return '-'
// 格式化为 "2026-03-28 10:46"
return dateStr.slice(0, 16).replace('T', ' ')
}
// 图标背景色
const PALETTE = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae', '#87d068', '#108ee9']
function iconBgColor(name?: string) {
if (!name) return PALETTE[0]
let h = 0
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) & 0xffffffff
return PALETTE[Math.abs(h) % PALETTE.length]
}
// 格式化时间
function formatTime(timestamp?: string | number | Date) {
if (!timestamp) return '-'
const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(timestamp)
return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })
}
// 导航函数
function goToDeveloperCenter() {
navigateTo('/developer/apps')
}
function goToMarket() {
navigateTo('/market')
}
// 组件挂载时加载数据
onMounted(() => {
fetchApps()
})
</script>
<style scoped>
.apps-center {
min-height: 400px;
}
/* ===== 工具栏 ===== */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
gap: 12px;
}
.search-input {
flex: 1;
max-width: 300px;
}
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.view-btn {
width: 32px;
height: 32px;
}
/* ===== 状态容器 ===== */
.state-wrap {
display: flex;
align-items: center;
justify-content: center;
min-height: 300px;
background: #fff;
border-radius: 12px;
}
.empty-icon {
font-size: 48px;
margin-bottom: 4px;
}
/* 空状态引导 */
.empty-guide {
margin-top: 16px;
text-align: center;
}
.empty-actions {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 16px;
}
.empty-tips {
display: flex;
flex-direction: column;
gap: 6px;
}
.empty-tip {
font-size: 13px;
color: #999;
}
/* ===== 网格视图 ===== */
.grid-view {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 20px;
}
.app-card {
background: #fff;
border-radius: 12px;
border: 1px solid #f0f0f0;
transition: all 0.2s;
cursor: pointer;
overflow: hidden;
}
.app-card:hover {
border-color: #d6e4ff;
box-shadow: 0 4px 12px rgba(102, 102, 102, 0.08);
transform: translateY(-2px);
}
.card-body {
padding: 20px;
}
.card-header {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
}
.app-icon {
width: 70px;
height: 70px;
border-radius: 10px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #fff;
overflow: hidden;
}
.icon-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.icon-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 700;
color: #fff;
}
.app-info {
flex: 1;
min-width: 0;
}
.app-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
}
.app-code {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.app-type {
margin-top: 2px;
}
.card-content {
margin-bottom: 12px;
}
.app-desc {
font-size: 13px;
color: #666;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
.app-meta {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #999;
}
.meta-item {
display: flex;
align-items: center;
}
.app-actions {
display: flex;
align-items: center;
gap: 8px;
}
.enter-link {
font-size: 12px;
color: #1890ff;
text-decoration: none;
padding: 2px 8px;
border-radius: 4px;
background: #e6f4ff;
transition: all 0.2s;
}
.enter-link:hover {
background: #bae0ff;
color: #0958d9;
}
.enter-link.primary-entry {
color: #fff;
background: #1890ff;
}
.enter-link.primary-entry:hover {
background: #0958d9;
color: #fff;
}
.enter-link-disabled {
color: #999;
background: #f5f5f5;
cursor: not-allowed;
}
.enter-link-disabled:hover {
color: #999;
background: #f5f5f5;
}
/* ===== 列表视图 ===== */
.list-view {
background: #fff;
border-radius: 12px;
border: 1px solid #f0f0f0;
overflow: hidden;
}
.app-row {
display: flex;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
transition: background 0.2s;
cursor: pointer;
}
.app-row:last-child { border-bottom: none; }
.app-row:hover { background: #fafafa; }
.list-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
min-width: 0;
}
.list-icon {
width: 40px;
height: 40px;
border-radius: 8px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #fff;
overflow: hidden;
}
.list-icon-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.list-icon-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 700;
color: #fff;
}
.list-info {
flex: 1;
min-width: 0;
}
.list-name {
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.list-meta {
font-size: 12px;
color: #999;
margin-top: 2px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.list-type-tag {
font-size: 11px;
padding: 1px 7px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #f0f0f0;
color: #666;
}
.list-type-tag.type-10 { background: #e6f4ff; color: #0958d9; }
.list-type-tag.type-20 { background: #f6ffed; color: #389e0d; }
.list-type-tag.type-60 { background: #fff7e6; color: #d46b08; }
.list-type-tag.type-100 { background: #f9f0ff; color: #722ed1; }
.list-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
}
.list-time {
font-size: 12px;
color: #bbb;
}
.list-actions {
display: flex;
align-items: center;
gap: 8px;
}
.enter-link-small {
font-size: 12px;
color: #1890ff;
text-decoration: none;
padding: 2px 8px;
border-radius: 4px;
background: #e6f4ff;
transition: all 0.2s;
}
.enter-link-small:hover {
background: #bae0ff;
color: #0958d9;
}
.enter-link-small.primary-entry {
color: #fff;
background: #1890ff;
}
.enter-link-small.primary-entry:hover {
background: #0958d9;
color: #fff;
}
.enter-link-small.enter-link-disabled {
color: #999;
background: #f5f5f5;
cursor: not-allowed;
}
.enter-link-small.enter-link-disabled:hover {
color: #999;
background: #f5f5f5;
}
/* ===== 分页 ===== */
.pagination-wrap {
display: flex;
justify-content: center;
padding: 4px 0 0;
}
/* ===== 应用类型标签 ===== */
.app-type-tag {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 11px;
padding: 1px 7px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #f0f0f0;
color: #666;
}
.app-type-tag.type-10 { background: #e6f4ff; color: #0958d9; }
.app-type-tag.type-20 { background: #f6ffed; color: #389e0d; }
.app-type-tag.type-60 { background: #fff7e6; color: #d46b08; }
.app-type-tag.type-100 { background: #f9f0ff; color: #722ed1; }
/* ===== 创建者/成员标签 ===== */
.owner-badge {
display: inline-flex;
align-items: center;
font-size: 11px;
padding: 1px 7px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #fff1f0;
color: #cf1322;
margin-right: 6px;
}
.member-badge {
display: inline-flex;
align-items: center;
font-size: 11px;
padding: 1px 7px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #f6ffed;
color: #389e0d;
margin-right: 6px;
}
.owner-badge-sm {
display: inline-flex;
align-items: center;
font-size: 10px;
padding: 1px 6px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #fff1f0;
color: #cf1322;
margin-right: 4px;
}
.member-badge-sm {
display: inline-flex;
align-items: center;
font-size: 10px;
padding: 1px 6px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #f6ffed;
color: #389e0d;
margin-right: 4px;
}
</style>

View File

@@ -1,176 +0,0 @@
<template>
<a-layout-header class="top-header !p-0">
<div class="h-full px-4 flex items-center justify-between">
<div class="logo">
<a-space size="large">
<div class="logo-brand" @click="navigateTo('/console')">
<img src="/logo.png" alt="logo" class="logo-img" />
<span class="logo-name">控制台</span>
</div>
</a-space>
</div>
<a-dropdown placement="bottomRight" :trigger="['click']">
<div class="user-trigger">
<a-space>
<a-avatar :size="28" :src="user?.avatar || user?.avatarUrl">
<template #icon>
<AppstoreOutlined />
</template>
</a-avatar>
<span class="user-name">
{{ userDisplayName }}
</span>
</a-space>
</div>
<template #overlay>
<a-menu @click="onUserMenuClick">
<a-menu-item v-for="item in mergedUserMenuItems" :key="item.key">
{{ item.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</a-layout-header>
</template>
<script setup lang="ts">
import { AppstoreOutlined } from '@ant-design/icons-vue'
import type { MenuProps } from 'ant-design-vue'
import type { User } from '@/api/system/user/model'
type ConsoleHeaderMenuItem = {
key: string
label: string
}
const props = withDefaults(
defineProps<{
productLabel?: string
defaultJumpKey?: 'oa' | 'developer'
user: User | null
userDisplayName: string
userMenuItems?: ConsoleHeaderMenuItem[]
}>(),
{
productLabel: '云·企业官网',
defaultJumpKey: 'developer',
userMenuItems: () => [{ key: 'logout', label: '退出登录' }],
}
)
const emit = defineEmits<{
(e: 'logout'): void
(e: 'userMenuClick', key: string): void
}>()
const mergedUserMenuItems = computed<ConsoleHeaderMenuItem[]>(() => {
const items = Array.isArray(props.userMenuItems) ? props.userMenuItems.slice() : []
if (!items.some((i) => i.key === 'account')) {
const accountItem: ConsoleHeaderMenuItem = { key: 'account', label: '账号管理' }
const logoutIndex = items.findIndex((i) => i.key === 'logout')
if (logoutIndex >= 0) items.splice(logoutIndex, 0, accountItem)
else items.push(accountItem)
}
return items
})
const consoleJumpTargets = {
oa: '/oa',
developer: '/developer',
} as const
function openExternal(url: string) {
if (!import.meta.client) return
if (url.startsWith('http://') || url.startsWith('https://')) {
window.location.href = url
return
}
void navigateTo(url)
}
const handleButtonClick = () => {
openExternal(consoleJumpTargets[props.defaultJumpKey])
}
const handleProductMenuClick: MenuProps['onClick'] = (e) => {
const key = String(e.key) as keyof typeof consoleJumpTargets
const url = consoleJumpTargets[key]
if (!url) return
openExternal(url)
}
function onUserMenuClick(info: { key: string }) {
const key = String(info.key)
emit('userMenuClick', key)
if (key === 'account') {
void navigateTo('/console/account')
return
}
if (key === 'logout') emit('logout')
}
</script>
<style scoped>
.top-header {
height: 56px;
line-height: 56px;
border-radius: 12px;
background: #111827 !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.user-trigger {
height: 36px;
display: flex;
align-items: center;
padding: 0 10px;
border-radius: 9999px;
cursor: pointer;
color: rgba(255, 255, 255, 0.85);
}
.user-trigger:hover {
background: rgba(255, 255, 255, 0.08);
}
.user-name {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: rgba(255, 255, 255, 0.85);
}
.logo-brand {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
text-decoration: none;
}
.logo-brand:hover .logo-img,
.logo-brand:hover .logo-name {
opacity: 0.8;
}
.logo-img {
height: 22px;
width: auto;
display: block;
}
.logo-name {
font-family: 'Alimama FangYuanTi VF, sans-serif', sans-serif;
font-size: 18px;
font-weight: 700;
letter-spacing: 0.04em;
white-space: nowrap;
background: linear-gradient(135deg, #ffffff 0%, #a5c8ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
</style>

View File

@@ -1,605 +0,0 @@
<template>
<div class="contract-management">
<!-- 顶部操作栏 -->
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<a-input-search
v-model:value="queryParam.keywords"
placeholder="搜索合同名称/编号"
allow-clear
style="width: 220px"
@search="handleSearch"
@change="onKeywordChange"
/>
<a-select
v-model:value="queryParam.contractType"
placeholder="合同类型"
allow-clear
style="width: 140px"
@change="handleSearch"
>
<a-select-option v-for="opt in contractTypeOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</a-select-option>
</a-select>
<a-select
v-model:value="queryParam.status"
placeholder="合同状态"
allow-clear
style="width: 140px"
@change="handleSearch"
>
<a-select-option v-for="opt in contractStatusOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</a-select-option>
</a-select>
</div>
<a-button type="primary" @click="openAddModal">
<template #icon><PlusOutlined /></template>
新增合同
</a-button>
</div>
<!-- 统计卡片 -->
<a-row :gutter="[12, 12]" class="mb-4">
<a-col :xs="12" :sm="8" :md="4" v-for="stat in statsCards" :key="stat.label">
<div class="stat-card" :class="stat.cls">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</a-col>
</a-row>
<!-- 表格 -->
<a-card :bordered="false">
<a-table
:data-source="contracts"
:loading="loading"
:pagination="pagination"
size="middle"
:row-key="(r: Contract) => r.contractId!"
@change="handleTableChange"
>
<a-table-column title="合同编号" data-index="contractNo" width="185">
<template #default="{ record }">
<span class="font-mono text-sm text-gray-600">{{ record.contractNo || '-' }}</span>
</template>
</a-table-column>
<a-table-column title="合同名称" data-index="title" ellipsis>
<template #default="{ record }">
<span class="font-medium cursor-pointer text-blue-600 hover:text-blue-400" @click="openDetail(record)">
{{ record.title }}
</span>
</template>
</a-table-column>
<a-table-column title="合同类型" data-index="contractType" width="110">
<template #default="{ record }">
{{ contractTypeText(record.contractType) }}
</template>
</a-table-column>
<a-table-column title="签约方" width="230">
<template #default="{ record }">
<div class="text-xs leading-5">
<div>甲方:{{ record.partyA || '-' }}</div>
<div>乙方:{{ record.partyB || '-' }}</div>
</div>
</template>
</a-table-column>
<a-table-column title="合同金额" data-index="amount" width="120">
<template #default="{ record }">
<span class="text-orange-600 font-semibold">¥{{ formatAmount(record.amount) }}</span>
</template>
</a-table-column>
<a-table-column title="有效期" width="210">
<template #default="{ record }">
<span v-if="record.startDate && record.endDate" class="text-sm">
{{ record.startDate }} ~ {{ record.endDate }}
</span>
<span v-else class="text-gray-400 text-sm">-</span>
</template>
</a-table-column>
<a-table-column title="状态" data-index="status" width="100">
<template #default="{ record }">
<a-tag :color="contractStatusInfo(record.status).color">
{{ contractStatusInfo(record.status).text }}
</a-tag>
</template>
</a-table-column>
<a-table-column title="操作" width="180" fixed="right">
<template #default="{ record }">
<a-space>
<a-button size="small" @click="openDetail(record)">查看</a-button>
<a-button size="small" @click="openEdit(record)">编辑</a-button>
<a-button danger size="small" @click="confirmRemove(record)">删除</a-button>
</a-space>
</template>
</a-table-column>
</a-table>
</a-card>
<!-- 新增/编辑弹窗 -->
<a-modal
v-model:open="formVisible"
:title="editingId ? '编辑合同' : '新增合同'"
:width="660"
:confirm-loading="formSubmitting"
@ok="handleFormSubmit"
@cancel="closeForm"
>
<a-form ref="formRef" layout="vertical" :model="formData" :rules="formRules" class="mt-2">
<a-form-item label="合同名称" name="title">
<a-input v-model:value="formData.title" placeholder="请输入合同名称" />
</a-form-item>
<a-row :gutter="12">
<a-col :span="12">
<a-form-item label="合同类型" name="contractType">
<a-select v-model:value="formData.contractType" placeholder="请选择合同类型" style="width:100%">
<a-select-option v-for="opt in contractTypeOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="合同状态" name="status">
<a-select v-model:value="formData.status" placeholder="请选择状态" style="width:100%">
<a-select-option v-for="opt in contractStatusOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="12">
<a-form-item label="甲方" name="partyA">
<a-input v-model:value="formData.partyA" placeholder="请输入甲方名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="乙方" name="partyB">
<a-input v-model:value="formData.partyB" placeholder="请输入乙方名称" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="8">
<a-form-item label="合同金额" name="amount">
<a-input-number
v-model:value="formData.amount"
:min="0"
:precision="2"
placeholder="金额"
style="width: 100%"
>
<template #prefix>¥</template>
</a-input-number>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="开始日期" name="startDate">
<a-date-picker
v-model:value="formData.startDate"
value-format="YYYY-MM-DD"
placeholder="开始日期"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="结束日期" name="endDate">
<a-date-picker
v-model:value="formData.endDate"
value-format="YYYY-MM-DD"
placeholder="结束日期"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 合同附件上传 -->
<a-form-item label="合同附件">
<a-upload
:file-list="fileList"
:max-count="1"
:before-upload="() => false"
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
@change="handleFileChange"
>
<a-button>
<UploadOutlined /> 上传合同文件
</a-button>
<span class="ml-2 text-gray-400 text-xs">支持 PDF、Word、图片最大 20MB</span>
</a-upload>
<div v-if="formData.fileUrl && !fileList.length" class="mt-1">
<a :href="formData.fileUrl" target="_blank" class="text-blue-500 text-sm">
<PaperClipOutlined /> {{ formData.fileName || '查看已上传文件' }}
</a>
<a-button type="link" danger size="small" class="ml-2" @click="clearFile">移除</a-button>
</div>
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="formData.remark" :rows="3" placeholder="备注选填" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情弹窗 -->
<a-modal
v-model:open="detailVisible"
title="合同详情"
:width="700"
:footer="null"
>
<a-descriptions v-if="detail" bordered size="small" :column="2" class="mt-2">
<a-descriptions-item label="合同编号" :span="2">
<span class="font-mono">{{ detail.contractNo || '-' }}</span>
</a-descriptions-item>
<a-descriptions-item label="合同名称" :span="2">{{ detail.title }}</a-descriptions-item>
<a-descriptions-item label="合同类型">{{ contractTypeText(detail.contractType) }}</a-descriptions-item>
<a-descriptions-item label="合同状态">
<a-tag :color="contractStatusInfo(detail.status).color">{{ contractStatusInfo(detail.status).text }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="甲方">{{ detail.partyA || '-' }}</a-descriptions-item>
<a-descriptions-item label="乙方">{{ detail.partyB || '-' }}</a-descriptions-item>
<a-descriptions-item label="合同金额">
<span class="text-orange-600 font-semibold">¥{{ formatAmount(detail.amount) }}</span>
</a-descriptions-item>
<a-descriptions-item label="有效期">
<span v-if="detail.startDate && detail.endDate">{{ detail.startDate }} ~ {{ detail.endDate }}</span>
<span v-else class="text-gray-400">-</span>
</a-descriptions-item>
<a-descriptions-item label="创建人">{{ detail.userName || '-' }}</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ detail.createTime || '-' }}</a-descriptions-item>
<a-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</a-descriptions-item>
<a-descriptions-item v-if="detail.fileUrl" label="合同附件" :span="2">
<a :href="detail.fileUrl" target="_blank" class="text-blue-500">
<PaperClipOutlined /> {{ detail.fileName || '查看附件' }}
</a>
</a-descriptions-item>
</a-descriptions>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { message, Modal } from 'ant-design-vue'
import { PlusOutlined, UploadOutlined, PaperClipOutlined } from '@ant-design/icons-vue'
import { uploadFile } from '@/api/system/file'
import type { UploadChangeParam, UploadFile } from 'ant-design-vue'
import {
pageContract,
addContract,
updateContract,
removeContract,
statsContract,
contractTypeOptions,
contractStatusOptions,
contractTypeText,
contractStatusInfo,
type Contract,
type ContractType,
type ContractStatus,
} from '@/api/app/contract'
defineOptions({ name: 'ContractManagement' })
// ─── 列表 & 分页 ──────────────────────────────────────────────
const contracts = ref<Contract[]>([])
const loading = ref(false)
const total = ref(0)
const queryParam = reactive<{
keywords?: string
contractType?: ContractType
status?: ContractStatus
page: number
limit: number
}>({
page: 1,
limit: 15,
})
const pagination = computed(() => ({
total: total.value,
current: queryParam.page,
pageSize: queryParam.limit,
showSizeChanger: true,
showTotal: (t: number) => `${t}`,
pageSizeOptions: ['10', '15', '20', '50'],
}))
async function loadData() {
loading.value = true
try {
const res = await pageContract({
page: queryParam.page,
limit: queryParam.limit,
keywords: queryParam.keywords,
contractType: queryParam.contractType,
status: queryParam.status,
})
contracts.value = res?.list ?? []
total.value = res?.total ?? 0
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
function handleSearch() {
queryParam.page = 1
loadData()
}
let keywordTimer: ReturnType<typeof setTimeout> | null = null
function onKeywordChange() {
if (keywordTimer) clearTimeout(keywordTimer)
keywordTimer = setTimeout(handleSearch, 400)
}
function handleTableChange(pag: { current?: number; pageSize?: number }) {
queryParam.page = pag.current ?? 1
queryParam.limit = pag.pageSize ?? 15
loadData()
}
// ─── 统计 ─────────────────────────────────────────────────────
const statsData = ref<Record<string, number>>({})
const statsCards = computed(() => [
{ label: '全部', value: statsData.value.total ?? 0, cls: 'blue' },
{ label: '生效中', value: statsData.value.active ?? 0, cls: 'green' },
{ label: '待签署', value: statsData.value.pending ?? 0, cls: 'orange' },
{ label: '已过期', value: statsData.value.expired ?? 0, cls: 'red' },
{ label: '草稿', value: statsData.value.draft ?? 0, cls: 'gray' },
{ label: '已终止', value: statsData.value.terminated ?? 0, cls: 'purple' },
])
async function loadStats() {
try {
const res = await statsContract()
statsData.value = res ?? {}
} catch {}
}
// ─── 表单 ─────────────────────────────────────────────────────
const formVisible = ref(false)
const formSubmitting = ref(false)
const editingId = ref<number | null>(null)
const formRef = ref()
const fileList = ref<UploadFile[]>([])
const uploading = ref(false)
interface FormData {
title: string
contractType?: ContractType
partyA?: string
partyB?: string
amount?: number
startDate?: string
endDate?: string
status?: ContractStatus
remark?: string
fileUrl?: string
fileName?: string
}
const formData = reactive<FormData>({
title: '',
contractType: undefined,
partyA: '',
partyB: '',
amount: undefined,
startDate: undefined,
endDate: undefined,
status: 'draft',
remark: '',
fileUrl: '',
fileName: '',
})
const formRules = {
title: [{ required: true, message: '请输入合同名称' }],
contractType: [{ required: true, message: '请选择合同类型' }],
status: [{ required: true, message: '请选择合同状态' }],
}
function resetFormData() {
formData.title = ''
formData.contractType = undefined
formData.partyA = ''
formData.partyB = ''
formData.amount = undefined
formData.startDate = undefined
formData.endDate = undefined
formData.status = 'draft'
formData.remark = ''
formData.fileUrl = ''
formData.fileName = ''
fileList.value = []
editingId.value = null
}
function openAddModal() {
resetFormData()
formVisible.value = true
}
function openEdit(record: Contract) {
resetFormData()
editingId.value = record.contractId ?? null
Object.assign(formData, {
title: record.title,
contractType: record.contractType,
partyA: record.partyA ?? '',
partyB: record.partyB ?? '',
amount: record.amount,
startDate: record.startDate,
endDate: record.endDate,
status: record.status,
remark: record.remark ?? '',
fileUrl: record.fileUrl ?? '',
fileName: record.fileName ?? '',
})
formVisible.value = true
}
function closeForm() {
formVisible.value = false
formRef.value?.resetFields()
resetFormData()
}
// 文件上传
function handleFileChange({ fileList: list }: UploadChangeParam) {
fileList.value = list.slice(-1)
}
function clearFile() {
formData.fileUrl = ''
formData.fileName = ''
}
async function uploadPendingFile(): Promise<{ url: string; name: string } | null> {
const raw = fileList.value[0]?.originFileObj
if (!raw) return null
uploading.value = true
try {
const result = await uploadFile(raw as File)
return { url: (result as any).url ?? (result as any).fileUrl, name: raw.name }
} finally {
uploading.value = false
}
}
async function handleFormSubmit() {
try {
await formRef.value?.validate()
} catch {
return
}
formSubmitting.value = true
try {
// 如有待上传文件先上传
if (fileList.value.length > 0) {
const uploaded = await uploadPendingFile()
if (uploaded) {
formData.fileUrl = uploaded.url
formData.fileName = uploaded.name
}
}
const payload: Partial<Contract> = { ...formData }
if (editingId.value) {
await updateContract(editingId.value, payload)
message.success('合同已更新')
} else {
await addContract(payload)
message.success('合同已创建')
}
closeForm()
await loadData()
await loadStats()
} catch (e: any) {
message.error(e?.message || '操作失败')
} finally {
formSubmitting.value = false
}
}
// ─── 详情 ─────────────────────────────────────────────────────
const detailVisible = ref(false)
const detail = ref<Contract | null>(null)
function openDetail(record: Contract) {
detail.value = record
detailVisible.value = true
}
// ─── 删除 ─────────────────────────────────────────────────────
function confirmRemove(record: Contract) {
Modal.confirm({
title: '确认删除该合同?',
content: '删除后不可恢复。',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
await removeContract(record.contractId!)
message.success('已删除')
if (detail.value?.contractId === record.contractId) {
detailVisible.value = false
detail.value = null
}
await loadData()
await loadStats()
},
})
}
// ─── 工具函数 ─────────────────────────────────────────────────
function formatAmount(value?: number | string | null) {
if (value === null || value === undefined) return '0.00'
const n = typeof value === 'string' ? parseFloat(value) : value
return Number.isFinite(n) ? n.toFixed(2) : '0.00'
}
// ─── 初始化 ───────────────────────────────────────────────────
onMounted(() => {
loadData()
loadStats()
})
</script>
<style scoped>
.contract-management {
width: 100%;
}
.stat-card {
padding: 14px;
border-radius: 10px;
border: 1px solid transparent;
text-align: center;
transition: box-shadow 0.2s;
}
.stat-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.stat-card.blue { background: #eff6ff; border-color: #dbeafe; }
.stat-card.green { background: #f0fdf4; border-color: #bbf7d0; }
.stat-card.orange{ background: #fff7ed; border-color: #fed7aa; }
.stat-card.red { background: #fef2f2; border-color: #fecaca; }
.stat-card.gray { background: #f9fafb; border-color: #e5e7eb; }
.stat-card.purple{ background: #faf5ff; border-color: #e9d5ff; }
.stat-value {
font-size: 22px;
font-weight: 700;
color: rgba(0, 0, 0, 0.85);
line-height: 1;
}
.stat-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-top: 4px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,870 +0,0 @@
<template>
<div class="apps-center">
<!-- 统计条 -->
<div class="app-stats-bar">
<span class="stats-total">
<b>{{ total }}</b> 个应用
</span>
<span v-if="ownerCount > 0" class="stats-item owner">
<span class="stats-dot owner-dot" />我创建的 {{ ownerCount }}
</span>
<span v-if="memberCount > 0" class="stats-item member">
<span class="stats-dot member-dot" />我参与的 {{ memberCount }}
</span>
</div>
<!-- 工具栏搜索 + 视图切换 -->
<div class="toolbar">
<a-input
v-model:value="keywords"
placeholder="搜索应用名称或标识"
class="search-input"
allow-clear
@press-enter="doSearch"
>
<template #prefix>
<SearchOutlined style="color: #bbb" />
</template>
</a-input>
<div class="toolbar-right">
<a-tooltip title="网格视图">
<a-button
:type="viewMode === 'grid' ? 'primary' : 'default'"
shape="default"
size="small"
class="view-btn"
@click="viewMode = 'grid'"
>
<template #icon><AppstoreOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="列表视图">
<a-button
:type="viewMode === 'list' ? 'primary' : 'default'"
shape="default"
size="small"
class="view-btn"
@click="viewMode = 'list'"
>
<template #icon><UnorderedListOutlined /></template>
</a-button>
</a-tooltip>
</div>
</div>
<!-- 错误提示 -->
<a-alert v-if="error" show-icon type="error" :message="String(error)" class="mb-4" />
<!-- 加载中 -->
<div v-if="pending" class="state-wrap">
<a-spin size="large" tip="加载中..." />
</div>
<!-- 空状态 -->
<div v-else-if="filteredApps.length === 0" class="state-wrap">
<a-empty :description="keywords ? '没有匹配的应用' : '还没有应用,点击上方按钮创建第一个吧'">
<template #image>
<div class="empty-icon">📦</div>
</template>
<a-button v-if="!keywords" type="primary" @click="$emit('create')">
<template #icon><PlusOutlined /></template>
创建企业自建应用
</a-button>
</a-empty>
</div>
<!-- 网格视图 -->
<div v-else-if="viewMode === 'grid'" class="app-grid">
<div
v-for="app in filteredApps"
:key="app.productId"
class="app-card"
@click="handleCardClick(app)"
>
<!-- 右上角状态标签 + 角色标签 -->
<div class="card-top-actions">
<div class="card-role-badge">
<RoleTag :role="app.myRole || 'owner'" size="small" />
</div>
<div class="card-status-badge" :class="`status-${app.status}`">
{{ statusText(app.status, app.statusText) }}
</div>
</div>
<!-- 图标 + 基本信息 -->
<div class="card-main">
<div class="app-icon-wrap">
<img
v-if="app.icon || app.logo"
:src="app.icon || app.logo"
:alt="app.productName"
class="app-icon-img"
/>
<div v-else class="app-icon-placeholder" :style="{ background: iconBgColor(app.productName) }">
{{ (app.productName || 'A').charAt(0).toUpperCase() }}
</div>
</div>
<div class="card-info">
<div class="app-name">{{ app.productName || '-' }}</div>
<div class="app-meta">
<!-- 应用类型标签 -->
<span class="app-type-tag" :class="appTypeClass(app.appType)">
{{ appTypeIcon(app.appType) }} {{ appTypeName(app.appType) }}
</span>
</div>
</div>
</div>
<!-- 分割线 -->
<div class="card-divider" />
<!-- 最新动态 -->
<div class="card-activity">
<span class="activity-label">最新动态</span>
<span class="activity-time">{{ formatDateTime(app.updateTime || app.createTime) }}</span>
<span class="activity-dot">·</span>
<span class="activity-action">{{ app.domain ? '已绑域名' : '已创建' }}</span>
<template v-for="entry in getAppEntries(app)" :key="entry.type">
<a
v-if="entry.available"
class="card-enter-btn"
:class="{ 'primary-entry': entry.isPrimary }"
@click.stop="handleEntryClick(entry, app)"
>{{ entry.label }} </a>
</template>
</div>
</div>
</div>
<!-- 列表视图 -->
<div v-else class="app-list">
<div
v-for="app in filteredApps"
:key="app.productId"
class="app-list-item"
@click="openDetail(app)"
>
<div class="list-icon-wrap">
<img
v-if="app.icon || app.logo"
:src="app.icon || app.logo"
:alt="app.productName"
class="list-icon-img"
/>
<div v-else class="list-icon-placeholder" :style="{ background: iconBgColor(app.productName) }">
{{ (app.productName || 'A').charAt(0).toUpperCase() }}
</div>
</div>
<div class="list-info">
<div class="list-name">{{ app.productName || '-' }}</div>
<div class="list-meta">
<span class="app-type-tag" :class="appTypeClass(app.appType)">
{{ appTypeIcon(app.appType) }} {{ appTypeName(app.type, app.appType) }}
</span>
<span class="meta-dot">·</span>
{{ app.domain || app.productCode || '-' }}
<span class="meta-dot">·</span>
{{ app.username || app.companyName || '管理员' }}
</div>
</div>
<div class="list-right">
<div class="list-time">{{ formatDateTime(app.updateTime || app.createTime) }}</div>
<div class="list-actions">
<RoleTag :role="app.myRole || 'owner'" size="small" />
<span class="list-status-badge" :class="`status-${app.status}`">
{{ statusText(app.status, app.statusText) }}
</span>
<template v-for="entry in getAppEntries(app)" :key="entry.type">
<a
v-if="entry.available"
class="list-enter-link"
:class="{ 'primary-entry': entry.isPrimary }"
@click.stop="handleEntryClick(entry, app)"
>{{ entry.label }}</a>
</template>
<a-popconfirm
v-if="app.myRole === 'owner'"
:title="`确认删除应用「${app.productName}」?`"
ok-text="确认删除"
cancel-text="取消"
ok-type="danger"
@confirm="handleDeleteApp(app)"
>
<a-button type="text" danger size="small" @click.stop>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-popconfirm>
</div>
</div>
</div>
</div>
<!-- 应用详情抽屉放在条件渲染链之外 -->
<AppDetail v-model:open="detailOpen" :app="selectedApp" @deleted="handleDeletedFromDetail" @updated="handleUpdatedFromDetail" />
<!-- 小程序扫码弹窗 -->
<QrCodeModal
v-model:open="qrOpen"
:qrcode-url="qrApp?.qrcode"
:app-name="qrApp?.productName"
:title="qrApp ? (APP_TYPE_NAME[qrApp.appType ?? 10] || '小程序') + '二维码' : ''"
:tip="qrApp ? getScanTip(qrApp.appType ?? 20) : ''"
/>
<!-- 分页 -->
<div v-if="total > pageSize" class="pagination-wrap">
<a-pagination
:current="page"
:page-size="pageSize"
:total="total"
show-size-changer
:page-size-options="['10', '20', '50']"
@change="onPageChange"
@show-size-change="onPageSizeChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import {
AppstoreOutlined,
SearchOutlined,
UnorderedListOutlined,
DeleteOutlined,
PlusOutlined,
GlobalOutlined,
QrcodeOutlined,
DownloadOutlined,
SettingOutlined,
} from '@ant-design/icons-vue'
import { message, Modal } from 'ant-design-vue'
import { removeAppProduct, getMyAccessibleApps } from '@/api/app/appProduct'
import type { AppProduct } from '@/api/app/appProduct/model'
import { APP_TYPE, APP_TYPE_NAME } from '@/api/app/appProduct/model'
import AppDetail from '@/components/developer/AppDetail.vue'
import RoleTag from '@/components/developer/RoleTag.vue'
import QrCodeModal from '@/components/QrCodeModal.vue'
import { getAppEntries, executeEntry, getScanTip } from '@/utils/appEntry'
import type { AppEntry } from '@/utils/appEntry'
const props = defineProps<{
userId?: number | string | null
ownerName?: string
}>()
const emit = defineEmits<{
create: []
}>()
const viewMode = ref<'grid' | 'list'>('grid')
const page = ref(1)
const pageSize = ref(20)
const keywords = ref('')
// 所有应用合并列表
const allApps = ref<AppProduct[]>([])
const pending = ref(false)
const error = ref<string | null>(null)
// 统计
const total = computed(() => allApps.value.length)
const ownerCount = computed(() => allApps.value.filter(a => a.myRole === 'owner' || !a.myRole).length)
const memberCount = computed(() => allApps.value.filter(a => a.myRole && a.myRole !== 'owner').length)
// 关键词搜索过滤
const filteredApps = computed(() => {
let result = allApps.value
if (keywords.value.trim()) {
const kw = keywords.value.trim().toLowerCase()
result = result.filter(app =>
(app.productName || '').toLowerCase().includes(kw) ||
(app.productCode || '').toLowerCase().includes(kw)
)
}
// 前端分页
const start = (page.value - 1) * pageSize.value
return result.slice(start, start + pageSize.value)
})
// 详情抽屉
const detailOpen = ref(false)
const selectedApp = ref<AppProduct | null>(null)
function openDetail(app: AppProduct) {
selectedApp.value = app
detailOpen.value = true
}
// 加载所有可访问的应用(带角色信息)
async function fetchApps() {
pending.value = true
error.value = null
try {
const apps = await getMyAccessibleApps()
// 排序owner 在前,然后按更新时间倒序
apps.sort((a, b) => {
const aIsOwner = a.myRole === 'owner' ? 0 : 1
const bIsOwner = b.myRole === 'owner' ? 0 : 1
if (aIsOwner !== bIsOwner) return aIsOwner - bIsOwner
return new Date(b.updateTime || b.createTime || 0).getTime() - new Date(a.updateTime || a.createTime || 0).getTime()
})
allApps.value = apps
} catch (e: any) {
error.value = e.message || '加载失败'
} finally {
pending.value = false
}
}
// 初始化时加载
fetchApps()
function doSearch() {
page.value = 1
}
function onPageChange(nextPage: number) {
page.value = nextPage
}
function onPageSizeChange(_current: number, nextSize: number) {
pageSize.value = nextSize
page.value = 1
}
function handleCardClick(app: AppProduct) {
openDetail(app)
}
// 删除应用
const deletingAppId = ref<number | null>(null)
function handleDeletedFromDetail() {
selectedApp.value = null
detailOpen.value = false
refresh()
}
// 更新应用后同步本地数据
function handleUpdatedFromDetail(updatedApp: AppProduct) {
const index = allApps.value.findIndex((app) => app.productId === updatedApp.productId)
if (index !== -1) {
allApps.value[index] = { ...allApps.value[index], ...updatedApp }
}
if (selectedApp.value?.productId === updatedApp.productId) {
selectedApp.value = { ...selectedApp.value, ...updatedApp }
}
}
function handleDeleteApp(app: AppProduct) {
const name = app.productName || app.productCode || '该应用'
Modal.confirm({
title: '确认删除应用',
content: `确定要删除应用「${name}」吗?删除后所有配置、成员和版本记录将被永久清除,且无法恢复。`,
okText: '确认删除',
cancelText: '取消',
okType: 'danger',
async onOk() {
deletingAppId.value = app.productId ?? null
try {
await removeAppProduct(app.productId!)
message.success(`应用「${name}」已删除`)
// 如果详情抽屉打开的是当前应用,关闭它
if (selectedApp.value?.productId === app.productId) {
detailOpen.value = false
selectedApp.value = null
}
// 刷新列表
await refresh()
} catch (e: any) {
message.error(e.message || '删除失败')
} finally {
deletingAppId.value = null
}
},
})
}
// 外部刷新:重新查询应用列表
async function refresh() {
await fetchApps()
}
// 暴露 refresh 方法供父组件调用
defineExpose({ refresh })
// 入口处理
function handleEntryClick(entry: AppEntry, app: AppProduct) {
if (entry.type === 'scan-qr') {
qrApp.value = app
qrOpen.value = true
return
}
executeEntry(entry)
}
// 扫码弹窗
const qrOpen = ref(false)
const qrApp = ref<AppProduct | null>(null)
// 应用类型名称(使用统一枚举)
function appTypeName(type?: number, appType?: number): string {
return APP_TYPE_NAME[type ?? 10] ?? 'Web 应用'
}
function appTypeIcon(appType?: number): string {
const iconMap: Record<number, string> = {
[APP_TYPE.WEBSITE]: '🌐',
[APP_TYPE.WECHAT_MP]: '📱',
[APP_TYPE.DOUYIN_MP]: '🎵',
[APP_TYPE.BAIDU_MP]: '🔍',
[APP_TYPE.ALIPAY_MP]: '💎',
[APP_TYPE.ANDROID]: '🤖',
[APP_TYPE.IOS]: '🍎',
[APP_TYPE.MACOS]: '💻',
[APP_TYPE.WINDOWS]: '🪟',
[APP_TYPE.PLUGIN]: '🔌',
}
return iconMap[appType ?? 10] ?? '🌐'
}
function appTypeClass(appType?: number | string): string {
const numType = typeof appType === 'string' ? Number(appType) : (appType ?? 10)
const classMap: Record<number, string> = {
[APP_TYPE.WEBSITE]: 'type-10',
[APP_TYPE.WECHAT_MP]: 'type-20',
[APP_TYPE.DOUYIN_MP]: 'type-30',
[APP_TYPE.BAIDU_MP]: 'type-40',
[APP_TYPE.ALIPAY_MP]: 'type-50',
[APP_TYPE.ANDROID]: 'type-60',
[APP_TYPE.IOS]: 'type-70',
[APP_TYPE.MACOS]: 'type-80',
[APP_TYPE.WINDOWS]: 'type-90',
[APP_TYPE.PLUGIN]: 'type-100',
}
return classMap[numType] ?? 'type-10'
}
function statusText(status?: number, fallback?: string) {
if (fallback) return fallback
const map: Record<number, string> = {
0: '未开通',
1: '已启用',
2: '维护中',
3: '已关闭',
4: '欠费停机',
5: '违规关停',
}
return typeof status === 'number' && status in map ? map[status] : '未知'
}
function formatDateTime(dateStr?: string) {
if (!dateStr) return '-'
// 格式化为 "2026-03-28 10:46"
return dateStr.slice(0, 16).replace('T', ' ')
}
// 根据名称生成一致的背景色
const PALETTE = [
'#4e6ef2', '#f4a261', '#e76f51', '#2a9d8f',
'#e9c46a', '#457b9d', '#a8dadc', '#f77f00',
'#6d6875', '#b5838d',
]
function iconBgColor(name?: string) {
if (!name) return PALETTE[0]
let h = 0
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) & 0xffffffff
return PALETTE[Math.abs(h) % PALETTE.length]
}
</script>
<style scoped>
.apps-center {
padding: 0;
}
/* ===== 统计条 ===== */
.app-stats-bar {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 14px;
font-size: 13px;
color: rgba(0, 0, 0, 0.55);
}
.stats-total b {
color: rgba(0, 0, 0, 0.85);
font-size: 16px;
margin: 0 2px;
}
.stats-item {
display: inline-flex;
align-items: center;
gap: 5px;
}
.stats-dot {
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
}
.owner-dot { background: #4f46e5; }
.member-dot { background: #16a34a; }
/* ===== 工具栏 ===== */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
gap: 12px;
}
.search-input {
width: 300px;
}
.toolbar-right {
display: flex;
gap: 4px;
}
.view-btn {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
/* ===== 状态/空状态 ===== */
.state-wrap {
display: flex;
align-items: center;
justify-content: center;
min-height: 300px;
}
.empty-icon {
font-size: 48px;
margin-bottom: 4px;
}
/* ===== 网格布局 ===== */
.app-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
@media (max-width: 900px) {
.app-grid { grid-template-columns: 1fr; }
}
/* ===== 单张卡片 ===== */
.app-card {
position: relative;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 12px;
padding: 18px 20px 14px;
cursor: pointer;
transition: box-shadow 0.18s, border-color 0.18s;
display: flex;
flex-direction: column;
gap: 0;
}
.app-card:hover {
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.08);
border-color: #c5d8ff;
}
/* 右上角操作区 */
.card-top-actions {
position: absolute;
top: 14px;
right: 14px;
display: flex;
align-items: center;
gap: 4px;
z-index: 2;
}
/* 状态角标 */
.card-status-badge {
font-size: 12px;
padding: 2px 8px;
border-radius: 20px;
font-weight: 500;
line-height: 1.8;
}
/* 角色角标 */
.card-role-badge {
margin-right: 4px;
}
/* 卡片更多操作按钮 */
.card-more-btn {
color: #999;
}
.card-more-btn:hover { color: #333; background: #f5f5f5; }
.status-1 { background: #e6f7ee; color: #389e0d; }
.status-0 { background: #f5f5f5; color: #999; }
.status-2 { background: #fff7e6; color: #d46b08; }
.status-3 { background: #fff1f0; color: #cf1322; }
.status-4 { background: #fff2e8; color: #d4380d; }
.status-5 { background: #fff1f0; color: #cf1322; }
/* 卡片主体:图标 + 信息 */
.card-main {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 14px;
padding-right: 100px; /* 给右上角状态标签 + 更多按钮留空间 */
}
.app-icon-wrap {
flex-shrink: 0;
width: 60px;
height: 60px;
border-radius: 14px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.app-icon-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.app-icon-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 26px;
font-weight: 700;
color: #fff;
letter-spacing: -1px;
}
.card-info {
flex: 1;
min-width: 0;
padding-top: 4px;
}
.app-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 6px;
}
.app-meta {
font-size: 13px;
color: #666;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 2px;
}
.meta-label { color: #999; }
.meta-dot { color: #ccc; margin: 0 4px; }
.meta-link {
color: #1677ff;
cursor: pointer;
text-decoration: none;
}
.meta-link:hover { text-decoration: underline; }
.meta-value { color: #555; }
/* 分割线 */
.card-divider {
height: 1px;
background: #f0f0f0;
margin: 0 0 10px;
}
/* 最新动态 */
.card-activity {
display: flex;
align-items: center;
font-size: 13px;
color: #999;
gap: 2px;
flex-wrap: wrap;
}
.activity-label { color: #bbb; }
.activity-time { color: #666; }
.activity-dot { color: #ddd; margin: 0 4px; }
.activity-action { color: #666; }
.card-enter-btn {
margin-left: auto;
color: #1677ff;
font-size: 12px;
text-decoration: none;
white-space: nowrap;
padding: 2px 0;
}
.card-enter-btn:hover { text-decoration: underline; }
.card-enter-btn.primary-entry {
color: #1677ff;
font-weight: 500;
}
/* ===== 列表视图 ===== */
.app-list {
border: 1px solid #e8e8e8;
border-radius: 10px;
overflow: hidden;
margin-bottom: 20px;
background: #fff;
}
.app-list-item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 18px;
border-bottom: 1px solid #f5f5f5;
transition: background 0.15s;
cursor: pointer;
}
.app-list-item:last-child { border-bottom: none; }
.app-list-item:hover { background: #fafafa; }
.list-icon-wrap {
flex-shrink: 0;
width: 42px;
height: 42px;
border-radius: 10px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.list-icon-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.list-icon-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 700;
color: #fff;
}
.list-info {
flex: 1;
min-width: 0;
}
.list-name {
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.list-meta {
font-size: 12px;
color: #999;
margin-top: 2px;
}
.list-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
}
.list-time {
font-size: 12px;
color: #bbb;
}
.list-actions {
display: flex;
align-items: center;
gap: 8px;
}
.list-status-badge {
font-size: 11px;
padding: 1px 7px;
border-radius: 20px;
}
.list-enter-link {
font-size: 12px;
color: #1677ff;
text-decoration: none;
}
.list-enter-link:hover { text-decoration: underline; }
.list-enter-link.primary-entry {
font-weight: 500;
}
/* ===== 分页 ===== */
.pagination-wrap {
display: flex;
justify-content: center;
padding: 4px 0 0;
}
/* ===== 应用类型标签 ===== */
.app-type-tag {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 11px;
padding: 1px 7px;
border-radius: 20px;
font-weight: 500;
white-space: nowrap;
background: #f0f0f0;
color: #666;
}
.app-type-tag.type-10 { background: #e6f4ff; color: #0958d9; }
.app-type-tag.type-20 { background: #f6ffed; color: #389e0d; }
.app-type-tag.type-60 { background: #fff7e6; color: #d46b08; }
.app-type-tag.type-100 { background: #f9f0ff; color: #722ed1; }
</style>

View File

@@ -1,53 +0,0 @@
<script setup lang="ts">
/**
* 权限守卫组件
* 根据应用角色控制子内容的显示/隐藏
*
* 用法:
* <PermissionGuard :app-id="123" permission="canManageMembers">
* <a-button>邀请成员</a-button>
* </PermissionGuard>
*
* <PermissionGuard :app-id="123" :min-role="'admin'">
* 仅管理员和 Owner 可见
* </PermissionGuard>
*/
import type { AppRole } from '@/api/app/appUser/model'
import { useAppPermission } from '@/composables/useAppPermission'
const props = defineProps<{
/** 应用 ID */
appId?: number | null
/** 要检查的权限字段(与 canManageMembers 等对应) */
permission?: string
/** 最低角色要求(优先级高于 permission */
minRole?: AppRole
/** 权限不足时是否显示提示(而非隐藏) */
showTip?: boolean
}>()
const { hasPermission, hasRole, getNoPermissionTip, getAppPermission } = useAppPermission()
const hasAccess = computed(() => {
if (!props.appId) return false
if (props.minRole) return hasRole(props.appId, props.minRole)
if (props.permission) return hasPermission(props.appId, props.permission as 'canManageMembers')
return true
})
const tipText = computed(() => {
const perm = getAppPermission(props.appId)
return perm ? getNoPermissionTip(perm.role) : ''
})
</script>
<template>
<slot v-if="hasAccess" />
<slot v-else-if="showTip" name="fallback">
<a-tooltip :title="tipText">
<span class="text-gray-400 cursor-not-allowed">
<slot name="disabled-content" />
</span>
</a-tooltip>
</slot>
</template>

View File

@@ -1,26 +0,0 @@
<script setup lang="ts">
/**
* 角色标签组件
* 在应用卡片、成员列表等位置显示当前用户的角色
*/
import type { AppRole } from '@/api/app/appUser/model'
import { ROLE_LABEL, ROLE_COLOR } from '@/composables/useAppPermission'
const props = defineProps<{
role: AppRole
size?: 'small' | 'default'
}>()
const colorMap: Record<AppRole, string> = {
owner: '#faad14',
admin: '#1677ff',
developer: '#52c41a',
viewer: '#8c8c8c',
}
</script>
<template>
<a-tag :color="colorMap[props.role]" :size="props.size || 'default'">
{{ ROLE_LABEL[props.role] }}
</a-tag>
</template>

View File

@@ -1,276 +0,0 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { message, Modal } from 'ant-design-vue'
import {
listPendingInvites,
acceptInvite,
rejectInvite,
type AppUser
} from '@/api/app/appUser'
import { useRouter } from 'vue-router'
import { TeamOutlined, PlusOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'
const router = useRouter()
const invites = ref<AppUser[]>([])
const loading = ref(false)
// 待确认邀请数量
const pendingCount = computed(() => invites.value.length)
// 是否有待确认邀请
const hasPending = computed(() => pendingCount.value > 0)
// 加载邀请列表
async function loadInvites() {
try {
loading.value = true
invites.value = await listPendingInvites()
} catch (error) {
console.error('加载邀请列表失败:', error)
} finally {
loading.value = false
}
}
// 接受邀请
async function handleAccept(invite: AppUser) {
if (!invite.id) return
try {
await acceptInvite(invite.id)
message.success('已接受邀请,加入应用成功')
// 移除已处理的邀请
invites.value = invites.value.filter(i => i.id !== invite.id)
// 刷新页面或跳转到应用
setTimeout(() => {
router.push('/developer/apps')
}, 500)
} catch (error: any) {
message.error(error.message || '接受邀请失败')
}
}
// 拒绝邀请
async function handleReject(invite: AppUser) {
if (!invite.id) return
Modal.confirm({
title: '确认拒绝邀请?',
content: `拒绝后将无法加入应用「${invite.productName || '未知应用'}`,
okText: '确认拒绝',
okType: 'danger',
cancelText: '取消',
async onOk() {
try {
await rejectInvite(invite.id!)
message.success('已拒绝邀请')
invites.value = invites.value.filter(i => i.id !== invite.id)
} catch (error: any) {
message.error(error.message || '拒绝邀请失败')
}
}
})
}
// 查看全部邀请
function viewAllInvites() {
router.push('/console/invites')
}
onMounted(() => {
loadInvites()
})
</script>
<template>
<ClientOnly>
<a-dropdown v-if="hasPending" :trigger="['hover']" placement="bottomRight">
<a-badge :count="pendingCount" :offset="[-2, 2]">
<a-button type="text" class="invite-bell-btn">
<template #icon>
<TeamOutlined style="font-size: 18px; color: #fff" />
</template>
</a-button>
</a-badge>
<template #overlay>
<a-menu class="invite-dropdown-menu">
<a-menu-item-group title="应用邀请">
<a-menu-item v-for="invite in invites.slice(0, 3)" :key="invite.id" class="invite-menu-item">
<div class="invite-item-content">
<a-avatar
:src="invite.icon || '/logo.png'"
:size="32"
class="app-icon"
/>
<div class="invite-info">
<div class="app-name">{{ invite.productName || '未知应用' }}</div>
<div class="invite-meta">
<span class="role-tag" :class="invite.role">
{{ invite.role === 'owner' ? '所有者' :
invite.role === 'admin' ? '管理员' :
invite.role === 'developer' ? '开发者' : '访客' }}
</span>
</div>
</div>
<div class="invite-actions">
<a-button
type="primary"
size="small"
shape="circle"
class="action-btn accept"
@click.stop="handleAccept(invite)"
>
<CheckOutlined />
</a-button>
<a-button
size="small"
shape="circle"
class="action-btn reject"
@click.stop="handleReject(invite)"
>
<CloseOutlined />
</a-button>
</div>
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider v-if="invites.length > 0" />
<a-menu-item key="view-all" @click="viewAllInvites">
<span style="text-align: center; display: block;">
查看全部 {{ pendingCount }} 个邀请
</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</ClientOnly>
</template>
<style scoped>
.invite-bell-btn {
width: 36px;
height: 36px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.invite-bell-btn:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
</style>
<style>
/* 下拉菜单样式 */
.invite-dropdown-menu {
min-width: 280px !important;
max-width: 320px;
}
.invite-dropdown-menu .ant-dropdown-menu-item-group-title {
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
padding: 8px 16px;
border-bottom: 1px solid #f0f0f0;
}
.invite-menu-item {
padding: 12px 16px !important;
height: auto !important;
line-height: normal !important;
}
.invite-menu-item:hover {
background-color: #f5f5f5 !important;
}
.invite-item-content {
display: flex;
align-items: center;
gap: 12px;
}
.invite-info {
flex: 1;
min-width: 0;
}
.app-name {
font-size: 14px;
font-weight: 500;
color: #262626;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.invite-meta {
margin-top: 2px;
}
.role-tag {
font-size: 11px;
padding: 1px 6px;
border-radius: 4px;
font-weight: 500;
}
.role-tag.owner {
background: #fff2e8;
color: #fa541c;
}
.role-tag.admin {
background: #e6f7ff;
color: #1890ff;
}
.role-tag.developer {
background: #f6ffed;
color: #52c41a;
}
.role-tag.viewer {
background: #f9f0ff;
color: #722ed1;
}
.invite-actions {
display: flex;
gap: 6px;
}
.action-btn {
width: 24px;
height: 24px;
min-width: 24px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn.accept {
background: #52c41a;
border-color: #52c41a;
}
.action-btn.accept:hover {
background: #73d13d;
border-color: #73d13d;
}
.action-btn.reject {
background: #ff4d4f;
border-color: #ff4d4f;
color: #fff;
}
.action-btn.reject:hover {
background: #ff7875;
border-color: #ff7875;
}
.app-icon {
flex-shrink: 0;
}
</style>

View File

@@ -1,335 +0,0 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { message, Modal } from 'ant-design-vue'
import {
listPendingInvites,
acceptInvite,
rejectInvite,
type AppUser
} from '@/api/app/appUser'
import { useRouter } from 'vue-router'
import { TeamOutlined, ClockCircleOutlined } from '@ant-design/icons-vue'
const router = useRouter()
const invites = ref<AppUser[]>([])
const loading = ref(false)
// 待确认邀请数量
const pendingCount = computed(() => invites.value.length)
// 是否有待确认邀请
const hasPending = computed(() => pendingCount.value > 0)
// 加载邀请列表
async function loadInvites() {
try {
loading.value = true
const data = await listPendingInvites()
console.log('邀请列表数据:', data)
invites.value = data
} catch (error) {
console.error('加载邀请列表失败:', error)
} finally {
loading.value = false
}
}
// 接受邀请
async function handleAccept(invite: AppUser) {
if (!invite.id) return
try {
await acceptInvite(invite.id)
message.success('已接受邀请,加入应用成功')
// 移除已处理的邀请
invites.value = invites.value.filter(i => i.id !== invite.id)
// 刷新页面或跳转到应用
setTimeout(() => {
router.push('/developer/apps')
}, 500)
} catch (error: any) {
message.error(error.message || '接受邀请失败')
}
}
// 拒绝邀请
async function handleReject(invite: AppUser) {
if (!invite.id) return
Modal.confirm({
title: '确认拒绝邀请?',
content: `拒绝后将无法加入应用「${invite.productName || '未知应用'}`,
okText: '确认拒绝',
okType: 'danger',
cancelText: '取消',
async onOk() {
try {
await rejectInvite(invite.id!)
message.success('已拒绝邀请')
invites.value = invites.value.filter(i => i.id !== invite.id)
} catch (error: any) {
message.error(error.message || '拒绝邀请失败')
}
}
})
}
// 查看全部邀请
function viewAllInvites() {
router.push('/console/invites')
}
onMounted(() => {
loadInvites()
})
defineExpose({
loadInvites,
pendingCount,
hasPending
})
</script>
<template>
<div class="invite-notification">
<!-- 悬浮卡片 - 直接展示邀请列表 -->
<div v-if="hasPending" class="invite-float-card">
<div class="invite-card-header">
<div class="invite-title">
<TeamOutlined class="invite-icon" />
应用邀请
</div>
<div class="invite-count">{{ pendingCount }}</div>
</div>
<div class="invite-card-body">
<a-spin :spinning="loading">
<div
v-for="invite in invites.slice(0, 3)"
:key="invite.id"
class="invite-item"
>
<a-avatar
:src="invite.icon || '/logo.png'"
:size="40"
class="app-icon"
/>
<div class="invite-info">
<div class="app-name">{{ invite.productName || '未知应用' }}</div>
<div class="invite-meta">
<span class="role-tag" :class="invite.role">
{{ invite.role === 'owner' ? '所有者' :
invite.role === 'admin' ? '管理员' :
invite.role === 'developer' ? '开发者' : '访客' }}
</span>
<span class="inviter"> {{ invite.username || '未知用户' }} 邀请</span>
</div>
<div v-if="invite.inviteExpireTime" class="expire-time">
<ClockCircleOutlined />
有效期至{{ invite.inviteExpireTime }}
</div>
</div>
<div class="invite-actions">
<a-button
size="small"
@click="handleReject(invite)"
>
拒绝
</a-button>
<a-button
type="primary"
size="small"
@click="handleAccept(invite)"
>
接受
</a-button>
</div>
</div>
<div v-if="invites.length > 3" class="view-more" @click="viewAllInvites">
查看全部 {{ pendingCount }} 个邀请
</div>
</a-spin>
</div>
</div>
</div>
</template>
<style scoped>
.invite-notification {
position: relative;
}
/* 悬浮卡片 */
.invite-float-card {
position: fixed;
right: 24px;
bottom: 100px;
z-index: 100;
width: 420px;
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border: 1px solid rgba(0, 0, 0, 0.06);
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 卡片头部 */
.invite-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px 12px 0 0;
}
.invite-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 600;
color: #fff;
}
.invite-icon {
font-size: 20px;
}
.invite-count {
min-width: 24px;
height: 24px;
padding: 0 8px;
background: #ff4d4f;
color: #fff;
font-size: 13px;
font-weight: 600;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(255, 77, 79, 0.4);
}
/* 卡片内容 */
.invite-card-body {
padding: 8px 0;
max-height: 400px;
overflow-y: auto;
}
.invite-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
border-bottom: 1px solid #f5f5f5;
transition: background-color 0.2s;
}
.invite-item:hover {
background-color: #fafafa;
}
.invite-item:last-child {
border-bottom: none;
}
.app-icon {
flex-shrink: 0;
}
.invite-info {
flex: 1;
min-width: 0;
}
.app-name {
font-size: 15px;
font-weight: 600;
color: #262626;
margin-bottom: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.invite-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.role-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.role-tag.owner {
background: #fff2e8;
color: #fa541c;
}
.role-tag.admin {
background: #e6f7ff;
color: #1890ff;
}
.role-tag.developer {
background: #f6ffed;
color: #52c41a;
}
.role-tag.viewer {
background: #f9f0ff;
color: #722ed1;
}
.inviter {
font-size: 12px;
color: #8c8c8c;
}
.expire-time {
font-size: 12px;
color: #faad14;
display: flex;
align-items: center;
gap: 4px;
}
.invite-actions {
display: flex;
gap: 8px;
flex-shrink: 0;
}
.view-more {
text-align: center;
padding: 12px 20px;
color: #667eea;
font-size: 14px;
cursor: pointer;
border-top: 1px solid #f5f5f5;
transition: background-color 0.2s;
}
.view-more:hover {
background-color: #f5f5f5;
color: #764ba2;
}
</style>

View File

@@ -10,28 +10,83 @@ export type NavItem = {
export const mainNav: NavItem[] = [ export const mainNav: NavItem[] = [
{key: 'home', label: '首页', to: '/'}, {key: 'home', label: '首页', to: '/'},
{ {
key: 'ai', key: 'news',
label: 'AI智能体', label: '政策要闻',
to: '/ai-agent', to: '/news',
badge: 'HOT',
children: [ children: [
{key: 'ai-agent', label: '🤖 AI 智能体', to: '/ai-agent'}, {key: 'news-central', label: '党中央国务院', to: '/news?type=central'},
{key: 'openclaw', label: '🦞 OpenClaw 生态', to: '/openclaw'}, {key: 'news-region', label: '自治区党委政府', to: '/news?type=region'},
{key: 'platform-api', label: '🔑 模型管理平台', to: '', href: 'https://platform.websoft.top'}, {key: 'news-department', label: '其他厅委办', to: '/news?type=department'},
{key: 'news-latest', label: '最新发布', to: '/news?type=latest'},
] ]
}, },
{ {
key: 'products', key: 'consultation',
label: '产品', label: '决策咨询',
to: '/products', to: '/consultation',
children: [ children: [
{key: 'website', label: '🌐 云·企业官网', to: '/website'}, {key: 'consult-city', label: '市县决策', to: '/consultation?type=city'},
{key: 'miniapp', label: '📱 小程序开发', to: '/miniapp'}, {key: 'consult-frontier', label: '前沿观察', to: '/consultation?type=frontier'},
{key: 'shop', label: '🛒 小程序商城', to: '/shop'}, {key: 'consult-industry', label: '行业资讯', to: '/consultation?type=industry'},
{key: 'oa', label: '🏠 葳管家', to: '/oa'}, {key: 'consult-enterprise', label: '企业动态', to: '/consultation?type=enterprise'},
{key: 'consult-research', label: '研究热点', to: '/consultation?type=research'},
{key: 'consult-academic', label: '学术活动', to: '/consultation?type=academic'},
{key: 'consult-other', label: '其他汇编', to: '/consultation?type=other'},
]
},
{
key: 'reference',
label: '决策参考',
to: '/reference',
children: [
{key: 'ref-policy', label: '政策原文', to: '/reference?type=policy'},
{key: 'ref-analysis', label: '深度解读', to: '/reference?type=analysis'},
{key: 'ref-research', label: '研究成果', to: '/reference?type=research'},
{key: 'ref-special', label: '专题研究', to: '/reference?type=special'},
{key: 'ref-asean', label: '东盟研究', to: '/reference?type=asean'},
{key: 'ref-data', label: '数据服务', to: '/reference?type=data', badge: 'VIP'},
]
},
{
key: 'expert',
label: '专家资讯',
to: '/expert',
children: [
{key: 'expert-view', label: '专家视点', to: '/expert?type=view'},
{key: 'expert-dynamic', label: '专家动态', to: '/expert?type=dynamic'},
{key: 'expert-apply', label: '专家申请', to: '/expert/apply'},
]
},
{
key: 'thinktank',
label: '智库观察',
to: '/think-tank',
children: [
{key: 'thinktank-intro', label: '智库介绍', to: '/think-tank?type=intro'},
{key: 'thinktank-view', label: '智库视角', to: '/think-tank?type=view'},
]
},
{key: 'suggestions', label: '建言献策', to: '/suggestions'},
{
key: 'membership',
label: '会员服务',
to: '/membership',
children: [
{key: 'member-consult', label: '企业咨询', to: '/membership?type=consult'},
{key: 'member-service', label: '专项服务', to: '/membership?type=service'},
]
},
{key: 'hanmo', label: '翰墨文谈', to: '/hanmo'},
{
key: 'about',
label: '关于我们',
to: '/about',
children: [
{key: 'about-intro', label: '学会简介', to: '/about'},
{key: 'about-org', label: '组织机构', to: '/about/organization'},
{key: 'about-charter', label: '学会章程', to: '/about/charter'},
{key: 'about-consult', label: '咨询服务', to: '/about/consultation'},
{key: 'about-join', label: '加入我们', to: '/about/join'},
] ]
}, },
{key: 'platform', label: '平台能力', to: '/platform'},
{key: 'market', label: '模板市场', to: '/market'},
{key: 'developer', label: '开发者中心', to: '/developer-center'}
] ]

View File

@@ -1,515 +0,0 @@
<template>
<a-layout class="layout-shell">
<!-- 左侧固定侧边栏 -->
<a-layout-sider
class="sider"
:width="220"
:collapsed-width="64"
breakpoint="lg"
theme="dark"
:trigger="null"
collapsible
v-model:collapsed="collapsed"
>
<!-- Logo 区域 -->
<div class="sider-logo" @click="navigateTo('/console')">
<img src="/logo.png" alt="logo" class="logo-img" />
<transition name="logo-text">
<span v-if="!collapsed" class="logo-name">控制台</span>
</transition>
</div>
<!-- 菜单 -->
<a-menu
mode="inline"
theme="dark"
:selected-keys="selectedKeys"
:open-keys="collapsed ? [] : openKeys"
:inline-collapsed="collapsed"
@open-change="onOpenChange"
@click="onMenuClick"
>
<template v-for="entry in menu" :key="isGroup(entry) ? entry.key : entry.to">
<!-- 分组有子菜单 -->
<a-sub-menu v-if="isGroup(entry)" :key="entry.key">
<template v-if="entry.icon" #icon>
<component :is="entry.icon" />
</template>
<template #title>{{ entry.label }}</template>
<a-menu-item v-for="item in entry.children" :key="item.to">
<template v-if="item.icon" #icon>
<component :is="item.icon" />
</template>
{{ item.label }}
</a-menu-item>
</a-sub-menu>
<!-- 单条链接 -->
<a-menu-item v-else :key="entry.to">
<template v-if="entry.icon" #icon>
<component :is="entry.icon" />
</template>
{{ entry.label }}
</a-menu-item>
</template>
</a-menu>
<!-- 折叠按钮 -->
<div
class="sider-collapse-trigger"
role="button"
tabindex="0"
:aria-label="collapsed ? '展开菜单' : '收起菜单'"
@click="toggleCollapsed"
@keydown.enter.prevent="toggleCollapsed"
@keydown.space.prevent="toggleCollapsed"
>
<RightOutlined v-if="collapsed" style="font-size: 10px" />
<LeftOutlined v-else style="font-size: 10px" />
</div>
</a-layout-sider>
<!-- 右侧主区域 -->
<a-layout class="main-layout" :class="{ 'main-layout--collapsed': collapsed }">
<!-- 顶部 Header -->
<a-layout-header class="main-header">
<div class="header-inner">
<!-- 左侧面包屑或页面标题可扩展 -->
<div class="header-left">
<span class="page-title">{{ currentPageTitle }}</span>
</div>
<!-- 右侧用户区 -->
<div class="header-right">
<!-- 消息通知铃铛 -->
<NotificationBell />
<a-dropdown placement="bottomRight" :trigger="['click']">
<div class="user-trigger">
<a-avatar :size="28" :src="user?.avatar || user?.avatarUrl">
<template #icon>
<UserOutlined />
</template>
</a-avatar>
<span class="user-name">{{ userDisplayName }}</span>
<DownOutlined style="font-size: 10px; opacity: 0.6; margin-left: 2px" />
</div>
<template #overlay>
<a-menu @click="onUserMenuClick" class="user-dropdown-menu">
<a-menu-item key="account-info">
<UserOutlined style="margin-right: 8px" />
账户信息
</a-menu-item>
<a-menu-item key="orders">
<ShoppingOutlined style="margin-right: 8px" />
我的订单
</a-menu-item>
<a-menu-item key="account-kyc">
<IdcardOutlined style="margin-right: 8px" />
实名认证
</a-menu-item>
<template v-if="isDeveloper">
<a-menu-divider />
<a-menu-item key="developer">
<CodeOutlined style="margin-right: 8px" />
开发者中心
</a-menu-item>
</template>
<template v-if="isAdmin">
<a-menu-divider />
<a-menu-item key="admin">
<SettingOutlined style="margin-right: 8px" />
平台管理
</a-menu-item>
</template>
<a-menu-divider />
<a-menu-item key="logout" class="logout-item">
<LogoutOutlined style="margin-right: 8px" />
退出登录
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</a-layout-header>
<!-- 内容区 -->
<a-layout-content class="main-content">
<a-spin v-if="!ready" size="large" tip="加载中..." class="spin" />
<template v-else>
<slot />
</template>
</a-layout-content>
</a-layout>
<!-- 邀请通知组件 -->
<ClientOnly>
<InviteNotification ref="inviteNotificationRef" />
</ClientOnly>
</a-layout>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import {
CodeOutlined,
DownOutlined,
IdcardOutlined,
LeftOutlined,
LogoutOutlined,
RightOutlined,
SettingOutlined,
ShoppingOutlined,
UserOutlined,
} from '@ant-design/icons-vue'
import { consoleNav, type ConsoleNavEntry, type ConsoleNavGroup } from '@/config/console-nav'
import { getUserInfo } from '@/api/layout'
import { getToken, removeToken } from '@/utils/token-util'
import { clearAuthz, setAuthzFromUser } from '@/utils/permission'
import type { User } from '@/api/system/user/model'
const route = useRoute()
const collapsed = ref(false)
const menu = computed(() => consoleNav)
const user = ref<User | null>(null)
const userDisplayName = computed(() => {
const u = user.value
return u?.nickname?.trim() || u?.username?.trim() || u?.phone?.trim() || u?.mobile?.trim() || '当前用户'
})
const isAdmin = computed(() => !!(user.value as any)?.isAdmin)
const isDeveloper = computed(() => (user.value as any)?.type === 2)
function isGroup(entry: ConsoleNavEntry): entry is ConsoleNavGroup {
return 'children' in entry
}
// 当前页面标题
const currentPageTitle = computed(() => {
const path = route.path
for (const entry of menu.value) {
if (isGroup(entry)) {
const hit = entry.children.find((c) => path === c.to || path.startsWith(c.to + '/'))
if (hit) return hit.label
} else {
if (path === entry.to || path.startsWith(entry.to + '/')) return entry.label
}
}
return '控制台'
})
// 选中 key
const selectedKeys = computed(() => {
const path = route.path
for (const entry of menu.value) {
if (isGroup(entry)) {
const hit = entry.children.find((c) => path === c.to || path.startsWith(c.to + '/'))
if (hit) return [hit.to]
} else {
if (path === entry.to) return [entry.to]
}
}
return []
})
// 展开 key自动展开当前路由所在的分组
const openKeys = ref<string[]>([])
function syncOpenKeys() {
const path = route.path
const hit = menu.value.find(
(entry) => isGroup(entry) && entry.children.some((c) => path === c.to || path.startsWith(c.to + '/'))
)
openKeys.value = hit ? [hit.key] : []
}
function onOpenChange(keys: string[]) {
openKeys.value = Array.isArray(keys) ? keys.slice(-1) : []
}
function onMenuClick(info: { key: string }) {
navigateTo(String(info.key))
}
function toggleCollapsed() {
collapsed.value = !collapsed.value
}
function logout() {
removeToken()
try {
localStorage.removeItem('TenantId')
localStorage.removeItem('UserId')
} catch { /* ignore */ }
clearAuthz()
navigateTo('/')
}
function onUserMenuClick(info: { key: string }) {
const key = String(info.key)
if (key === 'home') navigateTo('/')
if (key === 'account-info') navigateTo('/console/account')
if (key === 'orders') navigateTo('/console/orders')
if (key === 'account-members') navigateTo('/console/account/members')
if (key === 'account-security') navigateTo('/console/account/security')
if (key === 'account-kyc') navigateTo('/console/account/kyc')
if (key === 'developer') navigateTo('/developer')
if (key === 'admin') navigateTo('/admin')
if (key === 'logout') logout()
}
watch(() => route.path, syncOpenKeys, { immediate: true })
const ready = ref(false)
const inviteNotificationRef = ref()
onMounted(async () => {
const token = getToken()
if (!token) {
clearAuthz()
message.error('请先登录')
await navigateTo('/login')
ready.value = true
return
}
try {
const me = await getUserInfo()
user.value = me
setAuthzFromUser(me)
} catch {
user.value = null
clearAuthz()
}
syncOpenKeys()
ready.value = true
})
</script>
<style scoped>
/* ===== 整体 shell ===== */
.layout-shell {
min-height: 100vh;
background: #f5f7fa;
}
/* ===== 左侧边栏 ===== */
.sider {
position: fixed !important;
top: 0;
left: 0;
height: 100vh;
z-index: 100;
overflow: hidden;
background: #111827 !important;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
/* 覆盖 Ant Design sider 背景 */
}
:deep(.ant-layout-sider-children) {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
:deep(.ant-menu-dark) {
background: transparent;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
:deep(.ant-menu-dark .ant-menu-item-selected) {
background-color: rgba(59, 130, 246, 0.25) !important;
}
:deep(.ant-menu-dark .ant-menu-item:hover),
:deep(.ant-menu-dark .ant-menu-submenu-title:hover) {
background-color: rgba(255, 255, 255, 0.06) !important;
}
/* Logo 区域 */
.sider-logo {
height: 56px;
display: flex;
align-items: center;
gap: 10px;
padding: 0 20px;
cursor: pointer;
flex-shrink: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
overflow: hidden;
white-space: nowrap;
}
.sider-logo:hover .logo-img,
.sider-logo:hover .logo-name {
opacity: 0.8;
}
.logo-img {
height: 16px;
width: auto;
flex-shrink: 0;
display: block;
}
.logo-name {
font-size: 17px;
font-weight: 700;
letter-spacing: 0.04em;
background: linear-gradient(135deg, #ffffff 0%, #a5c8ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.logo-text-enter-active,
.logo-text-leave-active {
transition: opacity 0.2s, width 0.2s;
}
.logo-text-enter-from,
.logo-text-leave-to {
opacity: 0;
width: 0;
}
/* 折叠按钮 */
.sider-collapse-trigger {
position: absolute;
right: -14px;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #1f2937;
border: 1px solid rgba(255, 255, 255, 0.1);
border-left: 0;
border-radius: 0 9999px 9999px 0;
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.2);
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
z-index: 101;
}
.sider-collapse-trigger:hover {
color: #fff;
background: #374151;
}
/* ===== 右侧主区域 ===== */
.main-layout {
margin-left: 220px;
transition: margin-left 0.2s ease;
min-height: 100vh;
display: flex;
flex-direction: column;
background: #f5f7fa;
}
/* 当 sider 折叠时,右侧内容区跟着收缩 */
.main-layout--collapsed {
margin-left: 64px;
}
/* 顶部 Header */
.main-header {
position: sticky;
top: 0;
z-index: 99;
height: 56px;
line-height: 56px;
padding: 0;
background: #fff !important;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
flex-shrink: 0;
}
.header-inner {
height: 100%;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.page-title {
font-size: 15px;
font-weight: 600;
color: #1f2937;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.user-trigger {
height: 36px;
display: flex;
align-items: center;
gap: 6px;
padding: 0 10px;
border-radius: 9999px;
cursor: pointer;
color: #374151;
transition: background 0.2s;
}
.user-trigger:hover {
background: rgba(0, 0, 0, 0.04);
}
.user-name {
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #374151;
}
/* 头像下拉菜单样式 */
.user-dropdown-menu {
min-width: 180px;
}
.user-dropdown-menu .ant-menu-item,
.user-dropdown-menu .ant-menu-submenu-title {
padding: 8px 16px;
}
.logout-item {
color: #ff4d4f;
}
.logout-item:hover {
background-color: #fff1f0;
}
/* 内容区 */
.main-content {
flex: 1;
padding: 20px 24px;
min-height: calc(100vh - 56px);
}
.spin {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
</style>

View File

@@ -1,666 +0,0 @@
<template>
<a-layout class="min-h-screen layout-shell">
<a-layout class="w-full">
<SiteHeader />
<a-layout class="body w-full mx-auto max-w-screen-xl">
<!-- 未授权提示页面 -->
<div v-if="!accessible && ready" class="w-full">
<!-- 场景 1已登录但既不是平台开发者也没有被邀请到任何应用 -->
<div v-if="accessChecked && !hasJoinedApps" class="apply-developer-page">
<div class="apply-card">
<div class="icon-wrapper">
<span class="icon">🛠</span>
</div>
<h1 class="title">加入开发者中心</h1>
<p class="subtitle">
你还没有加入任何应用请联系应用管理员发送邀请链接或申请平台开发者资质以创建自己的应用
</p>
<div class="features">
<div class="feature-item">
<span class="feature-icon">🔑</span>
<span>获取 API Key调用 200+ REST 接口</span>
</div>
<div class="feature-item">
<span class="feature-icon">📦</span>
<span>创建和管理您的应用</span>
</div>
<div class="feature-item">
<span class="feature-icon">💻</span>
<span>申请源码访问权限</span>
</div>
<div class="feature-item">
<span class="feature-icon">🤖</span>
<span>接入 AI 智能体 API</span>
</div>
</div>
<div class="actions">
<a-button
type="primary"
size="large"
class="apply-btn"
:loading="applying"
@click="handleApply"
>
申请平台开发者资质
</a-button>
<a-button
size="large"
class="back-btn"
@click="navigateTo('/console')"
>
返回用户中心
</a-button>
</div>
<div class="tips">
<p>💡 收到应用邀请后刷新此页面即可进入开发者中心</p>
<p>如有疑问请联系客服或发送邮件至 support@websopy.com</p>
</div>
</div>
</div>
</div>
<template v-else>
<!-- 侧边栏 -->
<a-layout-sider
class="sider"
:width="240"
breakpoint="lg"
theme="light"
:trigger="null"
collapsible
v-model:collapsed="collapsed"
>
<!-- 品牌区 -->
<div class="sider-brand" :class="{ collapsed }">
<div class="sider-brand-icon">
<span class="text-lg">🛠</span>
</div>
<div v-if="!collapsed" class="sider-brand-text">
<div class="sider-title">开发者中心</div>
<div class="sider-subtitle">Developer Console</div>
</div>
</div>
<!-- 用户信息非折叠状态 -->
<div v-if="!collapsed && user" class="sider-user">
<a-avatar :size="32" class="sider-user-avatar">
{{ userDisplayName.charAt(0).toUpperCase() }}
</a-avatar>
<div class="sider-user-info">
<div class="sider-user-name">{{ userDisplayName }}</div>
<a-tag :color="isPlatformDev ? 'green' : 'blue'" class="sider-user-role">
{{ isPlatformDev ? '平台开发者' : '协作成员' }}
</a-tag>
</div>
</div>
<!-- 分组导航菜单 -->
<div class="sider-nav">
<template v-for="(group, gIdx) in navGroups" :key="gIdx">
<!-- 分组标题 -->
<div v-if="group.name" class="nav-group-label">{{ group.name }}</div>
<!-- 菜单项 -->
<div
v-for="item in group.items"
:key="item.key"
class="nav-item"
:class="{ active: isActive(item.to), collapsed }"
@click="navigateTo(item.to)"
>
<span class="nav-item-icon">{{ item.icon }}</span>
<span v-if="!collapsed" class="nav-item-label">{{ item.label }}</span>
<a-tag
v-if="!collapsed && item.badge"
:color="item.badge === 'NEW' ? 'green' : 'orange'"
class="nav-item-badge"
>{{ item.badge }}</a-tag>
</div>
</template>
</div>
<!-- 底部返回入口 -->
<div v-if="!collapsed" class="sider-footer">
<div class="sider-footer-link" @click="navigateTo('/developer-center')">
<span>📄</span> 开发文档
</div>
<div class="sider-footer-link" @click="navigateTo('/')">
<span>🏠</span> 返回首页
</div>
</div>
<!-- 折叠触发器 -->
<div
class="sider-collapse-trigger"
role="button"
tabindex="0"
:aria-label="collapsed ? '展开菜单' : '收起菜单'"
@click="toggleCollapsed"
@keydown.enter.prevent="toggleCollapsed"
@keydown.space.prevent="toggleCollapsed"
>
<RightOutlined :style="{ fontSize: '10px' }" v-if="collapsed" />
<LeftOutlined :style="{ fontSize: '10px' }" v-else />
</div>
</a-layout-sider>
<!-- 主内容区 -->
<a-layout class="main">
<a-layout-content class="content">
<a-spin v-if="!ready && isClient" size="large" tip="加载中..." class="spin" />
<NuxtPage />
</a-layout-content>
</a-layout>
</template>
</a-layout>
<SiteFooter />
</a-layout>
<!-- 邀请通知组件 -->
<ClientOnly>
<InviteNotification />
</ClientOnly>
</a-layout>
</template>
<script setup lang="ts">
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { developerNav } from '@/config/developer-nav'
import { getUserInfo } from '@/api/layout'
import { getToken } from '@/utils/token-util'
import { clearAuthz, setAuthzFromUser } from '@/utils/permission'
import type { User } from '@/api/system/user/model'
import { useAppPermission } from '@/composables/useAppPermission'
import SiteHeader from '~/components/SiteHeader.vue'
import SiteFooter from '~/components/SiteFooter.vue'
import InviteNotification from '~/components/invite/InviteNotification.vue'
const route = useRoute()
const collapsed = ref(false)
const user = ref<User | null>(null)
const isClient = ref(false)
onMounted(() => {
isClient.value = true
})
const userDisplayName = computed(() => {
const u = user.value
const nickname = u?.nickname?.trim()
const username = u?.username?.trim()
const phone = u?.phone?.trim()
const mobile = u?.mobile?.trim()
if (nickname) return nickname
if (username) return username
if (phone) return phone
if (mobile) return mobile
return '开发者'
})
// 按分组整理导航
const navGroups = computed(() => {
const groups: { name: string; items: typeof developerNav }[] = []
const groupMap = new Map<string, typeof developerNav[0][]>()
for (const item of developerNav) {
const g = item.group ?? ''
if (!groupMap.has(g)) groupMap.set(g, [])
groupMap.get(g)!.push(item)
}
for (const [name, items] of groupMap) {
groups.push({ name, items })
}
return groups
})
function isActive(to: string) {
if (to === '/developer') return route.path === '/developer'
return route.path === to || route.path.startsWith(to + '/')
}
function toggleCollapsed() {
collapsed.value = !collapsed.value
}
// ============ 权限控制 ============
const ready = ref(false)
const accessible = ref(false)
const accessChecked = ref(false)
const hasJoinedApps = ref(false)
const isPlatformDev = ref(false)
const applying = ref(false)
const { checkDeveloperAccess, loadAppPermissions, isPlatformDeveloper, setCurrentUser } = useAppPermission()
onMounted(async () => {
const token = getToken()
if (!token) {
clearAuthz()
message.warning('请先登录后访问开发者中心')
await navigateTo(`/login?from=${encodeURIComponent(route.path)}`)
ready.value = true
return
}
try {
const me = await getUserInfo()
user.value = me
setAuthzFromUser(me)
// 保存到共享状态,避免其他组件重复请求
setCurrentUser(me)
// 检查开发者访问权限type===2 或有参与的应用都可以进入
const result = await checkDeveloperAccess()
accessChecked.value = true
accessible.value = result.accessible
hasJoinedApps.value = result.hasJoinedApps
isPlatformDev.value = result.isPlatformDeveloper
// 如果有权限,加载完整的应用权限列表
if (result.accessible) {
await loadAppPermissions()
}
}
catch {
// check-access 接口失败时降级type=2 仍然放行
const userType = localStorage.getItem('UserType')
if (userType === '2') {
accessible.value = true
accessChecked.value = true
isPlatformDev.value = true
}
else {
accessible.value = false
accessChecked.value = true
}
}
ready.value = true
})
// 申请开发者资质
async function handleApply() {
applying.value = true
try {
message.info('开发者资质申请功能即将上线,敬请期待!')
}
finally {
applying.value = false
}
}
</script>
<style scoped>
.layout-shell {
background: #f0f2f5;
}
/* 侧边栏 */
.sider {
border-radius: 14px;
overflow: visible;
position: relative;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.sider-brand {
display: flex;
align-items: center;
gap: 10px;
padding: 18px 16px 12px;
border-bottom: 1px solid #f0f0f0;
}
.sider-brand.collapsed {
justify-content: center;
padding: 18px 8px 12px;
}
.sider-brand-icon {
flex-shrink: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
font-size: 18px;
}
.sider-brand-text {
flex: 1;
min-width: 0;
}
.sider-title {
color: rgba(0, 0, 0, 0.88);
font-size: 14px;
font-weight: 600;
line-height: 1.3;
}
.sider-subtitle {
color: rgba(0, 0, 0, 0.35);
font-size: 11px;
line-height: 1.3;
margin-top: 1px;
}
/* 用户信息 */
.sider-user {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: linear-gradient(135deg, #f5f0ff 0%, #eff6ff 100%);
margin: 10px 10px 0;
border-radius: 10px;
}
.sider-user-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
flex-shrink: 0;
font-size: 14px;
font-weight: 600;
}
.sider-user-info {
flex: 1;
min-width: 0;
}
.sider-user-name {
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.88);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sider-user-role {
font-size: 10px;
margin: 0;
line-height: 1.5;
}
/* 导航 */
.sider-nav {
padding: 8px 8px 0;
flex: 1;
}
.nav-group-label {
font-size: 11px;
font-weight: 600;
color: rgba(0, 0, 0, 0.35);
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 14px 10px 5px;
user-select: none;
}
.nav-item {
display: flex;
align-items: center;
gap: 9px;
padding: 9px 10px;
border-radius: 9px;
cursor: pointer;
transition: all 0.15s ease;
color: rgba(0, 0, 0, 0.72);
font-size: 14px;
margin-bottom: 2px;
user-select: none;
}
.nav-item:hover {
background: rgba(99, 102, 241, 0.07);
color: #5145cd;
}
.nav-item.active {
background: rgba(99, 102, 241, 0.12);
color: #4f46e5;
font-weight: 500;
}
.nav-item.collapsed {
justify-content: center;
padding: 9px;
}
.nav-item-icon {
font-size: 16px;
flex-shrink: 0;
width: 20px;
text-align: center;
line-height: 1;
}
.nav-item-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-item-badge {
font-size: 10px;
padding: 0 5px;
line-height: 16px;
height: 16px;
flex-shrink: 0;
}
/* 底部快捷入口 */
.sider-footer {
padding: 12px 10px 14px;
border-top: 1px solid #f0f0f0;
margin-top: 8px;
}
.sider-footer-link {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 10px;
border-radius: 8px;
cursor: pointer;
color: rgba(0, 0, 0, 0.45);
font-size: 13px;
transition: all 0.15s;
margin-bottom: 2px;
}
.sider-footer-link:hover {
background: #f5f5f5;
color: rgba(0, 0, 0, 0.72);
}
/* 折叠触发器 */
.sider-collapse-trigger {
position: absolute;
top: 50%;
right: -16px;
transform: translateY(-50%);
width: 16px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-left: 0;
border-radius: 0 9999px 9999px 0;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.06);
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
z-index: 2;
transition: all 0.15s;
}
.sider-collapse-trigger:hover {
background: #f5f5f5;
color: #4f46e5;
}
/* 内容区 */
.body {
margin: 16px auto;
padding: 0 16px;
}
.main {
margin-left: 16px;
background: transparent;
}
.content {
min-height: 520px;
background: #fff;
border-radius: 14px;
padding: 0;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.forbidden-wrap {
display: flex;
align-items: center;
justify-content: center;
min-height: 480px;
}
.spin {
display: flex;
align-items: center;
justify-content: center;
min-height: 480px;
}
/* 申请开发者页面 */
.apply-developer-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 600px;
padding: 40px 20px;
}
.apply-card {
max-width: 560px;
width: 100%;
background: #fff;
border-radius: 16px;
padding: 48px 40px;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.icon-wrapper {
width: 80px;
height: 80px;
margin: 0 auto 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.icon {
font-size: 40px;
}
.title {
font-size: 24px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 12px;
}
.subtitle {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 32px;
line-height: 1.6;
}
.features {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 32px;
text-align: left;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: #f8f9fa;
border-radius: 10px;
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.feature-icon {
font-size: 18px;
}
.actions {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 24px;
}
.apply-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
font-weight: 500;
}
.apply-btn:hover {
opacity: 0.9;
}
.back-btn {
color: rgba(0, 0, 0, 0.65);
}
.tips {
font-size: 12px;
color: rgba(0, 0, 0, 0.35);
line-height: 1.8;
}
@media (max-width: 640px) {
.apply-card {
padding: 32px 24px;
}
.features {
grid-template-columns: 1fr;
}
.actions {
flex-direction: column;
}
}
</style>

View File

@@ -1,246 +0,0 @@
<template>
<a-layout class="min-h-screen layout-shell">
<a-layout class="w-full px-4 py-4">
<ConsoleHeader
product-label="办公协同OA"
default-jump-key="oa"
:user="user"
:user-display-name="userDisplayName"
:user-menu-items="userMenuItems"
@user-menu-click="onUserMenuClick"
/>
<a-layout class="body">
<a-layout-sider
class="sider"
:width="240"
breakpoint="lg"
theme="light"
:trigger="null"
collapsible
v-model:collapsed="collapsed"
>
<div class="sider-brand" :class="{ collapsed }">
<a-avatar shape="square" :size="28" src="https://oss.wsdns.cn/20250215/457a343dba204d019281d8a23556c4b1.png">
<template #icon>
<AppstoreOutlined />
</template>
</a-avatar>
<div v-if="!collapsed" class="sider-title">
办公协同OA
</div>
</div>
<a-menu
mode="inline"
theme="light"
:selected-keys="selectedKeys"
:inline-collapsed="collapsed"
@click="onMenuClick"
>
<a-menu-item v-for="item in menu" :key="item.to">
{{ item.label }}
</a-menu-item>
</a-menu>
<div
class="sider-collapse-trigger"
role="button"
tabindex="0"
:aria-label="collapsed ? '展开菜单' : '收起菜单'"
@click="toggleCollapsed"
@keydown.enter.prevent="toggleCollapsed"
@keydown.space.prevent="toggleCollapsed"
>
<RightOutlined :style="{fontSize: '10px', paddingRight: '4px'}" v-if="collapsed" />
<LeftOutlined :style="{fontSize: '10px', paddingRight: '4px'}" v-else />
</div>
</a-layout-sider>
<a-layout class="main">
<a-layout-content class="content">
<a-spin v-if="!ready" size="large" tip="加载中..." class="spin" />
<template v-else>
<slot />
</template>
</a-layout-content>
</a-layout>
</a-layout>
</a-layout>
</a-layout>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { AppstoreOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
import { oaNav } from '@/config/oa-nav'
import { getUserInfo } from '@/api/layout'
import { getToken, removeToken } from '@/utils/token-util'
import { clearAuthz, setAuthzFromUser } from '@/utils/permission'
import type { User } from '@/api/system/user/model'
const route = useRoute()
const collapsed = ref(false)
const menu = computed(() => oaNav)
const user = ref<User | null>(null)
const userDisplayName = computed(() => {
const u = user.value
const nickname = u?.nickname?.trim()
const username = u?.username?.trim()
const phone = u?.phone?.trim()
const mobile = u?.mobile?.trim()
if (nickname) return nickname
if (username) return username
if (phone) return phone
if (mobile) return mobile
return '当前用户'
})
const userMenuItems = [
{ key: 'home', label: '返回首页' },
{ key: 'logout', label: '退出登录' },
]
const selectedKeys = computed(() => {
const match = menu.value.find((item) => route.path === item.to)
if (match) return [match.to]
if (route.path.startsWith('/oa')) return ['/oa']
return []
})
function onMenuClick(info: { key: string }) {
navigateTo(String(info.key))
}
function toggleCollapsed() {
collapsed.value = !collapsed.value
}
function logout() {
removeToken()
try {
localStorage.removeItem('TenantId')
localStorage.removeItem('UserId')
} catch {
// ignore
}
clearAuthz()
navigateTo('/')
}
function onUserMenuClick(key: string) {
if (key === 'home') navigateTo('/')
if (key === 'logout') logout()
}
const ready = ref(false)
onMounted(async () => {
const token = getToken()
if (!token) {
clearAuthz()
message.error('请先登录')
await navigateTo('/login')
ready.value = true
return
}
try {
const me = await getUserInfo()
user.value = me
setAuthzFromUser(me)
} catch {
user.value = null
clearAuthz()
}
ready.value = true
})
</script>
<style scoped>
.layout-shell {
background: #f5f5f5;
}
.sider {
border-radius: 12px;
overflow: visible;
position: relative;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.06);
}
.sider-brand {
display: flex;
align-items: center;
gap: 10px;
padding: 16px 16px 10px;
}
.sider-brand.collapsed {
justify-content: center;
}
.sider-title {
color: rgba(0, 0, 0, 0.88);
font-size: 16px;
}
:deep(.ant-menu-inline) {
border-inline-end: 0;
}
.sider-collapse-trigger {
position: absolute;
top: 50%;
right: -16px;
width: 16px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-left: 0;
border-radius: 0 9999px 9999px 0;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
color: rgba(0, 0, 0, 0.65);
cursor: pointer;
z-index: 2;
}
.sider-collapse-trigger:hover {
background: rgba(0, 0, 0, 0.02);
}
.sider-collapse-trigger:active {
background: rgba(0, 0, 0, 0.04);
}
.body {
margin: 16px 0;
}
.main {
margin-left: 16px;
background: transparent;
}
.content {
min-height: 520px;
background: #fff;
border-radius: 12px;
padding: 18px;
}
.spin {
display: flex;
align-items: center;
justify-content: center;
min-height: 380px;
}
</style>

193
app/pages/about/index.vue Normal file
View File

@@ -0,0 +1,193 @@
<template>
<div class="about-page">
<div class="page-header">
<h1 class="page-title">关于我们</h1>
<p class="page-desc">广西决策咨询网 - 汇聚智慧服务决策</p>
</div>
<div class="about-nav">
<a-space wrap>
<a-button type="primary" @click="currentSection = 'intro'">学会简介</a-button>
<a-button @click="currentSection = 'organization'">组织机构</a-button>
<a-button @click="currentSection = 'charter'">学会章程</a-button>
<a-button @click="currentSection = 'consultation'">咨询服务</a-button>
<a-button @click="currentSection = 'join'">加入我们</a-button>
</a-space>
</div>
<!-- 学会简介 -->
<div v-if="currentSection === 'intro'" class="content-section">
<h2>学会简介</h2>
<div class="content-body">
<p>广西决策咨询网是由广西壮族自治区决策咨询委员会主管的专业决策咨询平台致力于为各级政府和企业提供高质量的决策咨询服务</p>
<p>网站汇聚了区内外的知名专家学者围绕经济社会发展中的重大问题开展研究为科学决策提供参考依据</p>
<h3>主要职能</h3>
<ul>
<li>组织开展重大决策咨询课题研究</li>
<li>为各级政府提供政策建议和咨询报告</li>
<li>搭建专家学者交流合作平台</li>
<li>推广决策咨询研究成果</li>
</ul>
</div>
</div>
<!-- 组织机构 -->
<div v-if="currentSection === 'organization'" class="content-section">
<h2>组织机构</h2>
<div class="content-body">
<h3>顾问委员会</h3>
<p>由区内外知名专家学者组成为学会发展提供指导和咨询</p>
<h3>理事会</h3>
<p>负责学会日常运营管理制定和执行各项决策</p>
<h3>专家库</h3>
<p>汇聚各领域专业人才提供智力支持</p>
</div>
</div>
<!-- 学会章程 -->
<div v-if="currentSection === 'charter'" class="content-section">
<h2>学会章程</h2>
<div class="content-body">
<h3>第一章 总则</h3>
<p>广西决策咨询学会是由区内从事决策咨询研究的专家学者和实际工作者自愿组成的学术性非营利性社会组织</p>
<h3>第二章 业务范围</h3>
<ul>
<li>开展决策咨询理论研究</li>
<li>组织学术交流活动</li>
<li>提供决策咨询服务</li>
<li>培养决策咨询人才</li>
</ul>
</div>
</div>
<!-- 咨询服务 -->
<div v-if="currentSection === 'consultation'" class="content-section">
<h2>咨询服务</h2>
<div class="content-body">
<h3>服务内容</h3>
<ul>
<li><strong>政策研究</strong>为各级政府提供政策研究和评估服务</li>
<li><strong>规划咨询</strong>区域规划产业规划编制咨询</li>
<li><strong>项目评估</strong>重大投资项目可行性研究和评估</li>
<li><strong>专题调研</strong>根据需求开展专题调研</li>
</ul>
<h3>联系方式</h3>
<p>📞 联系电话0771-1234567</p>
<p>📧 电子邮箱service@jczxw.org</p>
<p>📍 地址广西南宁市民族大道XX号</p>
</div>
</div>
<!-- 加入我们 -->
<div v-if="currentSection === 'join'" class="content-section">
<h2>加入我们</h2>
<div class="content-body">
<p>我们热忱欢迎符合条件的专家学者和有志于决策咨询事业的各界人士加入我们的大家庭</p>
<h3>入会资格</h3>
<p>详见<NuxtLink to="/about/join">入会指南</NuxtLink></p>
<div class="action-buttons">
<a-button type="primary" size="large" @click="navigateTo('/about/join/enterprise')">
企业会员申请
</a-button>
<a-button size="large" @click="navigateTo('/about/join/personal')">
个人会员申请
</a-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
useHead({ title: '关于我们 - 决策咨询网' })
const route = useRoute()
const currentSection = ref((route.query.section as string) || 'intro')
watch(() => route.query.section, (newSection) => {
currentSection.value = (newSection as string) || 'intro'
})
</script>
<style scoped>
.about-page {
max-width: 1000px;
margin: 0 auto;
padding: 40px 20px;
}
.page-header {
text-align: center;
margin-bottom: 40px;
}
.page-title {
font-size: 32px;
font-weight: 700;
color: #1f2937;
margin: 0 0 12px;
}
.page-desc {
font-size: 16px;
color: #6b7280;
margin: 0;
}
.about-nav {
margin-bottom: 40px;
text-align: center;
}
.content-section {
background: #fff;
border-radius: 16px;
padding: 40px;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
.content-section h2 {
font-size: 24px;
font-weight: 700;
color: #1f2937;
margin: 0 0 24px;
padding-bottom: 16px;
border-bottom: 2px solid #f0f0f0;
}
.content-section h3 {
font-size: 18px;
font-weight: 600;
color: #374151;
margin: 24px 0 12px;
}
.content-section p {
font-size: 15px;
color: #4b5563;
line-height: 1.8;
margin: 0 0 12px;
}
.content-section ul {
padding-left: 24px;
}
.content-section li {
font-size: 15px;
color: #4b5563;
line-height: 2;
}
.action-buttons {
margin-top: 32px;
display: flex;
gap: 16px;
}
</style>

View File

@@ -0,0 +1,250 @@
<template>
<div class="join-page">
<div class="page-header">
<h1 class="page-title">企业会员申请</h1>
<p class="page-desc">加入我们共同推动决策咨询事业发展</p>
</div>
<div class="join-content">
<a-steps :current="currentStep" class="steps-wrap">
<a-step title="填写信息" />
<a-step title="上传资料" />
<a-step title="提交审核" />
</a-steps>
<a-form :model="formData" layout="vertical" class="join-form">
<!-- 步骤1填写信息 -->
<div v-show="currentStep === 0">
<h3 class="section-title">企业基本信息</h3>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="企业名称" name="name" required>
<a-input v-model:value="formData.name" placeholder="请输入企业名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="统一社会信用代码" name="creditCode">
<a-input v-model:value="formData.creditCode" placeholder="请输入信用代码" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系人" name="contact" required>
<a-input v-model:value="formData.contact" placeholder="请输入联系人姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话" name="phone" required>
<a-input v-model:value="formData.phone" placeholder="请输入联系电话" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formData.email" placeholder="请输入邮箱" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="企业地址" name="address">
<a-input v-model:value="formData.address" placeholder="请输入企业地址" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="企业简介" name="bio">
<a-textarea v-model:value="formData.bio" :rows="4" placeholder="请简要介绍企业情况" />
</a-form-item>
</div>
<!-- 步骤2上传资料 -->
<div v-show="currentStep === 1">
<h3 class="section-title">资质证明材料</h3>
<p class="section-desc">请上传以下材料以便我们审核您的入会资格</p>
<a-form-item label="营业执照">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('license')">
<a-button><UploadOutlined /> 上传营业执照</a-button>
</a-upload>
</a-form-item>
<a-form-item label="法人身份证">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('idCard')">
<a-button><UploadOutlined /> 上传法人身份证</a-button>
</a-upload>
</a-form-item>
<a-form-item label="企业简介">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('intro')">
<a-button><UploadOutlined /> 上传企业简介</a-button>
</a-upload>
<div class="upload-hint">支持 PDFWord 格式</div>
</a-form-item>
</div>
<!-- 步骤3确认提交 -->
<div v-show="currentStep === 2" class="confirm-section">
<a-result
title="确认提交申请"
sub-title="请确认您填写的信息和上传的材料准确无误"
>
<template #icon>
<CheckCircleOutlined style="font-size: 80px; color: #52c41a" />
</template>
<template #extra>
<a-button type="primary" size="large" @click="handleSubmit" :loading="submitting">
确认提交
</a-button>
</template>
</a-result>
</div>
<!-- 步骤按钮 -->
<div class="step-actions">
<a-button v-if="currentStep > 0" @click="currentStep--">上一步</a-button>
<a-button v-if="currentStep < 2" type="primary" @click="handleNext">下一步</a-button>
</div>
</a-form>
</div>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { CheckCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
useHead({ title: '企业会员申请 - 决策咨询网' })
const currentStep = ref(0)
const submitting = ref(false)
const formData = reactive({
name: '',
creditCode: '',
contact: '',
phone: '',
email: '',
address: '',
bio: '',
license: '',
idCard: '',
intro: '',
})
function beforeUpload(file: File) {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('文件大小不能超过 10MB')
return false
}
return true
}
function handleUpload(type: string) {
return async (option: any) => {
try {
// TODO: 调用上传API
option.onSuccess()
message.success('上传成功')
} catch {
option.onError()
message.error('上传失败')
}
}
}
function handleNext() {
if (currentStep.value === 0) {
if (!formData.name || !formData.contact || !formData.phone) {
message.warning('请填写必填项')
return
}
}
currentStep.value++
}
async function handleSubmit() {
submitting.value = true
try {
// TODO: 调用API提交申请
message.success('提交成功,请等待审核')
navigateTo('/about/join')
} catch (e: any) {
message.error(e?.message || '提交失败')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.join-page {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
}
.page-header {
text-align: center;
margin-bottom: 40px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #1f2937;
margin: 0 0 12px;
}
.page-desc {
font-size: 16px;
color: #6b7280;
margin: 0;
}
.join-content {
background: #fff;
border-radius: 16px;
padding: 40px;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
.steps-wrap {
margin-bottom: 40px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #1f2937;
margin: 0 0 20px;
}
.section-desc {
font-size: 14px;
color: #6b7280;
margin: -10px 0 20px;
}
.upload-hint {
font-size: 12px;
color: #9ca3af;
margin-top: 8px;
}
.confirm-section {
padding: 40px 0;
}
.step-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #f0f0f0;
}
</style>

View File

@@ -0,0 +1,284 @@
<template>
<div class="join-page">
<div class="page-header">
<h1 class="page-title">个人会员申请</h1>
<p class="page-desc">加入我们共同推动决策咨询事业发展</p>
</div>
<div class="join-content">
<a-steps :current="currentStep" class="steps-wrap">
<a-step title="填写信息" />
<a-step title="上传资料" />
<a-step title="提交审核" />
</a-steps>
<a-form :model="formData" layout="vertical" class="join-form">
<!-- 步骤1填写信息 -->
<div v-show="currentStep === 0">
<h3 class="section-title">个人信息</h3>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="姓名" name="name" required>
<a-input v-model:value="formData.name" placeholder="请输入您的姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="性别" name="gender">
<a-select v-model:value="formData.gender" placeholder="请选择">
<a-select-option value="male"></a-select-option>
<a-select-option value="female"></a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="出生年月" name="birthday">
<a-date-picker v-model:value="formData.birthday" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="学历" name="education">
<a-select v-model:value="formData.education" placeholder="请选择">
<a-select-option value="bachelor">本科</a-select-option>
<a-select-option value="master">硕士</a-select-option>
<a-select-option value="doctor">博士</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="职称/职务" name="title">
<a-input v-model:value="formData.title" placeholder="如:教授、研究员" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="工作单位" name="organization">
<a-input v-model:value="formData.organization" placeholder="请输入工作单位" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系电话" name="phone" required>
<a-input v-model:value="formData.phone" placeholder="请输入联系电话" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formData.email" placeholder="请输入邮箱" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="研究方向/专业领域" name="researchArea">
<a-input v-model:value="formData.researchArea" placeholder="请输入研究方向" />
</a-form-item>
<a-form-item label="个人简介" name="bio">
<a-textarea v-model:value="formData.bio" :rows="4" placeholder="请简要介绍您的学术背景和工作经历" />
</a-form-item>
</div>
<!-- 步骤2上传资料 -->
<div v-show="currentStep === 1">
<h3 class="section-title">资质证明材料</h3>
<p class="section-desc">请上传相关证明材料以便我们审核您的入会资格</p>
<a-form-item label="身份证">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('idCard')">
<a-button><UploadOutlined /> 上传身份证</a-button>
</a-upload>
</a-form-item>
<a-form-item label="学历/学位证明">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('diploma')">
<a-button><UploadOutlined /> 上传学历证明</a-button>
</a-upload>
</a-form-item>
<a-form-item label="职称证明">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('certificate')">
<a-button><UploadOutlined /> 上传职称证明</a-button>
</a-upload>
</a-form-item>
<a-form-item label="研究成果或获奖证明(可选)">
<a-upload multiple :before-upload="beforeUpload" :custom-request="handleUpload('achievements')">
<a-button><UploadOutlined /> 上传材料</a-button>
</a-upload>
<div class="upload-hint">可上传多份材料支持 JPGPNGPDF 格式</div>
</a-form-item>
</div>
<!-- 步骤3确认提交 -->
<div v-show="currentStep === 2" class="confirm-section">
<a-result
title="确认提交申请"
sub-title="请确认您填写的信息和上传的材料准确无误"
>
<template #icon>
<CheckCircleOutlined style="font-size: 80px; color: #52c41a" />
</template>
<template #extra>
<a-button type="primary" size="large" @click="handleSubmit" :loading="submitting">
确认提交
</a-button>
</template>
</a-result>
</div>
<!-- 步骤按钮 -->
<div class="step-actions">
<a-button v-if="currentStep > 0" @click="currentStep--">上一步</a-button>
<a-button v-if="currentStep < 2" type="primary" @click="handleNext">下一步</a-button>
</div>
</a-form>
</div>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { CheckCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
useHead({ title: '个人会员申请 - 决策咨询网' })
const currentStep = ref(0)
const submitting = ref(false)
const formData = reactive({
name: '',
gender: undefined,
birthday: undefined,
education: undefined,
title: '',
organization: '',
phone: '',
email: '',
researchArea: '',
bio: '',
idCard: '',
diploma: '',
certificate: '',
achievements: [] as string[],
})
function beforeUpload(file: File) {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('文件大小不能超过 10MB')
return false
}
return true
}
function handleUpload(type: string) {
return async (option: any) => {
try {
// TODO: 调用上传API
option.onSuccess()
message.success('上传成功')
} catch {
option.onError()
message.error('上传失败')
}
}
}
function handleNext() {
if (currentStep.value === 0) {
if (!formData.name || !formData.phone) {
message.warning('请填写必填项')
return
}
}
currentStep.value++
}
async function handleSubmit() {
submitting.value = true
try {
// TODO: 调用API提交申请
message.success('提交成功,请等待审核')
navigateTo('/about/join')
} catch (e: any) {
message.error(e?.message || '提交失败')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.join-page {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
}
.page-header {
text-align: center;
margin-bottom: 40px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #1f2937;
margin: 0 0 12px;
}
.page-desc {
font-size: 16px;
color: #6b7280;
margin: 0;
}
.join-content {
background: #fff;
border-radius: 16px;
padding: 40px;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
.steps-wrap {
margin-bottom: 40px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #1f2937;
margin: 0 0 20px;
}
.section-desc {
font-size: 14px;
color: #6b7280;
margin: -10px 0 20px;
}
.upload-hint {
font-size: 12px;
color: #9ca3af;
margin-top: 8px;
}
.confirm-section {
padding: 40px 0;
}
.step-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #f0f0f0;
}
</style>

View File

@@ -1,508 +0,0 @@
<template>
<div class="all-apps-page">
<!-- 页面头部 -->
<div class="page-header">
<div>
<h2 class="page-title">🌐 全局应用管理</h2>
<p class="page-desc">查看所有租户的应用支持按租户筛选</p>
</div>
<a-space>
<a-button @click="loadApps" :loading="loading">
<template #icon><ReloadOutlined /></template>
刷新
</a-button>
</a-space>
</div>
<!-- 统计卡片 -->
<a-row :gutter="[16, 16]" class="mb-6">
<a-col :xs="12" :md="6" v-for="stat in stats" :key="stat.key">
<div
class="stat-card"
:class="[stat.color, { active: filterTenantId === stat.key }]"
@click="handleStatFilter(stat.key)"
>
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-info">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</a-col>
</a-row>
<!-- 筛选 + 列表 -->
<div class="panel">
<div class="panel-header">
<span class="panel-title">📋 应用列表</span>
<a-space wrap>
<a-select
v-model:value="filterTenantId"
style="width: 180px"
allow-clear
placeholder="全部租户"
:loading="loadingTenants"
@change="handleSearch"
>
<a-select-option v-for="t in tenantList" :key="t.tenantId" :value="t.tenantId">
{{ t.tenantName }}
</a-select-option>
</a-select>
<a-select v-model:value="filterStatus" style="width: 130px" @change="handleSearch">
<a-select-option value="">全部状态</a-select-option>
<a-select-option :value="0">未开通</a-select-option>
<a-select-option :value="1">运行中</a-select-option>
<a-select-option :value="2">维护中</a-select-option>
<a-select-option :value="3">已关闭</a-select-option>
</a-select>
<a-select v-model:value="filterType" style="width: 140px" allow-clear placeholder="全部类型" @change="handleSearch">
<a-select-option v-for="(name, key) in APP_TYPE_NAME" :key="key" :value="Number(key)">
{{ name }}
</a-select-option>
</a-select>
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索应用名称/标识/租户"
style="width: 240px"
@search="handleSearch"
/>
</a-space>
</div>
<a-table
:columns="columns"
:data-source="apps"
:loading="loading"
:pagination="pagination"
row-key="productId"
@change="handleTableChange"
size="middle"
:scroll="{ x: 1400 }"
>
<template #bodyCell="{ column, record }">
<!-- 应用信息 -->
<template v-if="column.key === 'appInfo'">
<div class="app-info-cell">
<img v-if="record.icon" :src="record.icon" class="app-icon" />
<div v-else class="app-icon-placeholder" :style="{ background: iconBgColor(record.productName) }">
{{ (record.productName || 'A').charAt(0).toUpperCase() }}
</div>
<div class="app-info-text">
<div class="app-name">{{ record.productName }}</div>
<div class="app-code">{{ record.productCode }}</div>
</div>
</div>
</template>
<!-- 所属租户 -->
<template v-if="column.key === 'tenantName'">
<a-tag>{{ record.tenantId }}</a-tag>
</template>
<!-- 类型 -->
<template v-if="column.key === 'type'">
<a-tag color="blue">{{ APP_TYPE_NAME[record.appType ?? 10] || '未知' }}</a-tag>
<a-tag v-if="record.official === 1" color="gold" style="margin-left:4px">官方</a-tag>
</template>
<!-- 状态 -->
<template v-if="column.key === 'status'">
<a-badge :status="statusBadge(record.status)" :text="statusText(record.status)" />
</template>
<!-- 发布状态 -->
<template v-if="column.key === 'publishStatus'">
<a-tag v-if="record.publishStatus" :color="pubStatusColor(record.publishStatus)">
{{ pubStatusText(record.publishStatus) }}
</a-tag>
<span v-else class="text-gray-400">-</span>
</template>
<!-- 域名 -->
<template v-if="column.key === 'domain'">
<a v-if="record.domain" :href="'https://' + record.domain" target="_blank" class="domain-link">
{{ record.domain }}
</a>
<span v-else class="text-gray-400">-</span>
</template>
<!-- 创建时间 -->
<template v-if="column.key === 'createTime'">
<span class="text-sm text-gray">{{ record.createTime?.substring(0, 10) || '-' }}</span>
</template>
<!-- 操作 -->
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleView(record)">详情</a-button>
<a-dropdown :trigger="['click']">
<a-button type="link" size="small">更多 <DownOutlined /></a-button>
<template #overlay>
<a-menu @click="({ key }) => handleMoreAction(key as string, record)">
<a-menu-item key="toggle-status">
{{ record.status === 1 ? '🔒 暂停运行' : '▶️ 恢复运行' }}
</a-menu-item>
<a-menu-item key="toggle-official">
{{ record.official === 1 ? '取消官方' : '设为官方' }}
</a-menu-item>
<a-menu-item key="toggle-market">
{{ record.market === 1 ? '下架市场' : '上架市场' }}
</a-menu-item>
<a-menu-divider />
<a-menu-item key="delete" class="danger-item">🗑 删除应用</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</template>
</template>
</a-table>
</div>
<!-- 详情弹窗 -->
<a-modal
v-model:open="showDetailModal"
:title="`应用详情:${currentApp?.productName || ''}`"
width="720px"
:footer="null"
>
<template v-if="currentApp">
<a-descriptions :column="2" bordered size="small">
<a-descriptions-item label="应用名称">{{ currentApp.productName }}</a-descriptions-item>
<a-descriptions-item label="应用标识">{{ currentApp.productCode }}</a-descriptions-item>
<a-descriptions-item label="所属租户">
<a-tag color="purple">{{ getTenantName(currentApp.tenantId) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="开发者">{{ currentApp.developer || '-' }}</a-descriptions-item>
<a-descriptions-item label="应用类型">
<a-tag color="blue">{{ APP_TYPE_NAME[currentApp.appType ?? 10] || '未知' }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="运行状态">
<a-badge :status="statusBadge(currentApp.status)" :text="statusText(currentApp.status)" />
</a-descriptions-item>
<a-descriptions-item label="发布状态">
<a-tag v-if="currentApp.publishStatus" :color="pubStatusColor(currentApp.publishStatus)">
{{ pubStatusText(currentApp.publishStatus) }}
</a-tag>
<span v-else>-</span>
</a-descriptions-item>
<a-descriptions-item label="绑定域名" :span="2">
<a v-if="currentApp.domain" :href="'https://' + currentApp.domain" target="_blank">{{ currentApp.domain }}</a>
<span v-else>-</span>
</a-descriptions-item>
<a-descriptions-item label="ICP备案">{{ currentApp.icpNo || '-' }}</a-descriptions-item>
<a-descriptions-item label="安装次数">{{ currentApp.installs ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="评分">{{ currentApp.rating ? currentApp.rating + ' ⭐' : '-' }}</a-descriptions-item>
<a-descriptions-item label="到期时间">{{ currentApp.expirationTime || '-' }}</a-descriptions-item>
<a-descriptions-item label="创建时间" :span="2">{{ currentApp.createTime || '-' }}</a-descriptions-item>
<a-descriptions-item v-if="currentApp.description" label="应用简介" :span="2">
{{ currentApp.description }}
</a-descriptions-item>
</a-descriptions>
</template>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ReloadOutlined, DownOutlined } from '@ant-design/icons-vue'
import { message, Modal } from 'ant-design-vue'
import { pageAppProduct, updateAppProduct, removeAppProduct } from '@/api/app/appProduct'
import type { AppProduct } from '@/api/app/appProduct/model'
import { APP_TYPE_NAME } from '@/api/app/appProduct/model'
import { listTenant } from '@/api/system/tenant/index'
import type { Tenant } from '@/api/system/tenant/model'
definePageMeta({ layout: 'admin' })
useHead({ title: '全局应用管理 - 平台管理' })
const loading = ref(false)
const apps = ref<AppProduct[]>([])
const filterStatus = ref<number | ''>('')
const filterType = ref<number | ''>('')
const filterTenantId = ref<number | ''>('')
const searchKeyword = ref('')
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
})
// 租户列表
const tenantList = ref<Tenant[]>([])
const loadingTenants = ref(false)
async function loadTenantList() {
loadingTenants.value = true
try {
const res = await listTenant()
tenantList.value = res || []
} catch {
// 静默失败
} finally {
loadingTenants.value = false
}
}
function getTenantName(tenantId?: number) {
if (!tenantId) return '-'
const tenant = tenantList.value.find(t => t.tenantId === tenantId)
return tenant?.tenantName || `租户${tenantId}`
}
// 统计数据
const stats = computed(() => {
const total = apps.value.length
const running = apps.value.filter(a => a.status === 1).length
const maintenance = apps.value.filter(a => a.status === 2).length
const closed = apps.value.filter(a => a.status === 3).length
return [
{ key: '', icon: '📦', label: '全部应用', value: pagination.total, color: 'blue' },
{ key: 1, icon: '✅', label: '运行中', value: running, color: 'green' },
{ key: 2, icon: '🔧', label: '维护中', value: maintenance, color: 'orange' },
{ key: 3, icon: '⛔', label: '已关闭', value: closed, color: 'red' },
]
})
const columns = [
{ title: '应用信息', key: 'appInfo', width: 250, fixed: 'left' },
{ title: '所属租户', key: 'tenantName', width: 150 },
{ title: '类型', key: 'type', width: 140 },
{ title: '运行状态', key: 'status', width: 110 },
{ title: '发布状态', key: 'publishStatus', width: 110 },
{ title: '绑定域名', key: 'domain', width: 180 },
{ title: '创建时间', key: 'createTime', width: 110 },
{ title: '操作', key: 'action', width: 160, fixed: 'right' },
]
const showDetailModal = ref(false)
const currentApp = ref<AppProduct | null>(null)
async function loadApps() {
loading.value = true
try {
const res = await pageAppProduct({
current: pagination.current,
size: pagination.pageSize,
status: filterStatus.value !== '' ? (filterStatus.value as number) : undefined,
appType: filterType.value !== '' ? (filterType.value as number) : undefined,
tenantId: filterTenantId.value !== '' ? (filterTenantId.value as number) : undefined,
keywords: searchKeyword.value || undefined,
})
apps.value = res?.list || []
pagination.total = res?.count || 0
} catch {
message.error('加载应用列表失败')
} finally {
loading.value = false
}
}
function handleStatFilter(key: number | '') {
filterTenantId.value = key
pagination.current = 1
loadApps()
}
function handleSearch() {
pagination.current = 1
loadApps()
}
function handleTableChange(pag: any) {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
loadApps()
}
function handleView(record: AppProduct) {
currentApp.value = record
showDetailModal.value = true
}
async function handleMoreAction(key: string, record: AppProduct) {
if (key === 'toggle-status') {
const newStatus = record.status === 1 ? 3 : 1
try {
await updateAppProduct({ productId: record.productId, status: newStatus })
message.success('状态已更新')
loadApps()
} catch (e: any) { message.error(e?.message || '操作失败') }
}
if (key === 'toggle-official') {
try {
await updateAppProduct({ productId: record.productId, official: record.official ? 0 : 1 })
message.success(record.official ? '已取消官方标记' : '已设为官方应用')
loadApps()
} catch (e: any) { message.error(e?.message || '操作失败') }
}
if (key === 'toggle-market') {
try {
await updateAppProduct({ productId: record.productId, market: record.market ? 0 : 1 })
message.success(record.market ? '已从市场下架' : '已上架至应用市场')
loadApps()
} catch (e: any) { message.error(e?.message || '操作失败') }
}
if (key === 'delete') {
Modal.confirm({
title: '确认删除',
content: `确定要删除应用「${record.productName}」吗?此操作不可恢复!`,
okType: 'danger',
onOk: async () => {
try {
await removeAppProduct(record.productId)
message.success('应用已删除')
loadApps()
} catch (e: any) { message.error(e?.message || '删除失败') }
},
})
}
}
function statusText(status?: number) {
const map: Record<number, string> = { 0: '未开通', 1: '运行中', 2: '维护中', 3: '已关闭', 4: '已欠费', 5: '违规停止' }
return map[status ?? -1] || '未知'
}
function statusBadge(status?: number): 'success' | 'warning' | 'error' | 'default' {
if (status === 1) return 'success'
if (status === 2) return 'warning'
if (status === 3 || status === 5) return 'error'
return 'default'
}
function pubStatusColor(status?: string) {
const map: Record<string, string> = {
developing: 'default',
pending_review: 'orange',
published: 'success',
rejected: 'error',
deprecated: 'default',
}
return map[status || ''] || 'default'
}
function pubStatusText(status?: string) {
const map: Record<string, string> = {
developing: '开发中',
pending_review: '待审核',
published: '已上架',
rejected: '已拒绝',
deprecated: '已下架',
}
return map[status || ''] || '-'
}
const PALETTE = ['#4e6ef2', '#f4a261', '#e76f51', '#2a9d8f', '#e9c46a', '#457b9d']
function iconBgColor(name?: string) {
if (!name) return PALETTE[0]
let h = 0
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) & 0xffffffff
return PALETTE[Math.abs(h) % PALETTE.length]
}
onMounted(() => {
loadTenantList()
loadApps()
})
</script>
<style scoped>
.all-apps-page { min-height: 100%; }
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.page-title {
font-size: 18px;
font-weight: 700;
color: #1f2937;
margin: 0;
}
.page-desc {
font-size: 13px;
color: #9ca3af;
margin: 2px 0 0;
}
.stat-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border-radius: 12px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s;
}
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
.stat-card.active { box-shadow: 0 4px 12px rgba(0,0,0,0.12); }
.stat-card.blue { background: #eff6ff; border-color: #dbeafe; }
.stat-card.orange { background: #fff7ed; border-color: #fed7aa; }
.stat-card.green { background: #f0fdf4; border-color: #bbf7d0; }
.stat-card.red { background: #fff1f2; border-color: #fecdd3; }
.stat-card.active.blue { border-color: #3b82f6; }
.stat-card.active.orange { border-color: #f97316; }
.stat-card.active.green { border-color: #22c55e; }
.stat-card.active.red { border-color: #ef4444; }
.stat-icon { font-size: 28px; flex-shrink: 0; }
.stat-value { font-size: 22px; font-weight: 700; color: rgba(0,0,0,0.85); line-height: 1.2; }
.stat-label { font-size: 12px; color: rgba(0,0,0,0.45); margin-top: 2px; }
.panel {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid #f5f5f5;
flex-wrap: wrap;
gap: 10px;
}
.panel-title { font-size: 14px; font-weight: 600; color: rgba(0,0,0,0.85); }
.app-info-cell { display: flex; align-items: center; gap: 12px; }
.app-icon {
width: 44px; height: 44px;
border-radius: 8px; object-fit: cover; flex-shrink: 0;
}
.app-icon-placeholder {
width: 44px; height: 44px;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 18px; font-weight: 600; color: #fff; flex-shrink: 0;
}
.app-info-text { flex: 1; min-width: 0; }
.app-name { font-size: 14px; font-weight: 500; color: rgba(0,0,0,0.85); }
.app-code { font-size: 12px; color: rgba(0,0,0,0.45); }
.domain-link { font-size: 13px; color: #4f46e5; text-decoration: none; }
.domain-link:hover { text-decoration: underline; }
.text-sm { font-size: 12px; }
.text-gray { color: rgba(0,0,0,0.45); }
.text-gray-400 { color: #9ca3af; }
.danger-item { color: #ff4d4f !important; }
.mb-6 { margin-bottom: 24px; }
</style>

Some files were not shown because too many files have changed in this diff Show More