fix: 新增代理系列授权-建议售价使用套餐
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m38s
1
components.d.ts
vendored
@@ -14,7 +14,6 @@ declare module 'vue' {
|
||||
ArtBreadcrumb: typeof import('./src/components/core/layouts/art-breadcrumb/index.vue')['default']
|
||||
ArtButtonMore: typeof import('./src/components/core/forms/ArtButtonMore.vue')['default']
|
||||
ArtButtonTable: typeof import('./src/components/core/forms/ArtButtonTable.vue')['default']
|
||||
ArtCardBanner: typeof import('./src/components/core/banners/ArtCardBanner.vue')['default']
|
||||
ArtChatWindow: typeof import('./src/components/core/layouts/art-chat-window/index.vue')['default']
|
||||
ArtCutterImg: typeof import('./src/components/core/media/ArtCutterImg.vue')['default']
|
||||
ArtDataListCard: typeof import('./src/components/core/cards/ArtDataListCard.vue')['default']
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
当前后台“资产信息”页还没有消费接口新增的 `gateway_extend` 字段,因此运营人员无法在资产详情中直接看到运营商侧实际停机原因。
|
||||
|
||||
最新接口契约已经补充两类返回:
|
||||
|
||||
- 设备资产的绑定卡列表项新增 `gateway_extend`
|
||||
- 卡资产详情新增 `gateway_extend`
|
||||
|
||||
|
||||
@@ -33,15 +33,19 @@
|
||||
## Decisions
|
||||
|
||||
- Decision: 能力名称使用 `payment-configuration-management`,而不是沿用页面路径中的 `wechat-config`
|
||||
|
||||
- Rationale: 该能力已经覆盖微信、小程序、公众号、支付宝和富友相关字段,业务边界明显大于“微信配置”。
|
||||
|
||||
- Decision: `provider_type` 在本次 spec 中严格按文档限定为 `wechat`、`wechat_v2`、`fuiou`
|
||||
|
||||
- Rationale: 这是文档明确枚举出的渠道主类型,spec 不应凭猜测新增 `alipay`。
|
||||
|
||||
- Decision: `ali_*` 字段在本次 spec 中被视为支付配置对象支持的可选扩展字段,但不单独引入新的 `provider_type`
|
||||
|
||||
- Rationale: 文档已经明确给出这些字段;在没有更正枚举之前,先把它们作为统一字段模型的一部分记录下来。
|
||||
|
||||
- Decision: 所有读取响应中的敏感信息都必须采用脱敏或配置状态标记,不能返回明文密钥
|
||||
|
||||
- Rationale: 文档的详情和当前生效配置示例已经给出了 `***`、`已配置`、`未配置` 这类语义。
|
||||
|
||||
- Decision: 更新接口采用部分更新语义,只要求提交实际需要修改的字段
|
||||
@@ -50,6 +54,7 @@
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 风险: 当前前端类型定义尚未覆盖 `ali_*` 字段,按 spec 对齐后会触发类型和页面联动修改。
|
||||
|
||||
- Mitigation: 实施时先调整 `src/types/api/wechatConfig.ts`,再逐步修改列表、表单和详情展示。
|
||||
|
||||
- 风险: 文档没有给出“如何切换当前生效配置”的写接口,只有 `is_active` 状态和 `/active` 查询接口。
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
当前后台设备管理和企业设备授权流程还没有完整接入新的设备激活状态字段,导致运营人员无法按“已激活 / 未激活”筛选设备,也无法在资产详情中直接看到设备激活状态名称。
|
||||
|
||||
最新接口契约补充了以下内容:
|
||||
|
||||
- 设备列表接口新增可选过滤参数 `activation_status`
|
||||
- 设备列表与资产解析相关响应新增 `activation_status_name`
|
||||
- 企业设备授权弹窗中的可选设备列表也需要沿用这套激活状态筛选与展示规则
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
```typescript
|
||||
export enum ExchangeFlowType {
|
||||
SHIPPING = 'shipping', // 物流换货
|
||||
DIRECT = 'direct' // 直接换货
|
||||
DIRECT = 'direct' // 直接换货
|
||||
}
|
||||
|
||||
export const EXCHANGE_FLOW_TYPE_MAP = {
|
||||
@@ -47,6 +47,7 @@ export const EXCHANGE_FLOW_TYPE_MAP = {
|
||||
**Rationale**: 符合 Element Plus 表单最佳实践,避免不必要的字段提交。
|
||||
|
||||
**Alternatives considered**:
|
||||
|
||||
- 使用 `v-show` 隐藏:会导致隐藏字段仍参与表单验证
|
||||
- 使用动态表单项:增加复杂度,不够直观
|
||||
|
||||
@@ -55,16 +56,16 @@ export const EXCHANGE_FLOW_TYPE_MAP = {
|
||||
在详情页组件中定义计算属性:
|
||||
|
||||
```typescript
|
||||
const canShip = computed(() =>
|
||||
exchange.value.flow_type === 'shipping' && exchange.value.status === 2
|
||||
const canShip = computed(
|
||||
() => exchange.value.flow_type === 'shipping' && exchange.value.status === 2
|
||||
)
|
||||
|
||||
const canComplete = computed(() =>
|
||||
exchange.value.flow_type === 'shipping' && exchange.value.status === 3
|
||||
const canComplete = computed(
|
||||
() => exchange.value.flow_type === 'shipping' && exchange.value.status === 3
|
||||
)
|
||||
|
||||
const canCancel = computed(() =>
|
||||
exchange.value.flow_type === 'shipping' && [1, 2].includes(exchange.value.status)
|
||||
const canCancel = computed(
|
||||
() => exchange.value.flow_type === 'shipping' && [1, 2].includes(exchange.value.status)
|
||||
)
|
||||
```
|
||||
|
||||
@@ -78,6 +79,7 @@ const canCancel = computed(() =>
|
||||
- **原因**: direct 类型这些字段为 null,显示空白区块或"不适用"会造成页面冗余
|
||||
|
||||
**Alternatives considered**:
|
||||
|
||||
- 显示"不适用"占位:增加不必要的视觉噪音
|
||||
- 显示空区块:用户体验不佳
|
||||
|
||||
@@ -105,6 +107,7 @@ const flowType = exchange.flow_type || 'shipping'
|
||||
引入流程类型后,详情页的条件渲染逻辑增多。
|
||||
|
||||
**Mitigation**:
|
||||
|
||||
- 使用计算属性封装权限判断逻辑
|
||||
- 添加单元测试覆盖不同流程类型的渲染场景
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import request from '@/utils/http'
|
||||
import { PaginationResponse, BaseResponse } from '@/types/api'
|
||||
import { ArticleType, ArticleCategoryType, ArticleQueryParams } from '@/api/modules'
|
||||
|
||||
// 文章
|
||||
export class ArticleService {
|
||||
// 获取文章列表
|
||||
static getArticleList(params: ArticleQueryParams) {
|
||||
const { page, size, searchVal, year } = params
|
||||
return request.get<PaginationResponse<ArticleType>>({
|
||||
url: `/api/articles/${page}/${size}?title=${searchVal}&year=${year}`
|
||||
})
|
||||
}
|
||||
|
||||
// 获取文章类型
|
||||
static getArticleTypes(params: object) {
|
||||
return request.get<BaseResponse<ArticleCategoryType[]>>({
|
||||
url: '/api/articles/types',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取文章详情
|
||||
static getArticleDetail(id: number) {
|
||||
return request.get<BaseResponse<ArticleType>>({
|
||||
url: `/api/articles/${id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 新增文章
|
||||
static addArticle(params: any) {
|
||||
return request.post<BaseResponse>({
|
||||
url: '/api/articles/',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑文章
|
||||
static editArticle(id: number, params: any) {
|
||||
return request.put<BaseResponse>({
|
||||
url: `/api/articles/${id}`,
|
||||
data: params
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { asyncRoutes } from '@/router/routes/asyncRoutes'
|
||||
import { menuDataToRouter } from '@/router/utils/menuToRouter'
|
||||
import { AppRouteRecord } from '@/types/router'
|
||||
|
||||
interface MenuResponse {
|
||||
menuList: AppRouteRecord[]
|
||||
}
|
||||
|
||||
// 菜单接口
|
||||
export const menuService = {
|
||||
async getMenuList(delay = 300): Promise<MenuResponse> {
|
||||
try {
|
||||
// 模拟接口返回的菜单数据
|
||||
const menuData = asyncRoutes
|
||||
// 处理菜单数据
|
||||
const menuList = menuData.map((route) => menuDataToRouter(route))
|
||||
// 模拟接口延迟
|
||||
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||
|
||||
return { menuList }
|
||||
} catch (error) {
|
||||
throw error instanceof Error ? error : new Error('获取菜单失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* 文章相关类型定义
|
||||
*/
|
||||
|
||||
// 文章类型 (新命名规范)
|
||||
export interface Article {
|
||||
id?: number
|
||||
blogClass: string
|
||||
title: string
|
||||
count?: number
|
||||
htmlContent: string
|
||||
createTime: string
|
||||
homeImg: string
|
||||
brief: string
|
||||
typeName?: string
|
||||
status?: number
|
||||
author?: string
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
// 兼容原有的文章类型命名
|
||||
export interface ArticleType {
|
||||
id?: number
|
||||
blog_class: string
|
||||
title: string
|
||||
count?: number
|
||||
html_content: string
|
||||
create_time: string
|
||||
home_img: string
|
||||
brief: string
|
||||
type_name?: string
|
||||
}
|
||||
|
||||
// 文章分类类型 (新命名规范)
|
||||
export interface ArticleCategory {
|
||||
id: number
|
||||
name: string
|
||||
icon: string
|
||||
count: number
|
||||
description?: string
|
||||
sortOrder?: number
|
||||
}
|
||||
|
||||
// 兼容原有的文章分类类型命名
|
||||
export interface ArticleCategoryType {
|
||||
id: number
|
||||
name: string
|
||||
icon: string
|
||||
count: number
|
||||
}
|
||||
|
||||
// 文章查询参数
|
||||
export interface ArticleQueryParams {
|
||||
page?: number
|
||||
size?: number
|
||||
searchVal?: string
|
||||
year?: string
|
||||
categoryId?: number
|
||||
status?: number
|
||||
}
|
||||
|
||||
// 文章创建/更新参数
|
||||
export interface ArticleFormData {
|
||||
blogClass: string
|
||||
title: string
|
||||
htmlContent: string
|
||||
homeImg: string
|
||||
brief: string
|
||||
author?: string
|
||||
tags?: string[]
|
||||
status?: number
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
SuspendResumeRecord,
|
||||
CardOrder,
|
||||
BaseResponse,
|
||||
PaginationParams,
|
||||
PaginationResponse,
|
||||
ListResponse,
|
||||
GatewayRealnameLinkResponse,
|
||||
@@ -24,12 +25,36 @@ import type {
|
||||
ImportIotCardResponse,
|
||||
IotCardImportTask,
|
||||
IotCardImportTaskDetail,
|
||||
IotCardImportTaskQueryParams,
|
||||
StandaloneCardQueryParams,
|
||||
StandaloneIotCard,
|
||||
AllocateStandaloneCardsRequest,
|
||||
AllocateStandaloneCardsResponse,
|
||||
RecallStandaloneCardsRequest,
|
||||
AssetAllocationRecordQueryParams,
|
||||
AssetAllocationRecord,
|
||||
AssetAllocationRecordDetail,
|
||||
BatchSetCardSeriesBindingRequest,
|
||||
BatchSetCardSeriesBindingResponse
|
||||
} from '@/types/api'
|
||||
|
||||
type ApiQueryParams = PaginationParams & Record<string, unknown>
|
||||
|
||||
interface ImportFailureRecord {
|
||||
line?: number
|
||||
row?: number
|
||||
iccid?: string
|
||||
reason: string
|
||||
}
|
||||
|
||||
interface CardChangeNotice {
|
||||
id: string | number
|
||||
iccids: string[]
|
||||
reason: string
|
||||
createTime?: string
|
||||
operatorName?: string
|
||||
}
|
||||
|
||||
export class CardService extends BaseService {
|
||||
// ========== 号卡商品管理 ==========
|
||||
|
||||
@@ -37,7 +62,7 @@ export class CardService extends BaseService {
|
||||
* 获取号卡商品列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getSimCardProducts(params?: any): Promise<PaginationResponse<SimCardProduct>> {
|
||||
static getSimCardProducts(params?: ApiQueryParams): Promise<PaginationResponse<SimCardProduct>> {
|
||||
return this.getPage<SimCardProduct>('/api/simcard-products', params)
|
||||
}
|
||||
|
||||
@@ -95,15 +120,6 @@ export class CardService extends BaseService {
|
||||
return this.getOne<Card>(`/api/cards/iccid/${iccid}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过ICCID查询单卡详情(旧接口,已废弃)
|
||||
* @deprecated 使用 AssetService.resolveAsset 替代
|
||||
* @param iccid ICCID
|
||||
*/
|
||||
static getIotCardDetailByIccid(iccid: string): Promise<BaseResponse<any>> {
|
||||
return this.getOne<any>(`/api/admin/iot-cards/by-iccid/${iccid}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 网卡操作(充值、停复机、增减流量等)
|
||||
* @param params 操作参数
|
||||
@@ -214,7 +230,7 @@ export class CardService extends BaseService {
|
||||
* 获取导入批次列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getImportBatches(params?: any): Promise<PaginationResponse<CardImportBatch>> {
|
||||
static getImportBatches(params?: ApiQueryParams): Promise<PaginationResponse<CardImportBatch>> {
|
||||
return this.getPage<CardImportBatch>('/api/cards/import-batches', params)
|
||||
}
|
||||
|
||||
@@ -223,7 +239,7 @@ export class CardService extends BaseService {
|
||||
* @param file Excel文件
|
||||
* @param params 额外参数
|
||||
*/
|
||||
static importCards(file: File, params?: Record<string, any>): Promise<BaseResponse> {
|
||||
static importCards(file: File, params?: Record<string, unknown>): Promise<BaseResponse> {
|
||||
return this.upload('/api/cards/import', file, params)
|
||||
}
|
||||
|
||||
@@ -231,15 +247,17 @@ export class CardService extends BaseService {
|
||||
* 获取导入失败记录
|
||||
* @param batchId 批次ID
|
||||
*/
|
||||
static getImportFailures(batchId: string | number): Promise<ListResponse<any>> {
|
||||
return this.getList(`/api/cards/import-batches/${batchId}/failures`)
|
||||
static getImportFailures(batchId: string | number): Promise<ListResponse<ImportFailureRecord>> {
|
||||
return this.getList<ImportFailureRecord>(`/api/cards/import-batches/${batchId}/failures`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量充值记录列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getBatchRechargeRecords(params?: any): Promise<PaginationResponse<BatchRechargeRecord>> {
|
||||
static getBatchRechargeRecords(
|
||||
params?: ApiQueryParams
|
||||
): Promise<PaginationResponse<BatchRechargeRecord>> {
|
||||
return this.getPage<BatchRechargeRecord>('/api/cards/batch-recharge-records', params)
|
||||
}
|
||||
|
||||
@@ -258,7 +276,7 @@ export class CardService extends BaseService {
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getCardChangeApplications(
|
||||
params?: any
|
||||
params?: ApiQueryParams
|
||||
): Promise<PaginationResponse<CardChangeApplication>> {
|
||||
return this.getPage<CardChangeApplication>('/api/card-change-applications', params)
|
||||
}
|
||||
@@ -284,8 +302,10 @@ export class CardService extends BaseService {
|
||||
* 获取换卡通知记录
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getCardChangeNotices(params?: any): Promise<PaginationResponse<any>> {
|
||||
return this.getPage('/api/card-change-notices', params)
|
||||
static getCardChangeNotices(
|
||||
params?: ApiQueryParams
|
||||
): Promise<PaginationResponse<CardChangeNotice>> {
|
||||
return this.getPage<CardChangeNotice>('/api/card-change-notices', params)
|
||||
}
|
||||
|
||||
// ========== ICCID批量导入相关 ==========
|
||||
@@ -302,8 +322,10 @@ export class CardService extends BaseService {
|
||||
* 获取导入任务列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getIotCardImportTasks(params?: any): Promise<PaginationResponse<IotCardImportTask>> {
|
||||
return this.getPage('/api/admin/iot-cards/import-tasks', params)
|
||||
static getIotCardImportTasks(
|
||||
params?: IotCardImportTaskQueryParams
|
||||
): Promise<PaginationResponse<IotCardImportTask>> {
|
||||
return this.getPage<IotCardImportTask>('/api/admin/iot-cards/import-tasks', params)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,16 +352,26 @@ export class CardService extends BaseService {
|
||||
* 批量分配单卡
|
||||
* @param data 分配参数
|
||||
*/
|
||||
static allocateStandaloneCards(data: any): Promise<BaseResponse<any>> {
|
||||
return this.post('/api/admin/iot-cards/standalone/allocate', data)
|
||||
static allocateStandaloneCards(
|
||||
data: Partial<AllocateStandaloneCardsRequest>
|
||||
): Promise<BaseResponse<AllocateStandaloneCardsResponse>> {
|
||||
return this.post<BaseResponse<AllocateStandaloneCardsResponse>>(
|
||||
'/api/admin/iot-cards/standalone/allocate',
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量回收单卡
|
||||
* @param data 回收参数
|
||||
*/
|
||||
static recallStandaloneCards(data: any): Promise<BaseResponse<any>> {
|
||||
return this.post('/api/admin/iot-cards/standalone/recall', data)
|
||||
static recallStandaloneCards(
|
||||
data: Partial<RecallStandaloneCardsRequest>
|
||||
): Promise<BaseResponse<AllocateStandaloneCardsResponse>> {
|
||||
return this.post<BaseResponse<AllocateStandaloneCardsResponse>>(
|
||||
'/api/admin/iot-cards/standalone/recall',
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
// ========== 资产分配记录相关 ==========
|
||||
@@ -348,16 +380,20 @@ export class CardService extends BaseService {
|
||||
* 获取资产分配记录列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getAssetAllocationRecords(params?: any): Promise<PaginationResponse<any>> {
|
||||
return this.getPage('/api/admin/asset-allocation-records', params)
|
||||
static getAssetAllocationRecords(
|
||||
params?: AssetAllocationRecordQueryParams
|
||||
): Promise<PaginationResponse<AssetAllocationRecord>> {
|
||||
return this.getPage<AssetAllocationRecord>('/api/admin/asset-allocation-records', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资产分配记录详情
|
||||
* @param id 记录ID
|
||||
*/
|
||||
static getAssetAllocationRecordDetail(id: number): Promise<BaseResponse<any>> {
|
||||
return this.getOne(`/api/admin/asset-allocation-records/${id}`)
|
||||
static getAssetAllocationRecordDetail(
|
||||
id: number
|
||||
): Promise<BaseResponse<AssetAllocationRecordDetail>> {
|
||||
return this.getOne<AssetAllocationRecordDetail>(`/api/admin/asset-allocation-records/${id}`)
|
||||
}
|
||||
|
||||
// ========== 批量设置卡的套餐系列绑定相关 ==========
|
||||
|
||||
@@ -13,8 +13,6 @@ import type {
|
||||
WithdrawalSettingItem,
|
||||
CreateWithdrawalSettingParams,
|
||||
CommissionRecordQueryParams,
|
||||
MyCommissionRecordPageResult,
|
||||
MyCommissionSummary,
|
||||
SubmitWithdrawalParams,
|
||||
ShopCommissionRecordPageResult,
|
||||
ShopFundSummaryQueryParams,
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* 客户账号管理 API (企业账号)
|
||||
*
|
||||
* @deprecated 此 API 已废弃,请使用统一的 AccountService 代替
|
||||
* @see AccountService - 统一账号管理接口 (/api/admin/accounts)
|
||||
*
|
||||
* 迁移说明:
|
||||
* - 所有账号类型统一使用 /api/admin/accounts 接口
|
||||
* - 通过 user_type 参数区分账号类型 (2=平台, 3=代理, 4=企业)
|
||||
* - customer-accounts 已改名为 enterprise(企业账号)
|
||||
* - 详见:docs/迁移指南.md
|
||||
*/
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import type { BaseResponse } from '@/types/api'
|
||||
import type {
|
||||
CustomerAccountItem,
|
||||
CustomerAccountPageResult,
|
||||
CustomerAccountQueryParams,
|
||||
CreateCustomerAccountParams,
|
||||
UpdateCustomerAccountParams,
|
||||
UpdateCustomerAccountPasswordParams,
|
||||
UpdateCustomerAccountStatusParams
|
||||
} from '@/types/api/customerAccount'
|
||||
|
||||
export class CustomerAccountService extends BaseService {
|
||||
/**
|
||||
* 查询客户账号列表
|
||||
*/
|
||||
static getCustomerAccounts(
|
||||
params?: CustomerAccountQueryParams
|
||||
): Promise<BaseResponse<CustomerAccountPageResult>> {
|
||||
return this.get<BaseResponse<CustomerAccountPageResult>>('/api/admin/customer-accounts', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增代理商账号
|
||||
*/
|
||||
static createCustomerAccount(
|
||||
data: CreateCustomerAccountParams
|
||||
): Promise<BaseResponse<CustomerAccountItem>> {
|
||||
return this.post<BaseResponse<CustomerAccountItem>>('/api/admin/customer-accounts', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑账号
|
||||
*/
|
||||
static updateCustomerAccount(
|
||||
id: number,
|
||||
data: UpdateCustomerAccountParams
|
||||
): Promise<BaseResponse<CustomerAccountItem>> {
|
||||
return this.put<BaseResponse<CustomerAccountItem>>(`/api/admin/customer-accounts/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改账号密码
|
||||
*/
|
||||
static updateCustomerAccountPassword(
|
||||
id: number,
|
||||
data: UpdateCustomerAccountPasswordParams
|
||||
): Promise<BaseResponse> {
|
||||
return this.put<BaseResponse>(`/api/admin/customer-accounts/${id}/password`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改账号状态
|
||||
*/
|
||||
static updateCustomerAccountStatus(
|
||||
id: number,
|
||||
data: UpdateCustomerAccountStatusParams
|
||||
): Promise<BaseResponse> {
|
||||
return this.put<BaseResponse>(`/api/admin/customer-accounts/${id}/status`, data)
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ export class DeviceService extends BaseService {
|
||||
/**
|
||||
* 设置切卡模式
|
||||
* @param identifier 设备标识(虚拟号 / IMEI / SN)
|
||||
* @param switchMode 切卡模式(0=自动切卡,1=手动切卡)
|
||||
* @param data
|
||||
*/
|
||||
static setSwitchMode(
|
||||
identifier: string,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import type { BaseResponse, PaginationResponse } from '@/types/api'
|
||||
import type { BaseResponse } from '@/types/api'
|
||||
|
||||
// 换货单查询参数
|
||||
export interface ExchangeQueryParams {
|
||||
|
||||
@@ -2,21 +2,15 @@
|
||||
* API 服务模块统一导出
|
||||
*/
|
||||
|
||||
// 旧模块(待重构)
|
||||
export * from './article'
|
||||
|
||||
// 新模块
|
||||
export { AuthService } from './auth'
|
||||
export { RoleService } from './role'
|
||||
export { PermissionService } from './permission'
|
||||
export { AccountService } from './account'
|
||||
export { PlatformAccountService } from './platformAccount'
|
||||
export { ShopAccountService } from './shopAccount'
|
||||
export { ShopService } from './shop'
|
||||
export { CardService } from './card'
|
||||
export { CommissionService } from './commission'
|
||||
export { EnterpriseService } from './enterprise'
|
||||
export { CustomerAccountService } from './customerAccount'
|
||||
export { StorageService } from './storage'
|
||||
export { AuthorizationService } from './authorization'
|
||||
export { DeviceService } from './device'
|
||||
|
||||
@@ -10,10 +10,8 @@ import type {
|
||||
UpdatePackageRequest,
|
||||
UpdatePackageStatusRequest,
|
||||
UpdatePackageShelfStatusRequest,
|
||||
SeriesSelectOption,
|
||||
BaseResponse,
|
||||
PaginationResponse,
|
||||
ListResponse
|
||||
} from '@/types/api'
|
||||
|
||||
export class PackageManageService extends BaseService {
|
||||
|
||||
@@ -6,23 +6,12 @@ import { BaseService } from '../BaseService'
|
||||
import type {
|
||||
Permission,
|
||||
PermissionTreeNode,
|
||||
PermissionQueryParams,
|
||||
CreatePermissionParams,
|
||||
UpdatePermissionParams,
|
||||
BaseResponse,
|
||||
PaginationResponse
|
||||
BaseResponse
|
||||
} from '@/types/api'
|
||||
|
||||
export class PermissionService extends BaseService {
|
||||
/**
|
||||
* 获取权限列表(分页)
|
||||
* GET /api/admin/permissions
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getPermissions(params?: PermissionQueryParams): Promise<PaginationResponse<Permission>> {
|
||||
return this.getPage<Permission>('/api/admin/permissions', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限树
|
||||
* GET /api/admin/permissions/tree
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/**
|
||||
* 平台账号相关 API - 匹配后端实际接口
|
||||
*
|
||||
* @deprecated 此 API 已废弃,请使用统一的 AccountService 代替
|
||||
* @see AccountService - 统一账号管理接口 (/api/admin/accounts)
|
||||
*
|
||||
* 迁移说明:
|
||||
* - 所有账号类型统一使用 /api/admin/accounts 接口
|
||||
* - 通过 user_type 参数区分账号类型 (2=平台, 3=代理, 4=企业)
|
||||
* - 详见:docs/迁移指南.md
|
||||
*/
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import type {
|
||||
PlatformAccountResponse,
|
||||
PlatformAccountQueryParams,
|
||||
CreatePlatformAccountParams,
|
||||
UpdatePlatformAccountParams,
|
||||
ChangePlatformAccountPasswordParams,
|
||||
AssignRolesParams,
|
||||
UpdateAccountStatusParams,
|
||||
PlatformAccountRoleResponse,
|
||||
BaseResponse,
|
||||
PaginationResponse
|
||||
} from '@/types/api'
|
||||
|
||||
export class PlatformAccountService extends BaseService {
|
||||
/**
|
||||
* 1. 获取平台账号列表
|
||||
* GET /api/admin/platform-accounts
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getPlatformAccounts(
|
||||
params?: PlatformAccountQueryParams
|
||||
): Promise<PaginationResponse<PlatformAccountResponse>> {
|
||||
return this.getPage<PlatformAccountResponse>('/api/admin/platform-accounts', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 新增平台账号
|
||||
* POST /api/admin/platform-accounts
|
||||
* @param data 账号数据
|
||||
*/
|
||||
static createPlatformAccount(
|
||||
data: CreatePlatformAccountParams
|
||||
): Promise<BaseResponse<PlatformAccountResponse>> {
|
||||
return this.post<BaseResponse<PlatformAccountResponse>>('/api/admin/platform-accounts', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 移除角色
|
||||
* DELETE /api/admin/platform-accounts/{account_id}/roles/{role_id}
|
||||
* @param accountId 账号ID
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
static removeRoleFromPlatformAccount(accountId: number, roleId: number): Promise<BaseResponse> {
|
||||
return this.delete<BaseResponse>(`/api/admin/platform-accounts/${accountId}/roles/${roleId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 4. 删除平台账号
|
||||
* DELETE /api/admin/platform-accounts/{id}
|
||||
* @param id 账号ID
|
||||
*/
|
||||
static deletePlatformAccount(id: number): Promise<BaseResponse> {
|
||||
return this.remove(`/api/admin/platform-accounts/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. 获取平台账号详情
|
||||
* GET /api/admin/platform-accounts/{id}
|
||||
* @param id 账号ID
|
||||
*/
|
||||
static getPlatformAccountDetail(id: number): Promise<BaseResponse<PlatformAccountResponse>> {
|
||||
return this.getOne<PlatformAccountResponse>(`/api/admin/platform-accounts/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 6. 编辑平台账号
|
||||
* PUT /api/admin/platform-accounts/{id}
|
||||
* @param id 账号ID
|
||||
* @param data 更新数据
|
||||
*/
|
||||
static updatePlatformAccount(
|
||||
id: number,
|
||||
data: UpdatePlatformAccountParams
|
||||
): Promise<BaseResponse<PlatformAccountResponse>> {
|
||||
return this.put<BaseResponse<PlatformAccountResponse>>(
|
||||
`/api/admin/platform-accounts/${id}`,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 7. 修改密码
|
||||
* PUT /api/admin/platform-accounts/{id}/password
|
||||
* @param id 账号ID
|
||||
* @param data 新密码
|
||||
*/
|
||||
static changePlatformAccountPassword(
|
||||
id: number,
|
||||
data: ChangePlatformAccountPasswordParams
|
||||
): Promise<BaseResponse> {
|
||||
return this.put<BaseResponse>(`/api/admin/platform-accounts/${id}/password`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 8. 获取账号角色
|
||||
* GET /api/admin/platform-accounts/{id}/roles
|
||||
* @param id 账号ID
|
||||
*/
|
||||
static getPlatformAccountRoles(id: number): Promise<BaseResponse<PlatformAccountRoleResponse[]>> {
|
||||
return this.get<BaseResponse<PlatformAccountRoleResponse[]>>(
|
||||
`/api/admin/platform-accounts/${id}/roles`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 9. 分配角色
|
||||
* POST /api/admin/platform-accounts/{id}/roles
|
||||
* @param id 账号ID
|
||||
* @param data 角色ID列表
|
||||
*/
|
||||
static assignRolesToPlatformAccount(id: number, data: AssignRolesParams): Promise<BaseResponse> {
|
||||
return this.post<BaseResponse>(`/api/admin/platform-accounts/${id}/roles`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 10. 启用/禁用账号
|
||||
* PUT /api/admin/platform-accounts/{id}/status
|
||||
* @param id 账号ID
|
||||
* @param data 状态
|
||||
*/
|
||||
static updatePlatformAccountStatus(
|
||||
id: number,
|
||||
data: UpdateAccountStatusParams
|
||||
): Promise<BaseResponse> {
|
||||
return this.put<BaseResponse>(`/api/admin/platform-accounts/${id}/status`, data)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* 代理账号相关 API - 匹配后端实际接口
|
||||
*
|
||||
* @deprecated 此 API 已废弃,请使用统一的 AccountService 代替
|
||||
* @see AccountService - 统一账号管理接口 (/api/admin/accounts)
|
||||
*
|
||||
* 迁移说明:
|
||||
* - 所有账号类型统一使用 /api/admin/accounts 接口
|
||||
* - 通过 user_type 参数区分账号类型 (2=平台, 3=代理, 4=企业)
|
||||
* - 详见:docs/迁移指南.md
|
||||
*/
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import type {
|
||||
ShopAccountResponse,
|
||||
ShopAccountQueryParams,
|
||||
CreateShopAccountParams,
|
||||
UpdateShopAccountParams,
|
||||
UpdateShopAccountPasswordParams,
|
||||
UpdateShopAccountStatusParams,
|
||||
BaseResponse,
|
||||
PaginationResponse
|
||||
} from '@/types/api'
|
||||
|
||||
export class ShopAccountService extends BaseService {
|
||||
/**
|
||||
* 获取代理账号列表
|
||||
* GET /api/admin/shop-accounts
|
||||
* @param params 查询参数
|
||||
*/
|
||||
static getShopAccounts(
|
||||
params?: ShopAccountQueryParams
|
||||
): Promise<PaginationResponse<ShopAccountResponse>> {
|
||||
return this.getPage<ShopAccountResponse>('/api/admin/shop-accounts', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建代理账号
|
||||
* POST /api/admin/shop-accounts
|
||||
* @param data 代理账号数据
|
||||
*/
|
||||
static createShopAccount(
|
||||
data: CreateShopAccountParams
|
||||
): Promise<BaseResponse<ShopAccountResponse>> {
|
||||
return this.post<BaseResponse<ShopAccountResponse>>('/api/admin/shop-accounts', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新代理账号
|
||||
* PUT /api/admin/shop-accounts/{id}
|
||||
* @param id 账号ID
|
||||
* @param data 更新数据
|
||||
*/
|
||||
static updateShopAccount(
|
||||
id: number,
|
||||
data: UpdateShopAccountParams
|
||||
): Promise<BaseResponse<ShopAccountResponse>> {
|
||||
return this.put<BaseResponse<ShopAccountResponse>>(`/api/admin/shop-accounts/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置代理账号密码
|
||||
* PUT /api/admin/shop-accounts/{id}/password
|
||||
* @param id 账号ID
|
||||
* @param data 密码数据
|
||||
*/
|
||||
static updateShopAccountPassword(
|
||||
id: number,
|
||||
data: UpdateShopAccountPasswordParams
|
||||
): Promise<BaseResponse> {
|
||||
return this.put<BaseResponse>(`/api/admin/shop-accounts/${id}/password`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用代理账号
|
||||
* PUT /api/admin/shop-accounts/{id}/status
|
||||
* @param id 账号ID
|
||||
* @param data 状态数据
|
||||
*/
|
||||
static updateShopAccountStatus(
|
||||
id: number,
|
||||
data: UpdateShopAccountStatusParams
|
||||
): Promise<BaseResponse> {
|
||||
return this.put<BaseResponse>(`/api/admin/shop-accounts/${id}/status`, data)
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 954 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 726 B |
|
Before Width: | Height: | Size: 944 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 810 B |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -362,8 +362,8 @@
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
color: var(--el-color-primary);
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
<template>
|
||||
<div class="card-banner art-custom-card">
|
||||
<div class="banner-content">
|
||||
<div class="banner-icon">
|
||||
<img :src="props.icon" :alt="props.title" />
|
||||
</div>
|
||||
<div class="banner-text">
|
||||
<p class="banner-title">{{ props.title }}</p>
|
||||
<p class="banner-description">{{ props.description }}</p>
|
||||
</div>
|
||||
<div class="banner-buttons">
|
||||
<div
|
||||
v-if="showCancel"
|
||||
class="banner-button cancel-button"
|
||||
:style="{ backgroundColor: cancelButtonColor, color: cancelButtonTextColor }"
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</div>
|
||||
<div
|
||||
class="banner-button"
|
||||
:style="{ backgroundColor: buttonColor, color: buttonTextColor }"
|
||||
@click="handleClick"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import defaultIcon from '@imgs/3d/icon1.webp'
|
||||
|
||||
interface CardBannerProps {
|
||||
icon?: string
|
||||
title: string
|
||||
description: string
|
||||
buttonText?: string
|
||||
buttonColor?: string
|
||||
buttonTextColor?: string
|
||||
showCancel?: boolean
|
||||
cancelButtonText?: string
|
||||
cancelButtonColor?: string
|
||||
cancelButtonTextColor?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<CardBannerProps>(), {
|
||||
icon: defaultIcon,
|
||||
title: '',
|
||||
description: '',
|
||||
buttonText: '重试',
|
||||
buttonColor: 'var(--main-color)',
|
||||
buttonTextColor: '#fff',
|
||||
showCancel: false,
|
||||
cancelButtonText: '取消',
|
||||
cancelButtonColor: '#f5f5f5',
|
||||
cancelButtonTextColor: '#666'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void
|
||||
(e: 'cancel'): void
|
||||
}>()
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-banner {
|
||||
padding: 3rem 0 4rem;
|
||||
background-color: var(--art-main-bg-color);
|
||||
border-radius: calc(var(--custom-radius) + 2px) !important;
|
||||
|
||||
.banner-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
width: 180px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
.banner-title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--art-text-gray-800);
|
||||
}
|
||||
|
||||
.banner-description {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--art-text-gray-600);
|
||||
}
|
||||
}
|
||||
|
||||
.banner-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.banner-button {
|
||||
display: inline-block;
|
||||
height: var(--el-component-custom-height);
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
line-height: var(--el-component-custom-height);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-radius: 6px;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&.cancel-button {
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,9 @@
|
||||
<div class="chat-messages" ref="messageContainer">
|
||||
<template v-for="(message, index) in messages" :key="index">
|
||||
<div :class="['message-item', message.isMe ? 'message-right' : 'message-left']">
|
||||
<el-avatar :size="32" :src="message.avatar" class="message-avatar" />
|
||||
<el-avatar :size="32" class="message-avatar">
|
||||
{{ message.sender.slice(0, 1) }}
|
||||
</el-avatar>
|
||||
<div class="message-content">
|
||||
<div class="message-info">
|
||||
<span class="sender-name">{{ message.sender }}</span>
|
||||
@@ -67,8 +69,6 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Picture, Paperclip } from '@element-plus/icons-vue'
|
||||
import { mittBus } from '@/utils/sys'
|
||||
import meAvatar from '@/assets/img/avatar/avatar5.webp'
|
||||
import aiAvatar from '@/assets/img/avatar/avatar10.webp'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 500)
|
||||
@@ -86,79 +86,68 @@
|
||||
sender: 'Art Bot',
|
||||
content: '你好!我是你的AI助手,有什么我可以帮你的吗?',
|
||||
time: '10:00',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
isMe: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sender: 'Ricky',
|
||||
content: '我想了解一下系统的使用方法。',
|
||||
time: '10:01',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
isMe: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
sender: 'Art Bot',
|
||||
content: '好的,我来为您介绍系统的主要功能。首先,您可以通过左侧菜单访问不同的功能模块...',
|
||||
time: '10:02',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
isMe: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
sender: 'Ricky',
|
||||
content: '听起来很不错,能具体讲讲数据分析部分吗?',
|
||||
time: '10:05',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
isMe: true
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
sender: 'Art Bot',
|
||||
content: '当然可以。数据分析模块可以帮助您实时监控关键指标,并生成详细的报表...',
|
||||
time: '10:06',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
isMe: false
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
sender: 'Ricky',
|
||||
content: '太好了,那我如何开始使用呢?',
|
||||
time: '10:08',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
isMe: true
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
sender: 'Art Bot',
|
||||
content: '您可以先创建一个项目,然后在项目中添加相关的数据源,系统会自动进行分析。',
|
||||
time: '10:09',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
isMe: false
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
sender: 'Ricky',
|
||||
content: '明白了,谢谢你的帮助!',
|
||||
time: '10:10',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
isMe: true
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
sender: 'Art Bot',
|
||||
content: '不客气,有任何问题随时联系我。',
|
||||
time: '10:11',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
isMe: false
|
||||
}
|
||||
])
|
||||
|
||||
const messageId = ref(10) // 用于生成唯一的消息ID
|
||||
|
||||
const userAvatar = ref(meAvatar) // 使用导入的头像
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = () => {
|
||||
const text = messageText.value.trim()
|
||||
@@ -169,8 +158,7 @@
|
||||
sender: 'Ricky',
|
||||
content: text,
|
||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||
isMe: true,
|
||||
avatar: userAvatar.value
|
||||
isMe: true
|
||||
})
|
||||
|
||||
messageText.value = ''
|
||||
|
||||
@@ -78,13 +78,6 @@
|
||||
|
||||
const applications = computed<Application[]>(() => {
|
||||
const apps: Application[] = [
|
||||
{
|
||||
name: '聊天',
|
||||
description: '即时通讯功能',
|
||||
icon: '',
|
||||
iconColor: '#13DEB9',
|
||||
path: RoutesAlias.Chat
|
||||
},
|
||||
{
|
||||
name: '官方文档',
|
||||
description: '使用指南与开发文档',
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
CIRCLE: { SIZE: 12 }, // 圆形直径
|
||||
TRIANGLE: { SIZE: 10 }, // 三角形边长
|
||||
OVAL: { WIDTH: 24, HEIGHT: 12 }, // 椭圆宽高
|
||||
IMAGE: { WIDTH: 30, HEIGHT: 30 } // 图片尺寸
|
||||
IMAGE: { WIDTH: 30, HEIGHT: 30 } // 保留尺寸配置,兼容历史粒子类型
|
||||
},
|
||||
// 旋转相关参数
|
||||
ROTATION: {
|
||||
@@ -61,32 +61,6 @@
|
||||
]
|
||||
}
|
||||
|
||||
// 图片缓存
|
||||
const imageCache: { [url: string]: HTMLImageElement } = {}
|
||||
|
||||
// 预加载图片函数
|
||||
const preloadImage = (url: string): Promise<HTMLImageElement> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (imageCache[url]) {
|
||||
if (imageCache[url].complete) {
|
||||
resolve(imageCache[url])
|
||||
} else {
|
||||
imageCache[url].onload = () => resolve(imageCache[url])
|
||||
imageCache[url].onerror = reject
|
||||
}
|
||||
} else {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous' // 处理CORS
|
||||
img.src = url
|
||||
img.onload = () => {
|
||||
imageCache[url] = img
|
||||
resolve(img)
|
||||
}
|
||||
img.onerror = reject
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface Firework {
|
||||
x: number
|
||||
y: number
|
||||
@@ -99,7 +73,6 @@
|
||||
shape: string
|
||||
active: boolean
|
||||
rotationSpeed: { x: number; y: number; z: number }
|
||||
imageUrl?: string
|
||||
opacity: number // 新增透明度属性
|
||||
}
|
||||
|
||||
@@ -139,14 +112,12 @@
|
||||
}
|
||||
|
||||
// 创建烟花
|
||||
const createFirework = (imageUrl?: string) => {
|
||||
const createFirework = () => {
|
||||
// 异步创建粒子,避免阻塞主线程
|
||||
setTimeout(() => {
|
||||
const startX = Math.random() * window.innerWidth
|
||||
const startY = window.innerHeight
|
||||
|
||||
const availableShapes = imageUrl && imageCache[imageUrl] ? ['image'] : CONFIG.SHAPES
|
||||
|
||||
for (let i = 0; i < CONFIG.PARTICLES_PER_BURST; i++) {
|
||||
const particle = getParticleFromPool()
|
||||
if (!particle) continue
|
||||
@@ -167,8 +138,7 @@
|
||||
rotationX: Math.random() * 360 - 180,
|
||||
rotationY: Math.random() * 360 - 180,
|
||||
scale: 0.8 + Math.random() * 0.4,
|
||||
shape: availableShapes[Math.floor(Math.random() * availableShapes.length)],
|
||||
imageUrl: imageUrl && imageCache[imageUrl] ? imageUrl : undefined,
|
||||
shape: CONFIG.SHAPES[Math.floor(Math.random() * CONFIG.SHAPES.length)],
|
||||
rotationSpeed: {
|
||||
x:
|
||||
(Math.random() * CONFIG.ROTATION.RANDOM_SPEED + CONFIG.ROTATION.BASE_SPEED) *
|
||||
@@ -289,20 +259,6 @@
|
||||
ctx.value.closePath()
|
||||
ctx.value.fill()
|
||||
break
|
||||
case 'image':
|
||||
if (firework.imageUrl) {
|
||||
const img = imageCache[firework.imageUrl]
|
||||
if (img && img.complete) {
|
||||
ctx.value.drawImage(
|
||||
img,
|
||||
-CONFIG.SIZES.IMAGE.WIDTH / 2,
|
||||
-CONFIG.SIZES.IMAGE.HEIGHT / 2,
|
||||
CONFIG.SIZES.IMAGE.WIDTH,
|
||||
CONFIG.SIZES.IMAGE.HEIGHT
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -351,43 +307,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
import bp from '@imgs/ceremony/hb.png'
|
||||
import sd from '@imgs/ceremony/sd.png'
|
||||
import yd from '@imgs/ceremony/yd.png'
|
||||
|
||||
// 预加载所有需要的图片
|
||||
const preloadAllImages = async () => {
|
||||
const imageUrls = [bp, sd, yd]
|
||||
|
||||
try {
|
||||
await Promise.all(imageUrls.map((url) => preloadImage(url)))
|
||||
} catch (error) {
|
||||
console.error('Image preloading failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
if (canvas.value) {
|
||||
ctx.value = canvas.value.getContext('2d')
|
||||
resizeCanvas()
|
||||
}
|
||||
initParticlePool()
|
||||
|
||||
// 预加载所有图片
|
||||
await preloadAllImages()
|
||||
|
||||
animate()
|
||||
useEventListener(window, 'keydown', handleKeyPress)
|
||||
useEventListener(window, 'resize', resizeCanvas)
|
||||
|
||||
// 监听触发烟花的事件
|
||||
mittBus.on('triggerFireworks', ((event: unknown) => {
|
||||
const imageUrl = event as string | undefined
|
||||
if (imageUrl && imageCache[imageUrl]?.complete) {
|
||||
createFirework(imageUrl)
|
||||
} else {
|
||||
createFirework()
|
||||
}
|
||||
mittBus.on('triggerFireworks', (() => {
|
||||
createFirework()
|
||||
}) as Handler<unknown>)
|
||||
})
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<ul class="user-list" v-show="barActiveIndex === 1">
|
||||
<li v-for="(item, index) in msgList" :key="index">
|
||||
<div class="avatar">
|
||||
<img :src="item.avatar" />
|
||||
<span>{{ item.title.slice(0, 1) }}</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
<h4>{{ item.title }}</h4>
|
||||
@@ -89,12 +89,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import avatar1 from '@/assets/img/avatar/avatar1.webp'
|
||||
import avatar2 from '@/assets/img/avatar/avatar2.webp'
|
||||
import avatar3 from '@/assets/img/avatar/avatar3.webp'
|
||||
import avatar4 from '@/assets/img/avatar/avatar4.webp'
|
||||
import avatar5 from '@/assets/img/avatar/avatar5.webp'
|
||||
import avatar6 from '@/assets/img/avatar/avatar6.webp'
|
||||
import AppConfig from '@/config'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -169,33 +163,27 @@
|
||||
const msgList: any = [
|
||||
{
|
||||
title: '池不胖 关注了你',
|
||||
time: '2021-2-26 23:50',
|
||||
avatar: avatar1
|
||||
time: '2021-2-26 23:50'
|
||||
},
|
||||
{
|
||||
title: '唐不苦 关注了你',
|
||||
time: '2021-2-21 8:05',
|
||||
avatar: avatar2
|
||||
time: '2021-2-21 8:05'
|
||||
},
|
||||
{
|
||||
title: '中小鱼 关注了你',
|
||||
time: '2020-1-17 21:12',
|
||||
avatar: avatar3
|
||||
time: '2020-1-17 21:12'
|
||||
},
|
||||
{
|
||||
title: '何小荷 关注了你',
|
||||
time: '2021-01-14 0:20',
|
||||
avatar: avatar4
|
||||
time: '2021-01-14 0:20'
|
||||
},
|
||||
{
|
||||
title: '誶誶淰 关注了你',
|
||||
time: '2020-12-20 0:15',
|
||||
avatar: avatar5
|
||||
time: '2020-12-20 0:15'
|
||||
},
|
||||
{
|
||||
title: '冷月呆呆 关注了你',
|
||||
time: '2020-12-17 22:06',
|
||||
avatar: avatar6
|
||||
time: '2020-12-17 22:06'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem>
|
||||
<div style="font-size: 12px; color: #909399; line-height: 1.6">
|
||||
<div style="font-size: 12px; line-height: 1.6; color: #909399">
|
||||
<p>说明:</p>
|
||||
<p>• <strong>无需实名</strong>:用户使用设备无需进行实名认证</p>
|
||||
<p>• <strong>先实名后充值/购买</strong>:用户必须先完成实名认证才能进行充值或购买套餐</p>
|
||||
|
||||
@@ -39,8 +39,7 @@ export function useCeremony() {
|
||||
|
||||
const { start: startFireworks } = useTimeoutFn(() => {
|
||||
const { pause } = useIntervalFn(() => {
|
||||
// console.log(currentFestivalData.value?.image)
|
||||
mittBus.emit('triggerFireworks', currentFestivalData.value?.image)
|
||||
mittBus.emit('triggerFireworks')
|
||||
triggers++
|
||||
|
||||
if (triggers >= FESTIVAL_CONFIG.MAX_TRIGGERS) {
|
||||
|
||||
@@ -4,20 +4,16 @@
|
||||
*/
|
||||
// 图片需要在 components/Ceremony/Fireworks 文件预先定义
|
||||
import { FestivalConfig } from '@/types/config'
|
||||
import sd from '@imgs/ceremony/sd.png'
|
||||
import yd from '@imgs/ceremony/yd.png'
|
||||
|
||||
export const festivalConfigList: FestivalConfig[] = [
|
||||
{
|
||||
date: '2025-01-01',
|
||||
name: '元旦',
|
||||
image: yd,
|
||||
scrollText: '新年快乐!Art Design Pro 祝您在2025年万事如意,事业腾飞,阖家幸福,好运连连!'
|
||||
},
|
||||
{
|
||||
date: '2024-12-25',
|
||||
name: '圣诞节',
|
||||
image: sd,
|
||||
scrollText: 'Merry Christmas!Art Design Pro 祝您圣诞快乐,愿节日的欢乐与祝福如雪花般纷至沓来!'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -326,13 +326,6 @@
|
||||
"calendar": "Calendar",
|
||||
"pricing": "Pricing"
|
||||
},
|
||||
"article": {
|
||||
"title": "Article Management",
|
||||
"articleList": "Article List",
|
||||
"articleDetail": "Article Detail",
|
||||
"comment": "Comment",
|
||||
"articlePublish": "Article Publish"
|
||||
},
|
||||
"result": {
|
||||
"title": "Result Page",
|
||||
"success": "Success",
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import cover1 from '@imgs/cover/img1.webp'
|
||||
import cover2 from '@imgs/cover/img2.webp'
|
||||
import cover3 from '@imgs/cover/img3.webp'
|
||||
import cover4 from '@imgs/cover/img4.webp'
|
||||
import cover5 from '@imgs/cover/img5.webp'
|
||||
import cover6 from '@imgs/cover/img6.webp'
|
||||
import cover7 from '@imgs/cover/img7.webp'
|
||||
import cover8 from '@imgs/cover/img8.webp'
|
||||
import cover9 from '@imgs/cover/img9.webp'
|
||||
import cover10 from '@imgs/cover/img10.webp'
|
||||
import { ArticleType } from '@/api/modules'
|
||||
|
||||
export const ArticleList: ArticleType[] = [
|
||||
{
|
||||
id: 452,
|
||||
blog_class: '42',
|
||||
title: 'Node.js + Docker自动化部署',
|
||||
count: 56,
|
||||
create_time: '2024-08-26T00:00:00.000Z',
|
||||
home_img: cover1,
|
||||
brief:
|
||||
'本章将介绍 Node.js 使用 Docker 、Webhook 自动化部署、蓝绿部署、项目到服务器。1、Mac os 安装 Docker 客户端 OrbStack我这里使用的是第三方客户端,相比于官方的,较轻量,启动速度快OrbStack 是一种快速、轻便且简单的运行 Docker 容器和 Linux 的方法。使用我们的 Docker Desktop 替代方案以光速进行开发。下载地址: http',
|
||||
type_name: 'Node.js',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 451,
|
||||
blog_class: '36',
|
||||
title: 'HTTP 协议',
|
||||
count: 109,
|
||||
create_time: '2024-02-22T00:00:00.000Z',
|
||||
home_img: cover2,
|
||||
brief:
|
||||
'概念HTTP(hypertext transport protocol)协议;中文叫超文本传输协议是一种基于TCP/IP的应用层通信协议这个协议详细规定了 浏览器 和万维网 服务器 之间互相通信的规则。协议中主要规定了两个方面的内容客户端:用来向服务器发送数据,可以被称之为请求报文服务端:向客户端返回数据,可以被称之为响应报文报文:可以简单理解为就是一堆字符串请求报文的组成请求行请求头空行请求体H',
|
||||
type_name: '浏览器',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 450,
|
||||
blog_class: '40',
|
||||
title: 'MongoDB 数据库基本操作',
|
||||
count: 66,
|
||||
create_time: '2023-11-30T00:00:00.000Z',
|
||||
home_img: cover3,
|
||||
brief:
|
||||
'简介Mongodb 是什么MongoDB 是一个基于分布式文件存储的数据库,官方地址 https://www.mongodb.com/ 数据库是什么数据库(DataBase)是按照数据结构来组织、存储和管理数据的 应用程序数据库的作用数据库的主要作用就是 管理数据 ,对数据进行 增(c)、删(d)、改(u)、查(r)数据库管理数据的特点相比于纯文件管理数据,数据库管理数据有如下特点:1. 速度更快',
|
||||
type_name: 'MongoDB',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 449,
|
||||
blog_class: '40',
|
||||
title: 'Mac os 安装 MongoDB',
|
||||
count: 59,
|
||||
create_time: '2023-11-15T00:00:00.000Z',
|
||||
home_img: cover4,
|
||||
brief:
|
||||
'下载MongoDB安装包官网下载地址:https://www.mongodb.com/try/download/community?tck=docs_server安装MongoDB# 将压缩包解压到 /usr/local/目录下\nsudo tar -zxvf mongodb-macos-x86_64-5.0.24.tgz -C /usr/local\n\n# 重命名\nsudo mv mongodb-o',
|
||||
type_name: 'MongoDB',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 448,
|
||||
blog_class: '42',
|
||||
title: 'npm、yarn、nrm 常用命令',
|
||||
count: 91,
|
||||
create_time: '2023-11-07T00:00:00.000Z',
|
||||
home_img: cover5,
|
||||
brief:
|
||||
'设置镜像源#1,淘宝镜像源\nnpm config set registry https://registry.npmmirror.com\nnpm config set registry https://registry.npm.taobao.org\n\n#2,腾讯云镜像源\nnpm config set registry http://mirrors.cloud.tencent.com/npm/\n\n#',
|
||||
type_name: 'Node.js',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 447,
|
||||
blog_class: '42',
|
||||
title: 'Node.js 包管理工具',
|
||||
count: 53,
|
||||
create_time: '2023-10-31T00:00:00.000Z',
|
||||
home_img: cover6,
|
||||
brief:
|
||||
'介绍包是什么『包』英文单词是 package ,代表了一组特定功能的源码集合包管理工具管理『包』的应用软件,可以对「包」进行 下载安装 , 更新 , 删除 , 上传 等操作借助包管理工具,可以快速开发项目,提升开发效率包管理工具是一个通用的概念,很多编程语言都有包管理工具,所以 掌握好包管理工具非常重要常用的包管理工具下面列举了前端常用的包管理工具npmyarncnpmnpmnpm 全称 Node',
|
||||
type_name: 'Node.js',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 446,
|
||||
blog_class: '42',
|
||||
title: 'Node.js 模块化',
|
||||
count: 40,
|
||||
create_time: '2023-10-25T00:00:00.000Z',
|
||||
home_img: cover7,
|
||||
brief:
|
||||
'介绍什么是模块化与模块 ?将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为其中拆分出的 每个文件就是一个模块 ,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用什么是模块化项目 ?编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目模块化好处下面是模块化的一些好处:1.防止命名冲突2.高复用性3.高维护性模块暴露数据模块初体验可以通过下面的操作步骤,快速',
|
||||
type_name: 'Node.js',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 445,
|
||||
blog_class: '42',
|
||||
title: 'Node.js学习笔记',
|
||||
count: 198,
|
||||
create_time: '2023-10-15T00:00:00.000Z',
|
||||
home_img: cover8,
|
||||
brief:
|
||||
'fs 模块fs 全称为 file system ,称之为 文件系统 ,是 Node.js 中的 内置模块 ,可以对计算机中的磁盘进行操作。例如文件的创建、删除、修改移动,文件内容的写入、读取,以及文件夹的相关操作// 1 ------------------------------------------------------\n/**\n * 需求\n * 新建一个文件,写入内容\n */\n\n// 1',
|
||||
type_name: 'Node.js',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 444,
|
||||
blog_class: '41',
|
||||
title: '最好用的ChatGPT应用',
|
||||
count: 78,
|
||||
create_time: '2023-05-22T00:00:00.000Z',
|
||||
home_img: cover9,
|
||||
brief:
|
||||
'目前为止最好用的ChatGPT网站,支持6种AI模型和每日一次GPT4的使用机会,有网页版和ios应用程序,可以使用邮箱和手机号等方式注册,中国大陆可放心使用(需要科学上网),下面就介绍一下如何使用吧。网站地址:https://poe.com/进去之后可以选择邮箱或手机号等其他方式注册。除了网页版,你还可以到AppStore搜索poe下载ios客户端',
|
||||
type_name: 'GPT',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 443,
|
||||
blog_class: '35',
|
||||
title: 'Nuxt 百度收录 robots 和 sitemap',
|
||||
count: 109,
|
||||
create_time: '2023-04-07T00:00:00.000Z',
|
||||
home_img: cover10,
|
||||
brief:
|
||||
'前言robots 和 sitemap 文件,前者的作用是减少百度蜘蛛在站内的无谓爬取,后者增加百度蜘蛛在站内的有效爬取,对百度收录和自己网站的SEO推广都十分重要。robots只有一个:robot.txt,这是一个文本文件,主要利用Allow(允许)和DisAllow(禁止)两个命令,(这两个重要的是禁止),禁止百度蜘蛛爬取一些无谓的文件和文件夹,增加百度搜录速度。具体原理和写法网上去搜,制作简单',
|
||||
type_name: 'Nuxt',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 442,
|
||||
blog_class: '12',
|
||||
title: 'Vue3+TS+Vite 项目搭建笔记(更新中)',
|
||||
count: 516,
|
||||
create_time: '2023-04-03T00:00:00.000Z',
|
||||
home_img: cover4,
|
||||
brief:
|
||||
'介绍本章会教你在真实项目中如何搭建 VueRouter、Vuex、pinia、axios、主题切换等,你会见证一个后台管理系统的详细搭建过程。效果图:功能:后台管理系统常用模块登录加密多标签页全局面包屑国际化异常处理Utils工具包可配置的菜单栏徽标亮色 / 暗色 侧边栏浅色主题 / 暗黑主题丰富的个性化配置可折叠侧边栏支持内嵌页面重载当前页面动态路由支持自动重载支持多级路由嵌套及菜单栏嵌套分离路',
|
||||
type_name: 'Vue',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 441,
|
||||
blog_class: '9',
|
||||
title: 'CSS 根据系统自动切换主题方案',
|
||||
count: 184,
|
||||
create_time: '2023-04-01T00:00:00.000Z',
|
||||
home_img: cover8,
|
||||
brief:
|
||||
'原理是改变 css 变量 + window.matchMedia 来监听系统主题变,从而实现点击改变主题和监听系统主题变化1、首先定义 css 全局变量创建 variables.scss 文件light color(浅色模式)定义浅色模式下 css 主题变量dark color(深色模式)定义深色模式下 css 主题变量// css全局变量\n:root {\n // 文字大小\n --art-fo',
|
||||
type_name: 'CSS',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 440,
|
||||
blog_class: '36',
|
||||
title: '浏览器-安全',
|
||||
count: 116,
|
||||
create_time: '2023-03-28T00:00:00.000Z',
|
||||
home_img: cover2,
|
||||
brief:
|
||||
'通过这篇文章你可以了解到同源策略、跨站脚本攻击(xss)、跨域请求伪造(CSRF)以及安全沙箱相关知识;以下是本文的思维导图:(手机端可能看不清)获取高清 PDF,请在微信公众号【小狮子前端】回复【浏览器安全】同源策略什么是同源策略如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这',
|
||||
type_name: '浏览器',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 439,
|
||||
blog_class: '12',
|
||||
title: 'Vue-Router4',
|
||||
count: 135,
|
||||
create_time: '2023-02-08T00:00:00.000Z',
|
||||
home_img: cover3,
|
||||
brief:
|
||||
"路由模式构建 router.tsimport { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, createRouterMatcher } from 'vue-router'\nimport Home from '../views/home/index.vue'\nimport Login from",
|
||||
type_name: 'Vue',
|
||||
html_content: ''
|
||||
},
|
||||
{
|
||||
id: 438,
|
||||
blog_class: '10',
|
||||
title: 'Event Loop(事件循环)',
|
||||
count: 161,
|
||||
create_time: '2023-01-03T00:00:00.000Z',
|
||||
home_img: cover1,
|
||||
brief:
|
||||
'js是单线程的,一次只能执行一段代码。单线程会导致很多任务需要排队,一个个去执行,如果此时某个任务执行时间太长,就会出现阻塞,为了解决这个问题,js引入了事件循环机制。为什么要区分宏任务和微任务?js是单线程的,但是分同步异步微任务和宏任务皆为异步任务,它们都属于一个队列宏任务:script(整体代码)、setTimeout、setInterval、I/O、UI、 renderingsetImme',
|
||||
type_name: 'JavaScript',
|
||||
html_content: ''
|
||||
}
|
||||
]
|
||||
@@ -1,273 +0,0 @@
|
||||
import avatar1 from '@/assets/img/avatar/avatar1.webp'
|
||||
import avatar2 from '@/assets/img/avatar/avatar2.webp'
|
||||
import avatar3 from '@/assets/img/avatar/avatar3.webp'
|
||||
import avatar4 from '@/assets/img/avatar/avatar4.webp'
|
||||
import avatar5 from '@/assets/img/avatar/avatar5.webp'
|
||||
import avatar6 from '@/assets/img/avatar/avatar6.webp'
|
||||
import avatar7 from '@/assets/img/avatar/avatar7.webp'
|
||||
import avatar8 from '@/assets/img/avatar/avatar8.webp'
|
||||
import avatar9 from '@/assets/img/avatar/avatar9.webp'
|
||||
import avatar10 from '@/assets/img/avatar/avatar10.webp'
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
username: string
|
||||
gender: 1 | 0
|
||||
mobile: string
|
||||
email: string
|
||||
dep: string
|
||||
status: string
|
||||
create_time: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
// 用户列表
|
||||
export const ACCOUNT_TABLE_DATA: User[] = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'alexmorgan',
|
||||
gender: 1,
|
||||
mobile: '18670001591',
|
||||
email: 'alexmorgan@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-09-09 10:01:10',
|
||||
avatar: avatar1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'sophiabaker',
|
||||
gender: 1,
|
||||
mobile: '17766664444',
|
||||
email: 'sophiabaker@company.com',
|
||||
dep: '电商部',
|
||||
status: '1',
|
||||
create_time: '2020-10-10 13:01:12',
|
||||
avatar: avatar2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'liampark',
|
||||
gender: 1,
|
||||
mobile: '18670001597',
|
||||
email: 'liampark@company.com',
|
||||
dep: '人事部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 12:01:45',
|
||||
avatar: avatar3
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
username: 'oliviagrant',
|
||||
gender: 0,
|
||||
mobile: '18670001596',
|
||||
email: 'oliviagrant@company.com',
|
||||
dep: '产品部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 09:01:20',
|
||||
avatar: avatar4
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
username: 'emmawilson',
|
||||
gender: 0,
|
||||
mobile: '18670001595',
|
||||
email: 'emmawilson@company.com',
|
||||
dep: '财务部',
|
||||
status: '1',
|
||||
create_time: '2020-11-13 11:01:05',
|
||||
avatar: avatar5
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
username: 'noahevan',
|
||||
gender: 1,
|
||||
mobile: '18670001594',
|
||||
email: 'noahevan@company.com',
|
||||
dep: '运营部',
|
||||
status: '1',
|
||||
create_time: '2020-10-11 13:10:26',
|
||||
avatar: avatar6
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
username: 'avamartin',
|
||||
gender: 1,
|
||||
mobile: '18123820191',
|
||||
email: 'avamartin@company.com',
|
||||
dep: '客服部',
|
||||
status: '2',
|
||||
create_time: '2020-05-14 12:05:10',
|
||||
avatar: avatar7
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
username: 'jacoblee',
|
||||
gender: 1,
|
||||
mobile: '18670001592',
|
||||
email: 'jacoblee@company.com',
|
||||
dep: '总经办',
|
||||
status: '3',
|
||||
create_time: '2020-11-12 07:22:25',
|
||||
avatar: avatar8
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
username: 'miaclark',
|
||||
gender: 0,
|
||||
mobile: '18670001581',
|
||||
email: 'miaclark@company.com',
|
||||
dep: '研发部',
|
||||
status: '4',
|
||||
create_time: '2020-06-12 05:04:20',
|
||||
avatar: avatar9
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
username: 'ethanharris',
|
||||
gender: 1,
|
||||
mobile: '13755554444',
|
||||
email: 'ethanharris@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-11-12 16:01:10',
|
||||
avatar: avatar10
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
username: 'isabellamoore',
|
||||
gender: 1,
|
||||
mobile: '13766660000',
|
||||
email: 'isabellamoore@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 12:01:20',
|
||||
avatar: avatar6
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
username: 'masonwhite',
|
||||
gender: 1,
|
||||
mobile: '18670001502',
|
||||
email: 'masonwhite@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 12:01:20',
|
||||
avatar: avatar7
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
username: 'charlottehall',
|
||||
gender: 1,
|
||||
mobile: '13006644977',
|
||||
email: 'charlottehall@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 12:01:20',
|
||||
avatar: avatar8
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
username: 'benjaminscott',
|
||||
gender: 0,
|
||||
mobile: '13599998888',
|
||||
email: 'benjaminscott@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 12:01:20',
|
||||
avatar: avatar9
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
username: 'ameliaking',
|
||||
gender: 1,
|
||||
mobile: '13799998888',
|
||||
email: 'ameliaking@company.com',
|
||||
dep: '研发部',
|
||||
status: '1',
|
||||
create_time: '2020-11-14 12:01:20',
|
||||
avatar: avatar10
|
||||
}
|
||||
]
|
||||
|
||||
export interface Role {
|
||||
roleName: string
|
||||
roleCode: string
|
||||
des: string
|
||||
date: string
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
// 角色列表
|
||||
export const ROLE_LIST_DATA: Role[] = [
|
||||
{
|
||||
roleName: '超级管理员',
|
||||
roleCode: 'R_SUPER',
|
||||
des: '拥有系统全部权限',
|
||||
date: '2025-05-15 12:30:45',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '管理员',
|
||||
roleCode: 'R_ADMIN',
|
||||
des: '拥有系统管理权限',
|
||||
date: '2025-05-15 12:30:45',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '普通用户',
|
||||
roleCode: 'R_USER',
|
||||
des: '拥有系统普通权限',
|
||||
date: '2025-05-15 12:30:45',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '财务管理员',
|
||||
roleCode: 'R_FINANCE',
|
||||
des: '管理财务相关权限',
|
||||
date: '2025-05-16 09:15:30',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '数据分析师',
|
||||
roleCode: 'R_ANALYST',
|
||||
des: '拥有数据分析权限',
|
||||
date: '2025-05-16 11:45:00',
|
||||
enable: false
|
||||
},
|
||||
{
|
||||
roleName: '客服专员',
|
||||
roleCode: 'R_SUPPORT',
|
||||
des: '处理客户支持请求',
|
||||
date: '2025-05-17 14:30:22',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '营销经理',
|
||||
roleCode: 'R_MARKETING',
|
||||
des: '管理营销活动权限',
|
||||
date: '2025-05-17 15:10:50',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '访客用户',
|
||||
roleCode: 'R_GUEST',
|
||||
des: '仅限浏览权限',
|
||||
date: '2025-05-18 08:25:40',
|
||||
enable: false
|
||||
},
|
||||
{
|
||||
roleName: '系统维护员',
|
||||
roleCode: 'R_MAINTAINER',
|
||||
des: '负责系统维护和更新',
|
||||
date: '2025-05-18 09:50:12',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
roleName: '项目经理',
|
||||
roleCode: 'R_PM',
|
||||
des: '管理项目相关权限',
|
||||
date: '2025-05-19 13:40:35',
|
||||
enable: true
|
||||
}
|
||||
]
|
||||
@@ -5,7 +5,6 @@ import { useUserStore } from '@/store/modules/user'
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import { setWorktab } from '@/utils/navigation'
|
||||
import { setPageTitle, setSystemTheme } from '../utils/utils'
|
||||
import { menuService } from '@/api/menuApi'
|
||||
import { registerDynamicRoutes } from '../utils/registerRoutes'
|
||||
import { AppRouteRecord } from '@/types/router'
|
||||
import { RoutesAlias } from '../routesAlias'
|
||||
|
||||
@@ -805,15 +805,6 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'chat',
|
||||
// name: 'Chat',
|
||||
// component: RoutesAlias.Chat,
|
||||
// meta: {
|
||||
// title: 'menus.template.chat',
|
||||
// keepAlive: true
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'calendar',
|
||||
// name: 'Calendar',
|
||||
// component: RoutesAlias.Calendar,
|
||||
|
||||
@@ -99,7 +99,6 @@ export enum RoutesAlias {
|
||||
ContextMenu = '/widgets/context-menu', // 上下文菜单
|
||||
Qrcode = '/widgets/qrcode', // 二维码
|
||||
Drag = '/widgets/drag', // 拖拽
|
||||
Chat = '/template/chat', // 聊天
|
||||
Charts = '/template/charts', // 图表
|
||||
Map = '/template/map', // 地图
|
||||
Calendar = '/template/calendar' // 日历
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* 客户账号管理相关类型定义
|
||||
*/
|
||||
|
||||
// 客户账号信息
|
||||
export interface CustomerAccountItem {
|
||||
id: number
|
||||
username: string
|
||||
phone: string
|
||||
user_type: number // 3=代理账号, 4=企业账号
|
||||
user_type_name: string
|
||||
shop_id: number | null
|
||||
shop_name: string
|
||||
enterprise_id: number | null
|
||||
enterprise_name: string
|
||||
status: number // 0=禁用, 1=启用
|
||||
status_name: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// 客户账号分页结果
|
||||
export interface CustomerAccountPageResult {
|
||||
items: CustomerAccountItem[]
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
}
|
||||
|
||||
// 查询客户账号列表参数
|
||||
export interface CustomerAccountQueryParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
username?: string
|
||||
phone?: string
|
||||
user_type?: number | null
|
||||
shop_id?: number | null
|
||||
enterprise_id?: number | null
|
||||
status?: number | null
|
||||
}
|
||||
|
||||
// 新增代理商账号参数
|
||||
export interface CreateCustomerAccountParams {
|
||||
username: string
|
||||
phone: string
|
||||
password: string
|
||||
shop_id: number
|
||||
}
|
||||
|
||||
// 编辑账号参数
|
||||
export interface UpdateCustomerAccountParams {
|
||||
username?: string
|
||||
phone?: string
|
||||
}
|
||||
|
||||
// 修改账号密码参数
|
||||
export interface UpdateCustomerAccountPasswordParams {
|
||||
password: string
|
||||
}
|
||||
|
||||
// 修改账号状态参数
|
||||
export interface UpdateCustomerAccountStatusParams {
|
||||
status: 0 | 1
|
||||
}
|
||||
@@ -17,12 +17,6 @@ export * from './permission'
|
||||
// 账号相关
|
||||
export * from './account'
|
||||
|
||||
// 平台账号相关
|
||||
export * from './platformAccount'
|
||||
|
||||
// 代理账号相关
|
||||
export * from './shopAccount'
|
||||
|
||||
// 店铺相关
|
||||
export * from './shop'
|
||||
|
||||
@@ -56,9 +50,6 @@ export * from './commission'
|
||||
// 企业客户相关
|
||||
export * from './enterprise'
|
||||
|
||||
// 客户账号相关
|
||||
export * from './customerAccount'
|
||||
|
||||
// 设置相关
|
||||
export * from './setting'
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* 平台账号相关类型定义 - 匹配后端 API 文档
|
||||
*/
|
||||
|
||||
import { PaginationParams } from './common'
|
||||
|
||||
// 平台账号响应实体(匹配后端 ModelAccountResponse - 使用大写字段名)
|
||||
export interface PlatformAccountResponse {
|
||||
ID: number // 账号ID
|
||||
CreatedAt?: string // 创建时间
|
||||
UpdatedAt?: string // 更新时间
|
||||
DeletedAt?: string | null // 删除时间
|
||||
creator?: number // 创建人ID
|
||||
updater?: number // 更新人ID
|
||||
username: string // 用户名
|
||||
phone: string // 手机号
|
||||
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
|
||||
enterprise_id?: number | null // 关联企业ID
|
||||
shop_id?: number | null // 关联店铺ID
|
||||
status: number // 状态 (0:禁用, 1:启用)
|
||||
}
|
||||
|
||||
// 平台账号列表查询参数
|
||||
export interface PlatformAccountQueryParams extends PaginationParams {
|
||||
username?: string // 用户名模糊查询
|
||||
phone?: string // 手机号模糊查询
|
||||
status?: number | null // 状态 (0:禁用, 1:启用)
|
||||
page?: number // 页码
|
||||
page_size?: number // 每页数量
|
||||
}
|
||||
|
||||
// 创建平台账号参数(匹配后端 ModelCreateAccountRequest)
|
||||
export interface CreatePlatformAccountParams {
|
||||
username: string // 用户名
|
||||
password: string // 密码
|
||||
phone: string // 手机号
|
||||
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
|
||||
enterprise_id?: number | null // 关联企业ID(企业账号必填)
|
||||
shop_id?: number | null // 关联店铺ID(代理账号必填)
|
||||
}
|
||||
|
||||
// 更新平台账号参数(匹配后端 ModelUpdateAccountParams)
|
||||
export interface UpdatePlatformAccountParams {
|
||||
username?: string | null // 用户名
|
||||
password?: string | null // 密码
|
||||
phone?: string | null // 手机号
|
||||
status?: number | null // 状态 (0:禁用, 1:启用)
|
||||
}
|
||||
|
||||
// 修改密码参数
|
||||
export interface ChangePlatformAccountPasswordParams {
|
||||
new_password: string // 新密码
|
||||
}
|
||||
|
||||
// 分配角色参数
|
||||
export interface AssignRolesParams {
|
||||
role_ids: number[] // 角色ID列表
|
||||
}
|
||||
|
||||
// 启用/禁用账号参数
|
||||
export interface UpdateAccountStatusParams {
|
||||
status: number // 状态 (0:禁用, 1:启用)
|
||||
}
|
||||
|
||||
// 平台账号角色响应
|
||||
export interface PlatformAccountRoleResponse {
|
||||
creator?: number // 创建人ID
|
||||
role_desc?: string // 角色描述
|
||||
role_name?: string // 角色名称
|
||||
role_type?: number // 角色类型
|
||||
status?: number // 状态
|
||||
updater?: number // 更新人ID
|
||||
ID?: number // 角色ID
|
||||
}
|
||||
|
||||
// 平台账号列表分页响应(匹配后端 ModelAccountPageResult)
|
||||
export interface PlatformAccountPageResult {
|
||||
items: PlatformAccountResponse[] | null // 账号列表
|
||||
page?: number // 当前页码
|
||||
size?: number // 每页数量
|
||||
total?: number // 总记录数
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* 代理账号相关类型定义 - 匹配后端 API 文档
|
||||
*/
|
||||
|
||||
import { PaginationParams } from './common'
|
||||
|
||||
// 代理账号响应实体(根据API文档使用小写字段名)
|
||||
export interface ShopAccountResponse {
|
||||
id: number // 账号ID
|
||||
created_at: string // 创建时间
|
||||
updated_at: string // 更新时间
|
||||
username: string // 用户名
|
||||
phone: string // 手机号
|
||||
shop_id: number // 店铺ID
|
||||
user_type: number // 用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)
|
||||
status: number // 状态 (0:禁用, 1:启用)
|
||||
}
|
||||
|
||||
// 代理账号列表查询参数
|
||||
export interface ShopAccountQueryParams extends PaginationParams {
|
||||
username?: string // 用户名模糊查询
|
||||
phone?: string // 手机号精确查询
|
||||
shop_id?: number | null // 店铺ID过滤
|
||||
status?: number | null // 状态 (0:禁用, 1:启用)
|
||||
page?: number // 页码
|
||||
page_size?: number // 每页数量
|
||||
}
|
||||
|
||||
// 创建代理账号参数
|
||||
export interface CreateShopAccountParams {
|
||||
username: string // 用户名
|
||||
password: string // 密码
|
||||
phone: string // 手机号
|
||||
shop_id: number // 店铺ID
|
||||
}
|
||||
|
||||
// 更新代理账号参数
|
||||
export interface UpdateShopAccountParams {
|
||||
username: string // 用户名
|
||||
}
|
||||
|
||||
// 重置密码参数
|
||||
export interface UpdateShopAccountPasswordParams {
|
||||
new_password: string // 新密码
|
||||
}
|
||||
|
||||
// 更新状态参数
|
||||
export interface UpdateShopAccountStatusParams {
|
||||
status: number // 状态 (0:禁用, 1:启用)
|
||||
}
|
||||
|
||||
// 代理账号列表分页响应
|
||||
export interface ShopAccountPageResult {
|
||||
items: ShopAccountResponse[] | null // 账号列表
|
||||
page?: number // 当前页码
|
||||
size?: number // 每页数量
|
||||
total?: number // 总记录数
|
||||
}
|
||||
2
src/types/auto-imports.d.ts
vendored
@@ -8,7 +8,7 @@ export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElButton: (typeof import('element-plus/es'))['ElButton']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const ElMessageBox: (typeof import('element-plus/es'))['ElMessageBox']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
|
||||
52
src/types/components.d.ts
vendored
@@ -15,7 +15,6 @@ declare module 'vue' {
|
||||
ArtBreadcrumb: typeof import('./../components/core/layouts/art-breadcrumb/index.vue')['default']
|
||||
ArtButtonMore: typeof import('./../components/core/forms/ArtButtonMore.vue')['default']
|
||||
ArtButtonTable: typeof import('./../components/core/forms/ArtButtonTable.vue')['default']
|
||||
ArtCardBanner: typeof import('./../components/core/banners/ArtCardBanner.vue')['default']
|
||||
ArtChatWindow: typeof import('./../components/core/layouts/art-chat-window/index.vue')['default']
|
||||
ArtCutterImg: typeof import('./../components/core/media/ArtCutterImg.vue')['default']
|
||||
ArtDataListCard: typeof import('./../components/core/cards/ArtDataListCard.vue')['default']
|
||||
@@ -84,55 +83,7 @@ declare module 'vue' {
|
||||
CreateRefundDialog: typeof import('./../components/business/CreateRefundDialog.vue')['default']
|
||||
CustomerAccountDialog: typeof import('./../components/business/CustomerAccountDialog.vue')['default']
|
||||
DetailPage: typeof import('./../components/common/DetailPage.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ElWatermark: typeof import('element-plus/es')['ElWatermark']
|
||||
HorizontalSubmenu: typeof import('./../components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue')['default']
|
||||
ImportDialog: typeof import('./../components/business/ImportDialog.vue')['default']
|
||||
LoginLeftView: typeof import('./../components/core/views/login/LoginLeftView.vue')['default']
|
||||
@@ -156,7 +107,4 @@ declare module 'vue' {
|
||||
ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default']
|
||||
UpdateRealnameStatusDialog: typeof import('./../components/business/UpdateRealnameStatusDialog.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface MenuLayout {
|
||||
export interface FestivalConfig {
|
||||
date: string
|
||||
name: string
|
||||
image: string
|
||||
scrollText: string
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
@@ -256,15 +256,12 @@ export function formatDateTime(
|
||||
}
|
||||
}
|
||||
|
||||
const parsedDate = new Date(
|
||||
typeof date === 'number' ? normalizeTimestamp(date) : date
|
||||
)
|
||||
const parsedDate = new Date(typeof date === 'number' ? normalizeTimestamp(date) : date)
|
||||
if (Number.isNaN(parsedDate.getTime())) return '-'
|
||||
|
||||
return formatDateTimeParts(getUtcDateTimeParts(parsedDate), format)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化费率(基点 -> 百分比)
|
||||
* @param basisPoints 费率基点(100基点=1%)
|
||||
|
||||
@@ -56,9 +56,6 @@ export const isMaskedPaymentConfigValue = (value: string | undefined | null): bo
|
||||
if (!value) return false
|
||||
|
||||
return (
|
||||
value === '***' ||
|
||||
value.includes('已配置') ||
|
||||
value.includes('未配置') ||
|
||||
/\*{2,}/.test(value)
|
||||
value === '***' || value.includes('已配置') || value.includes('未配置') || /\*{2,}/.test(value)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
:label="role.role_name"
|
||||
:value="role.ID"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||
@@ -1010,13 +1010,13 @@
|
||||
width: 100%;
|
||||
|
||||
.role-item {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
@@ -1026,8 +1026,8 @@
|
||||
.role-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox),
|
||||
|
||||
@@ -251,8 +251,8 @@
|
||||
}
|
||||
|
||||
:deep(.el-card__header) .card-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.el-button {
|
||||
width: 100%;
|
||||
@@ -261,13 +261,13 @@
|
||||
}
|
||||
|
||||
.iccid-search {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.iccid-input-wrapper {
|
||||
flex-basis: auto;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
.iccid-magnifier {
|
||||
right: 0;
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
<!-- 卡专属字段 -->
|
||||
<template v-if="cardInfo?.asset_type === 'card'">
|
||||
<ElDescriptionsItem label="ICCID">{{ cardInfo?.iccid || '-' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="IMEI">{{ cardInfo?.gateway_card_imei || '-' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="IMEI">{{
|
||||
cardInfo?.gateway_card_imei || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="运营商账户">{{
|
||||
cardInfo?.carrier_name || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
@@ -182,7 +184,7 @@
|
||||
<el-icon
|
||||
class="copy-icon"
|
||||
@click="handleCopyICCID(scope.row.iccid)"
|
||||
style="cursor: pointer; color: var(--el-color-primary)"
|
||||
style=" color: var(--el-color-primary);cursor: pointer"
|
||||
>
|
||||
<CopyDocument />
|
||||
</el-icon>
|
||||
@@ -844,8 +846,8 @@
|
||||
|
||||
@media (width <= 768px) {
|
||||
.card-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.card-header-left,
|
||||
.card-header-right {
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐成本价格" v-if="isAdminOrPlatform">
|
||||
<ElDescriptionsItem label="套餐成本价格" v-if="isAdminOrPlatform">
|
||||
{{ formatAmount(currentPackage.paid_amount || 0) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐零售价">
|
||||
@@ -285,7 +285,8 @@
|
||||
hasAuth('asset_info:view_current_package_virtual_usage')
|
||||
)
|
||||
const canShowCurrentPackageVirtualUsage = computed(
|
||||
() => canViewCurrentPackageVirtualUsage.value && props.currentPackage?.enable_virtual_data !== false
|
||||
() =>
|
||||
canViewCurrentPackageVirtualUsage.value && props.currentPackage?.enable_virtual_data !== false
|
||||
)
|
||||
const shouldShowTrafficCard = computed(() => {
|
||||
if (!isAdminOrPlatform.value) return true
|
||||
@@ -322,11 +323,11 @@
|
||||
|
||||
<style scoped lang="scss">
|
||||
.current-package-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.card-header {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
@@ -336,24 +337,24 @@
|
||||
|
||||
.flow-progress-card {
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.flow-stats-container {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
min-width: 0;
|
||||
.flow-stats-container {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
min-width: 0;
|
||||
|
||||
.flow-stat-section {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.flow-stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.flow-stat-row {
|
||||
@@ -364,8 +365,8 @@
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
align-items: baseline;
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
@@ -390,8 +391,8 @@
|
||||
|
||||
.flow-stat-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
|
||||
.flow-stat-label {
|
||||
font-size: 13px;
|
||||
@@ -410,16 +411,16 @@
|
||||
padding: 0 4px;
|
||||
|
||||
.progress-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
align-items: center;
|
||||
|
||||
.progress-track {
|
||||
position: relative;
|
||||
@@ -429,13 +430,13 @@
|
||||
.breakpoint-marker {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
cursor: pointer;
|
||||
background-color: var(--el-color-danger);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,8 +473,8 @@
|
||||
|
||||
@media (width <= 768px) {
|
||||
.card-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
@@ -490,9 +491,9 @@
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
|
||||
.progress-percentage {
|
||||
min-width: 0;
|
||||
|
||||
@@ -604,10 +604,10 @@
|
||||
.operation-logs-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
@@ -616,9 +616,9 @@
|
||||
|
||||
.filter-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
|
||||
@@ -651,20 +651,20 @@
|
||||
|
||||
.change-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
|
||||
.field-name {
|
||||
color: var(--el-text-color-secondary);
|
||||
min-width: 72px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.field-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
word-break: break-all;
|
||||
|
||||
.new-value {
|
||||
@@ -691,8 +691,8 @@
|
||||
|
||||
@media (width <= 1200px) {
|
||||
.card-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.filter-section {
|
||||
justify-content: flex-start;
|
||||
@@ -703,12 +703,12 @@
|
||||
|
||||
@media (width <= 768px) {
|
||||
.card-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.filter-section {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
:deep(.el-select),
|
||||
.el-button,
|
||||
@@ -723,9 +723,9 @@
|
||||
}
|
||||
|
||||
.logs-table-wrapper {
|
||||
padding: 0 12px;
|
||||
margin-right: -12px;
|
||||
margin-left: -12px;
|
||||
padding: 0 12px;
|
||||
|
||||
:deep(.el-pagination) {
|
||||
justify-content: flex-start !important;
|
||||
|
||||
@@ -444,8 +444,8 @@
|
||||
|
||||
.package-table-wrapper {
|
||||
.table-scroll-container {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.package-table {
|
||||
@@ -459,9 +459,9 @@
|
||||
.package-name-text {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.traffic-progress {
|
||||
@@ -489,8 +489,8 @@
|
||||
|
||||
.progress-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
align-items: center;
|
||||
|
||||
.progress-track {
|
||||
position: relative;
|
||||
@@ -500,13 +500,13 @@
|
||||
.breakpoint-marker {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
cursor: pointer;
|
||||
background-color: var(--el-color-danger);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,15 +523,15 @@
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.card-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
|
||||
.card-header-right,
|
||||
.card-header-right .el-button {
|
||||
@@ -546,15 +546,15 @@
|
||||
|
||||
.traffic-progress {
|
||||
.progress-text {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
|
||||
.progress-percentage {
|
||||
min-width: 0;
|
||||
|
||||
@@ -421,8 +421,8 @@
|
||||
|
||||
@media (width <= 1200px) {
|
||||
.wallet-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.header-left,
|
||||
.filter-section {
|
||||
@@ -437,8 +437,8 @@
|
||||
|
||||
@media (width <= 768px) {
|
||||
.wallet-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.header-left,
|
||||
.filter-section {
|
||||
@@ -446,8 +446,8 @@
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
:deep(.el-select),
|
||||
:deep(.el-date-editor),
|
||||
@@ -462,9 +462,9 @@
|
||||
}
|
||||
|
||||
.header-left {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: flex-start;
|
||||
|
||||
.wallet-divider {
|
||||
display: none;
|
||||
@@ -490,9 +490,9 @@
|
||||
|
||||
@media (width <= 480px) {
|
||||
.wallet-table-wrapper {
|
||||
padding: 0 12px;
|
||||
margin-right: -12px;
|
||||
margin-left: -12px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,17 +44,17 @@
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
<template v-if="noSeriesId">
|
||||
<div style="color: var(--el-text-color-warning); font-size: 12px; margin-top: 4px">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-warning)">
|
||||
该设备未关联套餐系列,无法购买套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="packagesEmptyWithSeriesId">
|
||||
<div style="color: var(--el-text-color-info); font-size: 12px; margin-top: 4px">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-info)">
|
||||
该系列暂无可用套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="selectedPackageIsGift">
|
||||
<div style="color: var(--el-color-warning); font-size: 12px; margin-top: 4px">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-color-warning)">
|
||||
提示: 赠送套餐只能使用线下支付方式,且必须上传支付凭证
|
||||
</div>
|
||||
</template>
|
||||
@@ -95,9 +95,7 @@
|
||||
:limit="1"
|
||||
>
|
||||
<el-icon class="payment-voucher-upload__icon"><UploadFilled /></el-icon>
|
||||
<div class="payment-voucher-upload__text">
|
||||
将支付凭证拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<div class="payment-voucher-upload__text"> 将支付凭证拖到此处,或<em>点击上传</em> </div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 jpg、png 格式,文件大小不超过 5MB</div>
|
||||
</template>
|
||||
@@ -459,8 +457,8 @@
|
||||
<style lang="scss" scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.payment-voucher-upload {
|
||||
@@ -487,8 +485,8 @@
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
color: var(--el-color-primary);
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ export function useAssetInfo() {
|
||||
order_no: pkg.order_no || '',
|
||||
package_name: pkg.package_name || '',
|
||||
paid_amount: pkg.paid_amount || 0,
|
||||
retail_amount : pkg.retail_amount || 0,
|
||||
retail_amount: pkg.retail_amount || 0,
|
||||
real_total_mb: pkg.real_total_mb || 0,
|
||||
real_used_mb: pkg.real_used_mb || 0,
|
||||
real_remaining_mb: (pkg.real_total_mb || 0) - (pkg.real_used_mb || 0),
|
||||
|
||||
@@ -690,8 +690,8 @@
|
||||
}
|
||||
|
||||
.single-card-page {
|
||||
padding: 20px;
|
||||
max-width: 100%;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
|
||||
// 资产查询卡片
|
||||
@@ -836,8 +836,8 @@
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
&.full-width {
|
||||
.info-card {
|
||||
@@ -910,10 +910,10 @@
|
||||
:deep(.el-card__header) {
|
||||
padding: 20px 24px 16px;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 16px;
|
||||
@@ -946,8 +946,8 @@
|
||||
height: 26px;
|
||||
|
||||
.el-switch__core {
|
||||
height: 26px;
|
||||
min-width: 50px;
|
||||
height: 26px;
|
||||
|
||||
.el-switch__inner {
|
||||
font-size: 13px;
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<!-- 卡列表 -->
|
||||
<div
|
||||
class="card-selection-info"
|
||||
style="display: flex; justify-content: space-between; align-items: center"
|
||||
style="display: flex; align-items: center; justify-content: space-between"
|
||||
>
|
||||
<span>已选择 {{ selectedAvailableCards.length }} 张卡</span>
|
||||
<ElButton
|
||||
@@ -1312,6 +1312,7 @@
|
||||
.enterprise-cards-page {
|
||||
.enterprise-info-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
display: none;
|
||||
}
|
||||
@@ -1319,8 +1320,8 @@
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-selection-info {
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<!-- 设备列表 -->
|
||||
<div
|
||||
class="device-selection-info"
|
||||
style="display: flex; justify-content: space-between; align-items: center"
|
||||
style="display: flex; align-items: center; justify-content: space-between"
|
||||
>
|
||||
<span>已选择 {{ selectedAvailableDevices.length }} 台设备</span>
|
||||
<ElButton
|
||||
@@ -1057,12 +1057,13 @@
|
||||
.enterprise-devices-page {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.enterprise-info-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
},
|
||||
{
|
||||
label: '流程类型',
|
||||
formatter: (_, data) => data.flow_type_name || (flowType === 'direct' ? '直接换货' : '物流换货')
|
||||
formatter: (_, data) =>
|
||||
data.flow_type_name || (flowType === 'direct' ? '直接换货' : '物流换货')
|
||||
},
|
||||
{
|
||||
label: '换货原因',
|
||||
|
||||
@@ -514,7 +514,8 @@
|
||||
prop: 'flow_type',
|
||||
label: '流程类型',
|
||||
width: 120,
|
||||
formatter: (row: any) => row.flow_type_name || (row.flow_type === 'direct' ? '直接换货' : '物流换货')
|
||||
formatter: (row: any) =>
|
||||
row.flow_type_name || (row.flow_type === 'direct' ? '直接换货' : '物流换货')
|
||||
},
|
||||
{
|
||||
prop: 'old_asset_type',
|
||||
|
||||
@@ -957,8 +957,8 @@
|
||||
|
||||
.asset-identifier-link {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
|
||||
@@ -822,13 +822,13 @@
|
||||
width: 100%;
|
||||
|
||||
.role-item {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
@@ -838,8 +838,8 @@
|
||||
.role-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox),
|
||||
|
||||
@@ -222,17 +222,17 @@
|
||||
|
||||
.stats-chart {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
align-items: flex-end;
|
||||
height: 120px;
|
||||
padding-top: 10px;
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
.bar-label {
|
||||
font-size: 11px;
|
||||
@@ -240,16 +240,16 @@
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.bar-fill {
|
||||
width: 80%;
|
||||
max-width: 24px;
|
||||
cursor: pointer;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 4px 4px 0 0;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
@@ -292,8 +292,8 @@
|
||||
}
|
||||
|
||||
.stats-chart {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<el-table-column label="头像" prop="avatar" width="150px">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center">
|
||||
<img class="avatar" :src="scope.row.avatar" />
|
||||
<span class="avatar">{{ scope.row.username.slice(0, 1) }}</span>
|
||||
<span class="user-name">{{ scope.row.username }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -49,12 +49,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, reactive } from 'vue-demi'
|
||||
import avatar1 from '@/assets/img/avatar/avatar1.webp'
|
||||
import avatar2 from '@/assets/img/avatar/avatar2.webp'
|
||||
import avatar3 from '@/assets/img/avatar/avatar3.webp'
|
||||
import avatar4 from '@/assets/img/avatar/avatar4.webp'
|
||||
import avatar5 from '@/assets/img/avatar/avatar5.webp'
|
||||
import avatar6 from '@/assets/img/avatar/avatar6.webp'
|
||||
|
||||
const radio2 = ref('本月')
|
||||
|
||||
@@ -66,8 +60,7 @@
|
||||
age: 22,
|
||||
percentage: 60,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-primary)) !important',
|
||||
avatar: avatar1
|
||||
color: 'rgb(var(--art-primary)) !important'
|
||||
},
|
||||
{
|
||||
username: '何小荷',
|
||||
@@ -76,8 +69,7 @@
|
||||
age: 21,
|
||||
percentage: 20,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-secondary)) !important',
|
||||
avatar: avatar2
|
||||
color: 'rgb(var(--art-secondary)) !important'
|
||||
},
|
||||
{
|
||||
username: '誶誶淰',
|
||||
@@ -86,8 +78,7 @@
|
||||
age: 23,
|
||||
percentage: 60,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-warning)) !important',
|
||||
avatar: avatar3
|
||||
color: 'rgb(var(--art-warning)) !important'
|
||||
},
|
||||
{
|
||||
username: '发呆草',
|
||||
@@ -96,8 +87,7 @@
|
||||
age: 28,
|
||||
percentage: 50,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-info)) !important',
|
||||
avatar: avatar4
|
||||
color: 'rgb(var(--art-info)) !important'
|
||||
},
|
||||
{
|
||||
username: '甜筒',
|
||||
@@ -106,8 +96,7 @@
|
||||
age: 26,
|
||||
percentage: 70,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-error)) !important',
|
||||
avatar: avatar5
|
||||
color: 'rgb(var(--art-error)) !important'
|
||||
},
|
||||
{
|
||||
username: '冷月呆呆',
|
||||
@@ -116,8 +105,7 @@
|
||||
age: 25,
|
||||
percentage: 90,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-success)) !important',
|
||||
avatar: avatar6
|
||||
color: 'rgb(var(--art-success)) !important'
|
||||
}
|
||||
])
|
||||
|
||||
@@ -164,8 +152,14 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: rgb(var(--art-primary));
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,14 +47,6 @@
|
||||
<!-- </el-col>-->
|
||||
<!--</el-row>-->
|
||||
|
||||
<!--<el-row :gutter="20">-->
|
||||
<!-- <el-col :md="24" :lg="8">-->
|
||||
<!-- <RecentTransaction />-->
|
||||
<!-- </el-col>-->
|
||||
<!-- <el-col :md="24" :lg="16" class="no-margin-bottom">-->
|
||||
<!-- <HotProductsList />-->
|
||||
<!-- </el-col>-->
|
||||
<!--</el-row>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,7 +63,6 @@
|
||||
import ProductSales from './widget/ProductSales.vue'
|
||||
import SalesGrowth from './widget/SalesGrowth.vue'
|
||||
import CartConversionRate from './widget/CartConversionRate.vue'
|
||||
import HotProductsList from './widget/HotProductsList.vue'
|
||||
|
||||
defineOptions({ name: 'Ecommerce' })
|
||||
</script>
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
<template>
|
||||
<div class="card art-custom-card" style="height: 27.8rem">
|
||||
<div class="card-header">
|
||||
<p class="title">热销产品</p>
|
||||
<p class="subtitle">本月销售情况</p>
|
||||
</div>
|
||||
<div class="table">
|
||||
<el-scrollbar style="height: 21.55rem">
|
||||
<art-table
|
||||
:data="tableData"
|
||||
:pagination="false"
|
||||
style="margin-top: 0 !important"
|
||||
size="large"
|
||||
:border="false"
|
||||
:stripe="false"
|
||||
:show-header-background="false"
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column label="产品" prop="product" width="220px">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center">
|
||||
<img class="product-image" :src="scope.row.image" />
|
||||
<div class="product-info">
|
||||
<div class="product-name">{{ scope.row.name }}</div>
|
||||
<div class="product-category">{{ scope.row.category }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" prop="price">
|
||||
<template #default="scope">
|
||||
<span class="price">¥{{ scope.row.price.toLocaleString() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" prop="stock">
|
||||
<template #default="scope">
|
||||
<div class="stock-badge" :class="getStockClass(scope.row.stock)">
|
||||
{{ getStockStatus(scope.row.stock) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销量" prop="sales" />
|
||||
<el-table-column label="销售趋势" width="240">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="scope.row.pro"
|
||||
:color="scope.row.color"
|
||||
:stroke-width="4"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</art-table>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue'
|
||||
|
||||
// 导入产品图片
|
||||
import product1 from '@/assets/img/3d/icon1.webp'
|
||||
import product2 from '@/assets/img/3d/icon2.webp'
|
||||
import product3 from '@/assets/img/3d/icon3.webp'
|
||||
import product4 from '@/assets/img/3d/icon4.webp'
|
||||
import product5 from '@/assets/img/3d/icon5.webp'
|
||||
import product6 from '@/assets/img/3d/icon6.webp'
|
||||
|
||||
const tableData = reactive([
|
||||
{
|
||||
name: '智能手表 Pro',
|
||||
category: '电子设备',
|
||||
price: 1299,
|
||||
stock: 156,
|
||||
sales: 423,
|
||||
percentage: 75,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-primary)) !important',
|
||||
image: product1
|
||||
},
|
||||
{
|
||||
name: '无线蓝牙耳机',
|
||||
category: '音频设备',
|
||||
price: 499,
|
||||
stock: 89,
|
||||
sales: 652,
|
||||
percentage: 85,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-success)) !important',
|
||||
image: product2
|
||||
},
|
||||
{
|
||||
name: '机械键盘',
|
||||
category: '电脑配件',
|
||||
price: 399,
|
||||
stock: 12,
|
||||
sales: 238,
|
||||
percentage: 45,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-warning)) !important',
|
||||
image: product3
|
||||
},
|
||||
{
|
||||
name: '超薄笔记本电脑',
|
||||
category: '电子设备',
|
||||
price: 5999,
|
||||
stock: 0,
|
||||
sales: 126,
|
||||
percentage: 30,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-error)) !important',
|
||||
image: product4
|
||||
},
|
||||
{
|
||||
name: '智能音箱',
|
||||
category: '智能家居',
|
||||
price: 799,
|
||||
stock: 45,
|
||||
sales: 321,
|
||||
percentage: 60,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-info)) !important',
|
||||
image: product5
|
||||
},
|
||||
{
|
||||
name: '游戏手柄',
|
||||
category: '游戏配件',
|
||||
price: 299,
|
||||
stock: 78,
|
||||
sales: 489,
|
||||
percentage: 70,
|
||||
pro: 0,
|
||||
color: 'rgb(var(--art-secondary)) !important',
|
||||
image: product6
|
||||
}
|
||||
])
|
||||
|
||||
// 根据库存获取状态文本
|
||||
const getStockStatus = (stock: number) => {
|
||||
if (stock === 0) return '缺货'
|
||||
if (stock < 20) return '低库存'
|
||||
if (stock < 50) return '适中'
|
||||
return '充足'
|
||||
}
|
||||
|
||||
// 根据库存获取状态类名
|
||||
const getStockClass = (stock: number) => {
|
||||
if (stock === 0) return 'out-of-stock'
|
||||
if (stock < 20) return 'low-stock'
|
||||
if (stock < 50) return 'medium-stock'
|
||||
return 'in-stock'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
addAnimation()
|
||||
})
|
||||
|
||||
const addAnimation = () => {
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
let item = tableData[i]
|
||||
tableData[i].pro = item.percentage
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table {
|
||||
width: 100%;
|
||||
|
||||
.card-header {
|
||||
padding-left: 25px !important;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.product-category {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stock-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.in-stock {
|
||||
color: rgb(var(--art-success));
|
||||
background-color: rgba(var(--art-success-rgb), 0.1);
|
||||
}
|
||||
|
||||
.medium-stock {
|
||||
color: rgb(var(--art-info));
|
||||
background-color: rgba(var(--art-info-rgb), 0.1);
|
||||
}
|
||||
|
||||
.low-stock {
|
||||
color: rgb(var(--art-warning));
|
||||
background-color: rgba(var(--art-warning-rgb), 0.1);
|
||||
}
|
||||
|
||||
.out-of-stock {
|
||||
color: rgb(var(--art-error));
|
||||
background-color: rgba(var(--art-error-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -879,8 +879,8 @@
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
color: var(--el-color-primary);
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,15 @@
|
||||
<script setup lang="ts">
|
||||
import { h, onMounted, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElButton, ElCard, ElIcon, ElImage, ElMessage, ElTable, ElTableColumn } from 'element-plus'
|
||||
import {
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElIcon,
|
||||
ElImage,
|
||||
ElMessage,
|
||||
ElTable,
|
||||
ElTableColumn
|
||||
} from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
@@ -207,8 +215,7 @@
|
||||
baseFields.push({
|
||||
label: '实付金额',
|
||||
prop: 'actual_paid_amount',
|
||||
formatter: (value) =>
|
||||
value !== undefined && value !== null ? formatCurrency(value) : '-'
|
||||
formatter: (value) => (value !== undefined && value !== null ? formatCurrency(value) : '-')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -254,8 +261,7 @@
|
||||
},
|
||||
{
|
||||
label: '订单渠道',
|
||||
formatter: (_, data) =>
|
||||
data.purchase_role ? getPurchaseRoleText(data.purchase_role) : '-'
|
||||
formatter: (_, data) => (data.purchase_role ? getPurchaseRoleText(data.purchase_role) : '-')
|
||||
},
|
||||
{
|
||||
label: '购买备注',
|
||||
|
||||
@@ -152,17 +152,17 @@
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
<template v-if="noSeriesId">
|
||||
<div style="color: var(--el-text-color-warning); font-size: 12px; margin-top: 4px">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-warning)">
|
||||
该设备未关联套餐系列,无法购买套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="packagesEmptyWithSeriesId">
|
||||
<div style="color: var(--el-text-color-info); font-size: 12px; margin-top: 4px">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-info)">
|
||||
该系列暂无可用套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="selectedPackageIsGift">
|
||||
<div style="color: var(--el-color-warning); font-size: 12px; margin-top: 4px">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-color-warning)">
|
||||
提示: 赠送套餐只能使用线下支付方式,且必须上传支付凭证
|
||||
</div>
|
||||
</template>
|
||||
@@ -1403,8 +1403,8 @@
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
color: var(--el-color-primary);
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1542,25 +1542,25 @@
|
||||
|
||||
.gift-switch-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gift-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--el-color-warning);
|
||||
white-space: nowrap;
|
||||
background-color: var(--el-color-warning-light-9);
|
||||
color: var(--el-color-warning);
|
||||
border-radius: 4px;
|
||||
|
||||
&--info {
|
||||
background-color: var(--el-fill-color-light);
|
||||
color: var(--el-text-color-secondary);
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.gift-hint-icon {
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
<span
|
||||
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||
style=" margin-left: 8px; font-size: 12px;color: var(--el-text-color-secondary)"
|
||||
>单位:元</span
|
||||
>
|
||||
</ElFormItem>
|
||||
@@ -138,7 +138,7 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
<span
|
||||
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||
style=" margin-left: 8px; font-size: 12px;color: var(--el-text-color-secondary)"
|
||||
>单位:元</span
|
||||
>
|
||||
</ElFormItem>
|
||||
@@ -156,7 +156,7 @@
|
||||
>
|
||||
<ElCard shadow="hover">
|
||||
<div style="display: flex; gap: 12px; align-items: flex-start">
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 12px">
|
||||
<div style=" display: flex;flex: 1; flex-direction: column; gap: 12px">
|
||||
<!-- 第一行:比较运算符和阈值 -->
|
||||
<div style="display: flex; gap: 12px">
|
||||
<div style="flex: 1">
|
||||
@@ -269,10 +269,10 @@
|
||||
@click="removeTier(index)"
|
||||
style="
|
||||
flex-shrink: 0;
|
||||
margin-top: 28px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
margin-top: 28px;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
@@ -308,7 +308,7 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
<span
|
||||
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||
style=" margin-left: 8px; font-size: 12px;color: var(--el-text-color-secondary)"
|
||||
>单位:元</span
|
||||
>
|
||||
</ElFormItem>
|
||||
@@ -362,7 +362,7 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
<span
|
||||
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||
style=" margin-left: 8px; font-size: 12px;color: var(--el-text-color-secondary)"
|
||||
>单位:月</span
|
||||
>
|
||||
</ElFormItem>
|
||||
|
||||
@@ -1147,7 +1147,7 @@
|
||||
if (suggestedRetailPrice === undefined || suggestedRetailPrice === null) {
|
||||
return '-'
|
||||
}
|
||||
return `¥${(suggestedRetailPrice * 1.5).toFixed(2)}`
|
||||
return `¥${suggestedRetailPrice.toFixed(2)}`
|
||||
}
|
||||
|
||||
// 监听套餐选择变化
|
||||
|
||||
@@ -439,13 +439,13 @@
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.grant-name {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
@@ -456,9 +456,9 @@
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
|
||||
@@ -60,14 +60,14 @@
|
||||
<template #default>
|
||||
<el-table-column v-for="col in columns" :key="col.prop || col.type" v-bind="col">
|
||||
<template v-if="col.slots?.default === 'progress'" #default="scope">
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<div style="display: flex; gap: 8px; align-items: center">
|
||||
<el-progress
|
||||
:percentage="getProgressPercentage(scope.row)"
|
||||
stroke-width="6"
|
||||
:show-text="false"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<span style="font-size: 12px; width: 50px"
|
||||
<span style=" width: 50px;font-size: 12px"
|
||||
>{{ getProgressPercentage(scope.row) }}%</span
|
||||
>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@
|
||||
label="任务类型"
|
||||
prop="task_type"
|
||||
label-width="80"
|
||||
style="margin-bottom: 0; width: 300px"
|
||||
style=" width: 300px;margin-bottom: 0"
|
||||
>
|
||||
<el-select
|
||||
v-model="batchForm.task_type"
|
||||
|
||||
@@ -585,11 +585,15 @@
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
wx_serial_no: [{ validator: requiredValidator(isWechatProvider, '证书序列号'), trigger: 'blur' }],
|
||||
wx_serial_no: [
|
||||
{ validator: requiredValidator(isWechatProvider, '证书序列号'), trigger: 'blur' }
|
||||
],
|
||||
wx_cert_content: [
|
||||
{ validator: requiredValidator(isWechatProvider, '支付证书内容'), trigger: 'blur' }
|
||||
],
|
||||
wx_key_content: [{ validator: requiredValidator(isWechatProvider, '支付密钥内容'), trigger: 'blur' }],
|
||||
wx_key_content: [
|
||||
{ validator: requiredValidator(isWechatProvider, '支付密钥内容'), trigger: 'blur' }
|
||||
],
|
||||
oa_oauth_redirect_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||||
wx_notify_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||||
ali_notify_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||||
@@ -704,7 +708,9 @@
|
||||
})
|
||||
}
|
||||
|
||||
return h(ElTag, { type: row.is_active ? 'success' : 'info' }, () => getStatusText(row.is_active))
|
||||
return h(ElTag, { type: row.is_active ? 'success' : 'info' }, () =>
|
||||
getStatusText(row.is_active)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1108,8 +1108,8 @@
|
||||
|
||||
.role-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.role-name {
|
||||
font-size: 14px;
|
||||
|
||||
@@ -1,771 +0,0 @@
|
||||
<template>
|
||||
<div class="chat" :style="{ height: containerMinHeight }">
|
||||
<ElRow>
|
||||
<ElCol :span="12">
|
||||
<div class="grid-content ep-bg-purple" />
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="grid-content ep-bg-purple-light" />
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<div class="person-list">
|
||||
<div class="person-item-header">
|
||||
<div class="user-info">
|
||||
<ElAvatar :size="50" :src="selectedPerson?.avatar" />
|
||||
<div class="user-details">
|
||||
<div class="name">{{ selectedPerson?.name }}</div>
|
||||
<div class="email">{{ selectedPerson?.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<ElInput v-model="searchQuery" placeholder="搜索联系人" prefix-icon="Search" clearable />
|
||||
</div>
|
||||
<ElDropdown trigger="click" placement="bottom-start">
|
||||
<span class="sort-btn">
|
||||
排序方式
|
||||
<ElIcon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</ElIcon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem>按时间排序</ElDropdownItem>
|
||||
<ElDropdownItem>按名称排序</ElDropdownItem>
|
||||
<ElDropdownItem>全部标为已读</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</div>
|
||||
<ElScrollbar>
|
||||
<div
|
||||
v-for="item in personList"
|
||||
:key="item.id"
|
||||
class="person-item"
|
||||
:class="{ active: selectedPerson?.id === item.id }"
|
||||
@click="selectPerson(item)"
|
||||
>
|
||||
<div class="avatar-wrapper">
|
||||
<ElAvatar :size="40" :src="item.avatar">
|
||||
{{ item.name.charAt(0) }}
|
||||
</ElAvatar>
|
||||
<div class="status-dot" :class="{ online: item.online }"></div>
|
||||
</div>
|
||||
<div class="person-info">
|
||||
<div class="info-top">
|
||||
<span class="person-name">{{ item.name }}</span>
|
||||
<span class="last-time">{{ item.lastTime }}</span>
|
||||
</div>
|
||||
<div class="info-bottom">
|
||||
<span class="email">{{ item.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
<div class="chat-modal">
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<span class="name">Art Bot</span>
|
||||
<div class="status">
|
||||
<div class="dot" :class="{ online: isOnline, offline: !isOnline }"></div>
|
||||
<span class="status-text">{{ isOnline ? '在线' : '离线' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="btn">
|
||||
<i class="iconfont-sys"></i>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<i class="iconfont-sys"></i>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<i class="iconfont-sys"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-container">
|
||||
<!-- 聊天消息区域 -->
|
||||
<div class="chat-messages" ref="messageContainer">
|
||||
<template v-for="(message, index) in messages" :key="index">
|
||||
<div :class="['message-item', message.isMe ? 'message-right' : 'message-left']">
|
||||
<ElAvatar :size="32" :src="message.avatar" class="message-avatar" />
|
||||
<div class="message-content">
|
||||
<div class="message-info">
|
||||
<span class="sender-name">{{ message.sender }}</span>
|
||||
<span class="message-time">{{ message.time }}</span>
|
||||
</div>
|
||||
<div class="message-text">{{ message.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 聊天输入区域 -->
|
||||
<div class="chat-input">
|
||||
<ElInput
|
||||
v-model="messageText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="输入消息"
|
||||
resize="none"
|
||||
@keyup.enter.prevent="sendMessage"
|
||||
>
|
||||
<template #append>
|
||||
<div class="input-actions">
|
||||
<ElButton :icon="Paperclip" circle plain />
|
||||
<ElButton :icon="Picture" circle plain />
|
||||
<ElButton type="primary" @click="sendMessage" v-ripple>发送</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElInput>
|
||||
<div class="chat-input-actions">
|
||||
<div class="left">
|
||||
<i class="iconfont-sys"></i>
|
||||
<i class="iconfont-sys"></i>
|
||||
</div>
|
||||
<ElButton type="primary" @click="sendMessage" v-ripple>发送</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Picture, Paperclip } from '@element-plus/icons-vue'
|
||||
import { mittBus } from '@/utils/sys'
|
||||
import meAvatar from '@/assets/img/avatar/avatar5.webp'
|
||||
import aiAvatar from '@/assets/img/avatar/avatar10.webp'
|
||||
import avatar2 from '@/assets/img/avatar/avatar2.webp'
|
||||
import avatar3 from '@/assets/img/avatar/avatar3.webp'
|
||||
import avatar4 from '@/assets/img/avatar/avatar4.webp'
|
||||
import avatar5 from '@/assets/img/avatar/avatar5.webp'
|
||||
import avatar6 from '@/assets/img/avatar/avatar6.webp'
|
||||
import avatar7 from '@/assets/img/avatar/avatar7.webp'
|
||||
import avatar8 from '@/assets/img/avatar/avatar8.webp'
|
||||
import avatar9 from '@/assets/img/avatar/avatar9.webp'
|
||||
import avatar10 from '@/assets/img/avatar/avatar10.webp'
|
||||
import { useCommon } from '@/composables/useCommon'
|
||||
|
||||
const { containerMinHeight } = useCommon()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
// 抽屉显示状态
|
||||
const isDrawerVisible = ref(false)
|
||||
// 是否在线
|
||||
const isOnline = ref(true)
|
||||
|
||||
interface Person {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
online?: boolean
|
||||
lastTime: string
|
||||
unread?: number
|
||||
}
|
||||
|
||||
const selectedPerson = ref<Person | null>(null)
|
||||
|
||||
const personList = ref<Person[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '梅洛迪·梅西',
|
||||
email: 'melody@altbox.com',
|
||||
avatar: meAvatar,
|
||||
online: true,
|
||||
lastTime: '20小时前',
|
||||
unread: 0
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '马克·史密斯',
|
||||
email: 'max@kt.com',
|
||||
avatar: avatar2,
|
||||
online: true,
|
||||
lastTime: '2周前',
|
||||
unread: 6
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '肖恩·宾',
|
||||
email: 'sean@dellito.com',
|
||||
avatar: avatar3,
|
||||
online: false,
|
||||
lastTime: '5小时前',
|
||||
unread: 5
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '爱丽丝·约翰逊',
|
||||
email: 'alice@domain.com',
|
||||
avatar: avatar4,
|
||||
online: true,
|
||||
lastTime: '1小时前',
|
||||
unread: 2
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '鲍勃·布朗',
|
||||
email: 'bob@domain.com',
|
||||
avatar: avatar5,
|
||||
online: false,
|
||||
lastTime: '3天前',
|
||||
unread: 1
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '查理·戴维斯',
|
||||
email: 'charlie@domain.com',
|
||||
avatar: avatar6,
|
||||
online: true,
|
||||
lastTime: '10分钟前',
|
||||
unread: 0
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '戴安娜·普林斯',
|
||||
email: 'diana@domain.com',
|
||||
avatar: avatar7,
|
||||
online: true,
|
||||
lastTime: '15分钟前',
|
||||
unread: 3
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '伊桑·亨特',
|
||||
email: 'ethan@domain.com',
|
||||
avatar: avatar8,
|
||||
online: true,
|
||||
lastTime: '5分钟前',
|
||||
unread: 0
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: '杰西卡·琼斯',
|
||||
email: 'jessica@domain.com',
|
||||
avatar: avatar9,
|
||||
online: false,
|
||||
lastTime: '1天前',
|
||||
unread: 4
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: '彼得·帕克',
|
||||
email: 'peter@domain.com',
|
||||
avatar: avatar10,
|
||||
online: true,
|
||||
lastTime: '2小时前',
|
||||
unread: 1
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: '克拉克·肯特',
|
||||
email: 'clark@domain.com',
|
||||
avatar: avatar3,
|
||||
online: true,
|
||||
lastTime: '30分钟前',
|
||||
unread: 2
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: '布鲁斯·韦恩',
|
||||
email: 'bruce@domain.com',
|
||||
avatar: avatar5,
|
||||
online: false,
|
||||
lastTime: '3天前',
|
||||
unread: 0
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: '韦德·威尔逊',
|
||||
email: 'wade@domain.com',
|
||||
avatar: avatar6,
|
||||
online: true,
|
||||
lastTime: '10分钟前',
|
||||
unread: 5
|
||||
}
|
||||
])
|
||||
|
||||
const selectPerson = (person: Person) => {
|
||||
selectedPerson.value = person
|
||||
}
|
||||
|
||||
// 消息相关数据
|
||||
const messageText = ref('')
|
||||
const messages = ref([
|
||||
{
|
||||
id: 1,
|
||||
sender: 'Art Bot',
|
||||
content: '你好!我是你的AI助手,有什么我可以帮你的吗?',
|
||||
time: '10:00',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sender: 'Ricky',
|
||||
content: '我想了解一下系统的使用方法。',
|
||||
time: '10:01',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
sender: 'Art Bot',
|
||||
content: '好的,我来为您介绍系统的主要功能。首先,您可以通过左侧菜单访问不同的功能模块...',
|
||||
time: '10:02',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
sender: 'Ricky',
|
||||
content: '听起来很不错,能具体讲讲数据分析部分吗?',
|
||||
time: '10:05',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
sender: 'Art Bot',
|
||||
content: '当然可以。数据分析模块可以帮助您实时监控关键指标,并生成详细的报表...',
|
||||
time: '10:06',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
sender: 'Ricky',
|
||||
content: '太好了,那我如何开始使用呢?',
|
||||
time: '10:08',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
sender: 'Art Bot',
|
||||
content: '您可以先创建一个项目,然后在项目中添加相关的数据源,系统会自动进行分析。',
|
||||
time: '10:09',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
sender: 'Ricky',
|
||||
content: '明白了,谢谢你的帮助!',
|
||||
time: '10:10',
|
||||
isMe: true,
|
||||
avatar: meAvatar
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
sender: 'Art Bot',
|
||||
content: '不客气,有任何问题随时联系我。',
|
||||
time: '10:11',
|
||||
isMe: false,
|
||||
avatar: aiAvatar
|
||||
}
|
||||
])
|
||||
|
||||
const messageId = ref(10) // 用于生成唯一的消息ID
|
||||
|
||||
const userAvatar = ref(meAvatar) // 使用导入的头像
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = () => {
|
||||
const text = messageText.value.trim()
|
||||
if (!text) return
|
||||
|
||||
messages.value.push({
|
||||
id: messageId.value++,
|
||||
sender: 'Ricky',
|
||||
content: text,
|
||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||
isMe: true,
|
||||
avatar: userAvatar.value
|
||||
})
|
||||
|
||||
messageText.value = ''
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
const messageContainer = ref<HTMLElement | null>(null)
|
||||
const scrollToBottom = () => {
|
||||
setTimeout(() => {
|
||||
if (messageContainer.value) {
|
||||
messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const openChat = () => {
|
||||
isDrawerVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scrollToBottom()
|
||||
mittBus.on('openChat', openChat)
|
||||
|
||||
selectedPerson.value = personList.value[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.chat-modal {
|
||||
.el-overlay {
|
||||
background-color: rgb(0 0 0 / 20%) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chat {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
background-color: var(--art-main-bg-color);
|
||||
border: 1px solid var(--art-border-color);
|
||||
border-radius: 10px;
|
||||
|
||||
.person-list {
|
||||
box-sizing: border-box;
|
||||
width: 360px;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
border-right: 1px solid var(--art-border-color);
|
||||
|
||||
.person-item-header {
|
||||
padding-bottom: 20px;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.user-details {
|
||||
.name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--art-gray-900);
|
||||
}
|
||||
|
||||
.email {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--art-gray-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.sort-btn {
|
||||
margin-top: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.person-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
position: relative;
|
||||
margin-right: 12px;
|
||||
|
||||
.status-dot {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-color: var(--el-color-error);
|
||||
border-radius: 50%;
|
||||
|
||||
&.online {
|
||||
background-color: var(--el-color-success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.person-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.info-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.person-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.last-time {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.info-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.email {
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
:deep(.el-badge__content) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-modal {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.header-left {
|
||||
.name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.online {
|
||||
background-color: var(--el-color-success);
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background-color: var(--el-color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 12px;
|
||||
color: var(--art-gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
.btn {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--art-gray-200);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: var(--art-text-gray-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 85px);
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
padding: 30px 16px;
|
||||
overflow-y: auto;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px !important;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.message-text {
|
||||
font-size: 14px;
|
||||
color: var(--art-gray-900);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&.message-left {
|
||||
justify-content: flex-start;
|
||||
|
||||
.message-content {
|
||||
align-items: flex-start;
|
||||
|
||||
.message-info {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
background-color: var(--art-gray-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.message-right {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.message-content {
|
||||
align-items: flex-end;
|
||||
|
||||
.message-info {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
background-color: #e9f3ff;
|
||||
background-color: rgb(var(--art-bg-secondary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 70%;
|
||||
|
||||
.message-info {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
.message-time {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.sender-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.message-text {
|
||||
padding: 10px 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 16px; // 增加填充以提升输入区域的布局
|
||||
|
||||
.input-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.chat-input-actions {
|
||||
display: flex;
|
||||
align-items: center; // 修正为单数
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 20px;
|
||||
font-size: 16px;
|
||||
color: var(--art-gray-500);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保发送按钮与输入框对齐
|
||||
el-button {
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $device-ipad-pro) {
|
||||
.chat {
|
||||
flex-direction: column;
|
||||
|
||||
.person-list {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
border-right: none;
|
||||
|
||||
.person-item-header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-modal {
|
||||
height: calc(70% - 30px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||