整理系统菜单及权限

This commit is contained in:
gxwebsoft
2024-05-01 16:50:27 +08:00
parent ae068890b2
commit 0bf561f544
60 changed files with 4288 additions and 2007 deletions

View File

@@ -127,3 +127,16 @@ export async function checkExistence(
}
return Promise.reject(new Error(res.data.message));
}
/**
* 恢复项目参数
*/
export async function undeleteWebsiteField(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field/undelete/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -9,10 +9,21 @@ export interface WebsiteField {
value?: string;
comments?: string;
userId?: number;
websiteId?: number;
type?: number;
status?: any;
sortNumber?: any;
createTime?: string;
deleted?: number;
}
// 约定的网站参数名称
export interface WebsiteParam {
// 网站名称
site_logo?: string;
// 登录页面标题
login_name?: string;
// 登录页面的背景图片
login_bg_img?: string;
}
/**
@@ -20,7 +31,7 @@ export interface WebsiteField {
*/
export interface WebsiteFieldParam extends PageParam {
id?: number;
name?: string;
userId?: number;
name?: string;
websiteId?: number;
}

View File

@@ -44,6 +44,26 @@ export async function getCaptcha() {
return Promise.reject(new Error(res.data.message));
}
export async function loginBySms(data: LoginParam) {
const res = await request.post<ApiResult<LoginResult>>(
SERVER_API_URL + '/loginBySms',
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
if (res.data.data?.user) {
const user = res.data.data?.user;
localStorage.setItem('TenantId', String(user.tenantId));
localStorage.setItem('Phone', String(user.phone));
localStorage.setItem('UserId', String(user.userId));
localStorage.setItem('MerchantId', String(user.merchantId));
}
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 发送短信验证码
*/

View File

@@ -1,14 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Merchant, MerchantParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询商户
*/
export async function pageMerchant(params: MerchantParam) {
const res = await request.get<ApiResult<PageResult<Merchant>>>(
MODULES_API_URL + '/shop/merchant/page',
SERVER_API_URL + '/system/merchant/page',
{
params
}
@@ -24,7 +24,7 @@ export async function pageMerchant(params: MerchantParam) {
*/
export async function listMerchant(params?: MerchantParam) {
const res = await request.get<ApiResult<Merchant[]>>(
MODULES_API_URL + '/shop/merchant',
SERVER_API_URL + '/system/merchant',
{
params
}
@@ -40,7 +40,7 @@ export async function listMerchant(params?: MerchantParam) {
*/
export async function addMerchant(data: Merchant) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant',
SERVER_API_URL + '/system/merchant',
data
);
if (res.data.code === 0) {
@@ -54,7 +54,7 @@ export async function addMerchant(data: Merchant) {
*/
export async function updateMerchant(data: Merchant) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant',
SERVER_API_URL + '/system/merchant',
data
);
if (res.data.code === 0) {
@@ -68,7 +68,7 @@ export async function updateMerchant(data: Merchant) {
*/
export async function removeMerchant(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant/' + id
SERVER_API_URL + '/system/merchant/' + id
);
if (res.data.code === 0) {
return res.data.message;
@@ -81,7 +81,7 @@ export async function removeMerchant(id?: number) {
*/
export async function removeBatchMerchant(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant/batch',
SERVER_API_URL + '/system/merchant/batch',
{
data
}
@@ -97,7 +97,7 @@ export async function removeBatchMerchant(data: (number | undefined)[]) {
*/
export async function getMerchant(id: number) {
const res = await request.get<ApiResult<Merchant>>(
MODULES_API_URL + '/shop/merchant/' + id
SERVER_API_URL + '/system/merchant/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;

View File

@@ -55,10 +55,6 @@ export interface Merchant {
// 默认商户管理角色ID
roleId?: number;
roleName?: string;
key?: number;
value?: number;
title?: string;
disabled?: boolean;
}
/**

View File

@@ -1,14 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MerchantAccount, MerchantAccountParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询商户账号
*/
export async function pageMerchantAccount(params: MerchantAccountParam) {
const res = await request.get<ApiResult<PageResult<MerchantAccount>>>(
MODULES_API_URL + '/shop/merchant-account/page',
SERVER_API_URL + '/system/merchant-account/page',
{
params
}
@@ -24,7 +24,7 @@ export async function pageMerchantAccount(params: MerchantAccountParam) {
*/
export async function listMerchantAccount(params?: MerchantAccountParam) {
const res = await request.get<ApiResult<MerchantAccount[]>>(
MODULES_API_URL + '/shop/merchant-account',
SERVER_API_URL + '/system/merchant-account',
{
params
}
@@ -40,7 +40,7 @@ export async function listMerchantAccount(params?: MerchantAccountParam) {
*/
export async function addMerchantAccount(data: MerchantAccount) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-account',
SERVER_API_URL + '/system/merchant-account',
data
);
if (res.data.code === 0) {
@@ -54,7 +54,7 @@ export async function addMerchantAccount(data: MerchantAccount) {
*/
export async function updateMerchantAccount(data: MerchantAccount) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-account',
SERVER_API_URL + '/system/merchant-account',
data
);
if (res.data.code === 0) {
@@ -68,7 +68,7 @@ export async function updateMerchantAccount(data: MerchantAccount) {
*/
export async function removeMerchantAccount(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-account/' + id
SERVER_API_URL + '/system/merchant-account/' + id
);
if (res.data.code === 0) {
return res.data.message;
@@ -81,7 +81,7 @@ export async function removeMerchantAccount(id?: number) {
*/
export async function removeBatchMerchantAccount(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-account/batch',
SERVER_API_URL + '/system/merchant-account/batch',
{
data
}
@@ -97,10 +97,26 @@ export async function removeBatchMerchantAccount(data: (number | undefined)[]) {
*/
export async function getMerchantAccount(id: number) {
const res = await request.get<ApiResult<MerchantAccount>>(
MODULES_API_URL + '/shop/merchant-account/' + id
SERVER_API_URL + '/system/merchant-account/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
export async function getMerchantAccountByPhone(params?: MerchantAccountParam) {
const res = await request.get<ApiResult<MerchantAccount>>(
SERVER_API_URL + '/system/merchant-account/getMerchantAccountByPhone',
{
params
}
);
if (res.data.code === 1) {
return null;
}
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -14,6 +14,8 @@ export interface MerchantAccount {
// 商户ID
merchantId?: number;
merchantName?: string;
// 是否需要审核
goodsReview?: boolean;
roleId?: number;
roleName?: string;
// 用户ID
@@ -35,5 +37,6 @@ export interface MerchantAccount {
*/
export interface MerchantAccountParam extends PageParam {
id?: number;
phone?: string;
keywords?: string;
}

View File

@@ -1,14 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MerchantApply, MerchantApplyParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询商户入驻申请
*/
export async function pageMerchantApply(params: MerchantApplyParam) {
const res = await request.get<ApiResult<PageResult<MerchantApply>>>(
MODULES_API_URL + '/shop/merchant-apply/page',
SERVER_API_URL + '/system/merchant-apply/page',
{
params
}
@@ -24,7 +24,7 @@ export async function pageMerchantApply(params: MerchantApplyParam) {
*/
export async function listMerchantApply(params?: MerchantApplyParam) {
const res = await request.get<ApiResult<MerchantApply[]>>(
MODULES_API_URL + '/shop/merchant-apply',
SERVER_API_URL + '/system/merchant-apply',
{
params
}
@@ -40,7 +40,7 @@ export async function listMerchantApply(params?: MerchantApplyParam) {
*/
export async function addMerchantApply(data: MerchantApply) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-apply',
SERVER_API_URL + '/system/merchant-apply',
data
);
if (res.data.code === 0) {
@@ -54,7 +54,7 @@ export async function addMerchantApply(data: MerchantApply) {
*/
export async function updateMerchantApply(data: MerchantApply) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-apply',
SERVER_API_URL + '/system/merchant-apply',
data
);
if (res.data.code === 0) {
@@ -68,7 +68,7 @@ export async function updateMerchantApply(data: MerchantApply) {
*/
export async function removeMerchantApply(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-apply/' + id
SERVER_API_URL + '/system/merchant-apply/' + id
);
if (res.data.code === 0) {
return res.data.message;
@@ -81,7 +81,7 @@ export async function removeMerchantApply(id?: number) {
*/
export async function removeBatchMerchantApply(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-apply/batch',
SERVER_API_URL + '/system/merchant-apply/batch',
{
data
}
@@ -97,7 +97,7 @@ export async function removeBatchMerchantApply(data: (number | undefined)[]) {
*/
export async function getMerchantApply(id: number) {
const res = await request.get<ApiResult<MerchantApply>>(
MODULES_API_URL + '/shop/merchant-apply/' + id
SERVER_API_URL + '/system/merchant-apply/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;

View File

@@ -1,14 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MerchantType, MerchantTypeParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询商户类型
*/
export async function pageMerchantType(params: MerchantTypeParam) {
const res = await request.get<ApiResult<PageResult<MerchantType>>>(
MODULES_API_URL + '/shop/merchant-type/page',
SERVER_API_URL + '/system/merchant-type/page',
{
params
}
@@ -24,7 +24,7 @@ export async function pageMerchantType(params: MerchantTypeParam) {
*/
export async function listMerchantType(params?: MerchantTypeParam) {
const res = await request.get<ApiResult<MerchantType[]>>(
MODULES_API_URL + '/shop/merchant-type',
SERVER_API_URL + '/system/merchant-type',
{
params
}
@@ -40,7 +40,7 @@ export async function listMerchantType(params?: MerchantTypeParam) {
*/
export async function addMerchantType(data: MerchantType) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-type',
SERVER_API_URL + '/system/merchant-type',
data
);
if (res.data.code === 0) {
@@ -54,7 +54,7 @@ export async function addMerchantType(data: MerchantType) {
*/
export async function updateMerchantType(data: MerchantType) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-type',
SERVER_API_URL + '/system/merchant-type',
data
);
if (res.data.code === 0) {
@@ -68,7 +68,7 @@ export async function updateMerchantType(data: MerchantType) {
*/
export async function removeMerchantType(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-type/' + id
SERVER_API_URL + '/system/merchant-type/' + id
);
if (res.data.code === 0) {
return res.data.message;
@@ -81,7 +81,7 @@ export async function removeMerchantType(id?: number) {
*/
export async function removeBatchMerchantType(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/merchant-type/batch',
SERVER_API_URL + '/system/merchant-type/batch',
{
data
}
@@ -97,7 +97,7 @@ export async function removeBatchMerchantType(data: (number | undefined)[]) {
*/
export async function getMerchantType(id: number) {
const res = await request.get<ApiResult<MerchantType>>(
MODULES_API_URL + '/shop/merchant-type/' + id
SERVER_API_URL + '/system/merchant-type/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;

View File

@@ -44,5 +44,6 @@ export interface ChatMessageParam extends PageParam {
formUserId?: number;
toUserId?: number;
type?: string;
status?: number;
keywords: string;
}

View File

@@ -49,6 +49,20 @@ export async function addChatMessage(data: ChatMessage) {
return Promise.reject(new Error(res.data.message));
}
/**
* 添加聊天消息表
*/
export async function addBatchChatMessage(data: ChatMessage[]) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-message/batch',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改聊天消息表
*/

View File

@@ -22,8 +22,10 @@ export interface ChatMessage {
withdraw?: number;
// 文件信息
fileInfo?: string;
toUserName?: string;
toUserName?: any;
formUserName?: string;
// 批量发送
toUserIds?: any[];
// 存在联系方式
hasContact?: number;
// 状态, 0未读, 1已读

View File

@@ -1,14 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { User, UserParam } from './model';
import { MODULES_API_URL, SERVER_API_URL } from '@/config/setting';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询用户
*/
export async function pageUsers(params: UserParam) {
const res = await request.get<ApiResult<PageResult<User>>>(
MODULES_API_URL + '/system/user/page',
SERVER_API_URL + '/system/user/page',
{ params }
);
if (res.data.code === 0) {

View File

@@ -62,6 +62,7 @@ export interface User {
payTime?: string;
deliveryTime?: string;
receiptTime?: string;
merchantId?: number;
// 创建时间
createTime?: string;
// 租户ID
@@ -73,6 +74,7 @@ export interface User {
companyInfo?: Company;
planId?: number;
code?: string;
smsCode?: string;
//
remember?: boolean;
balance?: number;
@@ -101,7 +103,7 @@ export interface User {
//
truename?: string;
// 是否管理员1是2否
isAdmin?: string;
isAdmin?: boolean;
// 客户端ID
clientId?: string;
// 注册来源客户端 (APP、H5、小程序等)

View File

@@ -0,0 +1,129 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type {
WebsiteField,
WebsiteFieldParam
} from '@/api/cms/website/field/model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询项目参数
*/
export async function pageWebsiteField(params: WebsiteFieldParam) {
const res = await request.get<ApiResult<PageResult<WebsiteField>>>(
MODULES_API_URL + '/cms/website-field/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询项目参数列表
*/
export async function listWebsiteField(params?: WebsiteFieldParam) {
const res = await request.get<ApiResult<WebsiteField[]>>(
MODULES_API_URL + '/cms/website-field',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询项目参数
*/
export async function getWebsiteField(id: number) {
const res = await request.get<ApiResult<WebsiteField>>(
MODULES_API_URL + '/cms/website-field/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加项目参数
*/
export async function addWebsiteField(data: WebsiteField) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改项目参数
*/
export async function updateWebsiteField(data: WebsiteField) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除项目参数
*/
export async function removeWebsiteField(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除项目参数
*/
export async function removeBatchWebsiteField(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 检查IP是否存在
*/
export async function checkExistence(
field: string,
value: string,
id?: number
) {
const res = await request.get<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field/existence',
{
params: { field, value, id }
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,25 @@
import type { PageParam } from '@/api';
/**
* 网站参数
*/
export interface WebsiteField {
id?: number;
name?: string;
value?: string;
comments?: string;
userId?: number;
websiteId?: number;
status?: any;
sortNumber?: any;
createTime?: string;
}
/**
* 网站参数搜索条件
*/
export interface WebsiteFieldParam extends PageParam {
id?: number;
userId?: number;
websiteId?: number;
}

View File

@@ -0,0 +1,169 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Website, WebsiteParam } from './model';
import { SERVER_API_URL } from '@/config/setting';
/**
* 获取网站信息
*/
export async function getSiteInfo() {
const res = await request.get<ApiResult<Website>>(
SERVER_API_URL + '/system/website/getSiteInfo'
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 清除缓存
*/
export async function removeSiteInfoCache(key?: string) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/website/clearSiteInfo/' + key
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 分页查询网站
*/
export async function pageWebsite(params: WebsiteParam) {
const res = await request.get<ApiResult<PageResult<Website>>>(
SERVER_API_URL + '/system/website/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询网站列表
*/
export async function listWebsite(params?: WebsiteParam) {
const res = await request.get<ApiResult<Website[]>>(
SERVER_API_URL + '/system/website',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加网站
*/
export async function addWebsite(data: Website) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/system/website',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改网站
*/
export async function updateWebsite(data: Website) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/system/website',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除网站
*/
export async function removeWebsite(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/website/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除网站
*/
export async function removeBatchWebsite(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/website/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改用户状态
*/
export async function updateWebsiteStatus(websiteId?: number, status?: number) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/system/website/status',
{
websiteId,
status
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询网站
*/
export async function getWebsite(id: number) {
const res = await request.get<ApiResult<Website>>(
SERVER_API_URL + '/system/website/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 检查IP是否存在
*/
export async function checkExistence(
field: string,
value: string,
id?: number
) {
const res = await request.get<ApiResult<unknown>>(
SERVER_API_URL + '/system/website/existence',
{
params: { field, value, id }
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,58 @@
import { WebsiteField } from '@/api/cms/website/field/model';
import { Navigation } from '@/api/cms/navigation/model';
import { Link } from '@/api/oa/link/model';
import { ArrangeCategory } from '@/api/cms/category/model';
/**
* 菜单
*/
export interface Website {
websiteId?: number;
websiteName?: string;
websiteCode?: string;
websiteIcon?: string;
websiteLogo?: string;
websiteDarkLogo?: string;
keywords?: string;
address?: string;
phone?: string;
email?: string;
websiteType?: string;
expirationTime?: string;
templateId?: string;
industryParent?: string;
industryChild?: string;
companyId?: number;
domain?: string;
icpNo?: string;
policeNo?: string;
comments?: string;
sortNumber?: number;
createTime?: string;
disabled?: boolean;
country?: string;
province?: string;
recommend?: number;
city?: string;
region?: string;
appId?: number;
fields?: WebsiteField[];
status?: number;
tenantId?: number;
tenantName?: string;
navigations?: Navigation[];
categoryList?: ArrangeCategory[];
links?: Link[];
// 配置信息
config?: any;
}
/**
* 菜单搜索参数
*/
export interface WebsiteParam {
title?: string;
path?: string;
authority?: string;
parentId?: number;
}

View File

@@ -0,0 +1,163 @@
<template>
<ele-modal
:width="750"
:visible="visible"
:maskClosable="false"
:title="title"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<ele-pro-table
ref="tableRef"
row-key="id"
:datasource="datasource"
:columns="columns"
v-model:selection="selection"
:customRow="customRow"
:pagination="false"
>
<template #toolbar>
<a-input-search
allow-clear
v-model:value="searchText"
placeholder="请输入搜索关键词"
style="width: 200px"
@search="reload"
@pressEnter="reload"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image
v-if="record.image"
:src="record.image"
:preview="false"
:width="45"
/>
</template>
</template>
</ele-pro-table>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
ColumnItem,
DatasourceFunction
} from 'ele-admin-pro/es/ele-pro-table/types';
import { pageMerchantAccount } from '@/api/shop/merchantAccount';
import { EleProTable } from 'ele-admin-pro';
import {
MerchantAccount,
MerchantAccountParam
} from '@/api/shop/merchantAccount/model';
import { getMerchantId } from '@/utils/common';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 标题
title?: string;
// 商户类型
shopType?: string;
// 修改回显的数据
data?: MerchantAccount | null;
}>();
const emit = defineEmits<{
(e: 'done', data: MerchantAccount[]): void;
(e: 'update:visible', visible: boolean): void;
}>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 搜索内容
const searchText = ref(null);
// 表格选中数据
const selection = ref<MerchantAccount[]>([]);
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'userId',
key: 'userId',
align: 'center',
width: 90
},
{
title: '姓名',
dataIndex: 'realName'
},
{
title: '联系电话',
dataIndex: 'mobile'
},
{
title: '角色',
dataIndex: 'roleName'
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where = {};
// 搜索条件
if (searchText.value) {
where.keywords = searchText.value;
}
if (props.shopType == 'empty') {
where.emptyType = true;
} else {
where.shopType = props.shopType;
}
where.merchantId = getMerchantId();
return pageMerchantAccount({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = (where?: MerchantAccountParam) => {
selection.value = [];
tableRef?.value?.reload({ page: 1, where });
};
// const onRadio = (record: MerchantAccount) => {
// updateVisible(false);
// emit('done', record);
// };
const save = () => {
updateVisible(false);
emit('done', selection.value);
};
/* 自定义行属性 */
const customRow = (record: MerchantAccount) => {
return {
// 行点击事件
// onClick: () => {
// updateVisible(false);
// emit('done', record);
// },
// 行双击事件
onDblclick: () => {
updateVisible(false);
emit('done', [record]);
}
};
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,61 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
:placeholder="placeholder"
/>
<a-button @click="openEdit">
<template #icon><BulbOutlined class="ele-text-warning" /></template>
</a-button>
</a-input-group>
<!-- 选择弹窗 -->
<SelectData
v-model:visible="showEdit"
:data="current"
:title="placeholder"
:customer-type="customerType"
@done="onChange"
/>
</div>
</template>
<script lang="ts" setup>
import { BulbOutlined } from '@ant-design/icons-vue';
import { ref } from 'vue';
import SelectData from './components/select-data.vue';
import { MerchantAccount } from '@/api/shop/merchantAccount/model';
withDefaults(
defineProps<{
value?: any;
customerType?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择商户成员'
}
);
const emit = defineEmits<{
(e: 'done', MerchantAccount): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<MerchantAccount | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: MerchantAccount) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -6,178 +6,73 @@
:trigger="['click']"
:overlay-style="{ padding: '0 10px' }"
>
<a-badge :count="unreadNum" class="ele-notice-trigger" :offset="[6, 4]">
<a-badge :count="unreadNum" dot class="ele-notice-trigger" :offset="[4, 6]">
<bell-outlined style="padding: 8px 0" />
</a-badge>
<template #overlay>
<div class="ant-dropdown-menu ele-notice-pop">
<div @click.stop="">
<a-tabs v-model:active-key="active" :centered="true">
<a-tab-pane key="notice" :tab="noticeTitle">
<a-list item-layout="horizontal" :data-source="notice">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item-meta
@click="openUrl('/user/notice?type=notice')"
:title="item.title"
:description="timeAgo(item.createTime)"
>
<template #avatar>
<a-avatar :style="{ background: item.color }">
<template #icon>
<component :is="item.icon" />
</template>
</a-avatar>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<div v-if="notice.length" class="ele-cell ele-notice-actions">
<div class="ele-cell-content" @click="clearNotice">
清空通知
</div>
<a-divider type="vertical" />
<router-link
to="/user/notice?type=notice"
class="ele-cell-content"
>
查看更多
</router-link>
</div>
</a-tab-pane>
</a-tabs>
</div>
</div>
</template>
</a-dropdown>
<!-- <chat-message-list-->
<!-- v-model:visible="visibleChatMessageList"-->
<!-- :data="currentChatConversation"-->
<!-- />-->
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { message } from 'ant-design-vue/es';
// import { useChatStore } from '@/store/modules/chat';
// import { useUserStore } from '@/store/modules/user';
// import { pageChatMessage } from '@/api/system/chat';
import { ChatMessage } from '@/api/system/chat/model';
// import { getUnReadNum, pageNotices, pageTodos } from '@/api/user/message';
import { openUrl } from '@/utils/common';
import { timeAgo } from 'ele-admin-pro';
import { useChatStore } from '@/store/modules/chat';
import { useUserStore } from '@/store/modules/user';
import { storeToRefs } from 'pinia';
// import ChatMessageList from '@/views/love/chat/components/chat-message-list.vue';
import { ChatConversation } from '@/api/system/chat/model';
const chatStore = useChatStore();
const userStore = useUserStore();
// const chatStore = useChatStore();
// const userStore = useUserStore();
// 是否显示
const visible = ref<boolean>(false);
const visibleChatMessageList = ref<boolean>(false);
const currentChatConversation = ref<ChatConversation>();
// 选项卡选中
const active = ref<string>('notice');
// 通知数据
const notice = ref<any>([]);
// 待办数据
const todo = ref<any>([]);
// 通知未读数量
const unReadNotice = ref<any>(0);
// 私信未读数量
const { unReadLetter, unReadConversations } = storeToRefs(chatStore);
const notice = ref<ChatMessage[]>([]);
chatStore.connectSocketIO(userStore.info?.userId || 0);
// 代办未读数量
const unReadTodo = ref<any>(0);
// 通知标题
const noticeTitle = computed(() => {
return '通知' + (unReadNotice.value > 0 ? `(${unReadNotice.value})` : '');
});
// chatStore.connectSocketIO(userStore.info?.userId || 0);
// 未读数量
const unreadNum = computed(() => {
return unReadNotice.value + unReadLetter.value + unReadTodo.value;
return notice.value.length;
});
const openChat = (item: ChatConversation) => {
chatStore.readConversation(item.id);
currentChatConversation.value = item;
visible.value = false;
visibleChatMessageList.value = true;
};
/* 查询数据 */
const query = () => {
// pageNotices({ status: 0 })
// .then((result) => {
// notice.value = result?.list;
// })
// .catch((e) => {
// message.error(e.message);
// });
//
// pageTodos({ status: 0 })
// .then((result) => {
// todo.value = result?.list;
// })
// .catch((e) => {
// message.error(e.message);
// });
};
// const query = () => {
// pageNotices({ status: 0 })
// .then((result) => {
// notice.value = result?.list;
// })
// .catch((e) => {
// message.error(e.message);
// });
//
// pageTodos({ status: 0 })
// .then((result) => {
// todo.value = result?.list;
// })
// .catch((e) => {
// message.error(e.message);
// });
// };
/* 查询未读数量 */
const queryUnReadNum = () => {
// getUnReadNum()
// .then((result) => {
// unReadNotice.value = result?.notice;
// unReadTodo.value = result?.todo;
// })
// .catch((e) => {
// message.error(e.message);
// });
};
// const queryUnReadNum = () => {
// const toUserId = Number(userStore.info?.userId || 0);
// console.log(toUserId);
// const status = 0;
// pageChatMessage({ toUserId, status, keywords: '' }).then((result) => {
// console.log(result);
// notice.value = result?.list;
// });
// };
queryUnReadNum();
/* 清空通知 */
const clearNotice = () => {
unReadNotice.value = 0;
};
/* 清空通知 */
const clearLetter = () => {
// unReadLetter = 0;
};
/* 清空通知 */
const clearTodo = () => {
unReadTodo.value = 0;
};
// query();
// queryUnReadNum();
</script>
<script lang="ts">
import {
BellOutlined,
NotificationFilled,
PushpinFilled,
VideoCameraFilled,
CarryOutFilled,
BellFilled
} from '@ant-design/icons-vue';
import { BellOutlined } from '@ant-design/icons-vue';
export default {
name: 'HeaderNotice',
components: {
BellOutlined,
NotificationFilled,
PushpinFilled,
VideoCameraFilled,
CarryOutFilled,
BellFilled
BellOutlined
}
};
</script>
@@ -190,7 +85,7 @@
.ele-notice-pop {
&.ant-dropdown-menu {
padding: 0;
width: 336px;
width: 286px;
max-width: 100%;
margin-top: 11px;
}
@@ -228,4 +123,9 @@
}
}
}
.ele-cell-content {
padding: 4px;
display: flex;
flex-direction: column;
}
</style>

View File

@@ -1,6 +1,11 @@
<!-- 顶栏右侧区域 -->
<template>
<div class="ele-admin-header-tool">
<div class="ele-admin-header-tool-item">
<a-button @click="openUrl(`/merchant/account`)">{{
getMerchantName()
}}</a-button>
</div>
<div class="ele-admin-header-tool-item">
<a-tree-select
show-search
@@ -24,7 +29,7 @@
<!-- 旧版-->
<!-- </div>-->
<!-- 消息通知 -->
<div class="ele-admin-header-tool-item">
<div class="ele-admin-header-tool-item" @click="openUrl(`/user/notice`)">
<header-notice />
</div>
<!-- 全屏切换 -->
@@ -137,9 +142,9 @@
</a-dropdown>
</div>
<!-- 主题设置 -->
<!-- <div class="ele-admin-header-tool-item" @click="openSetting">-->
<!-- <more-outlined />-->
<!-- </div>-->
<!-- <div class="ele-admin-header-tool-item" @click="openSetting">-->
<!-- <more-outlined />-->
<!-- </div>-->
</div>
<!-- 修改密码弹窗 -->
<password-modal v-model:visible="passwordVisible" />
@@ -164,7 +169,7 @@
SearchOutlined
} from '@ant-design/icons-vue';
import { storeToRefs } from 'pinia';
import { copyText, openNew, openUrl } from '@/utils/common';
import { copyText, getUserId, openNew, openUrl } from '@/utils/common';
import { useThemeStore } from '@/store/modules/theme';
import HeaderNotice from './header-notice.vue';
import PasswordModal from './password-modal.vue';
@@ -174,6 +179,7 @@
import type { Menu } from '@/api/system/menu/model';
import { listMenus } from '@/api/system/menu';
import { isExternalLink, toTreeData } from 'ele-admin-pro';
import { getMerchantName } from '../../utils/merchant';
// 是否开启响应式布局
const themeStore = useThemeStore();

View File

@@ -15,6 +15,7 @@ export {
BarChartOutlined,
AuditOutlined,
PicLeftOutlined,
BellOutlined,
CloseCircleOutlined,
QuestionCircleOutlined,
SoundOutlined,

View File

@@ -2,13 +2,16 @@ import { message, SelectProps } from 'ant-design-vue';
import { isExternalLink, random, toDateString } from 'ele-admin-pro';
import router from '@/router';
import { listDictionaryData } from '@/api/system/dictionary-data';
import { ref } from 'vue';
import { ref, unref } from 'vue';
import { getJson } from '@/api/json';
import { APP_SECRET, FILE_SERVER } from '@/config/setting';
import mitt from 'mitt';
import { ChatMessage } from '@/api/system/chat/model';
import { useUserStore } from '@/store/modules/user';
import CryptoJS from 'crypto-js';
// import { useTenantStore } from '@/store/modules/tenant';
// import { listMerchantAccount } from '@/api/shop/merchantAccount';
import { useRouter } from 'vue-router';
type Events = {
message: ChatMessage;
@@ -321,6 +324,39 @@ export const decrypt = (encrypt) => {
return bytes.toString(CryptoJS.enc.Utf8);
};
// 获取商户ID
export const getMerchantId = () => {
// 读取商户账号
const merchantId = localStorage.getItem('MerchantId');
const userId = localStorage.getItem('UserId');
if (Number(userId) == 3731) {
return undefined;
}
if (merchantId) {
return Number(merchantId);
}
// const tenantStore = useTenantStore();
// console.log(tenantStore);
// tenantStore.setMerchant({
// merchantId: merchantAccount.merchantId,
// merchantName: merchantAccount.merchantName
// });
// const phone = String(localStorage.getItem('Phone'));
// listMerchantAccount({ phone }).then((list) => {
// if (list.length == 0) {
// return;
// }
// const merchantAccount = list[0];
// const tenantStore = useTenantStore();
// tenantStore.setMerchant({
// merchantId: merchantAccount.merchantId,
// merchantName: merchantAccount.merchantName
// });
// localStorage.setItem('MerchantId', merchantAccount.merchantId + '');
// });
// return Number(merchantId);
};
// 获取当前登录用户ID
export const getUserId = () => {
let userId = 0;
@@ -331,3 +367,11 @@ export const getUserId = () => {
}
return userId;
};
// 获取页签数据
export const getPageTitle = () => {
const { currentRoute } = useRouter();
const { meta } = unref(currentRoute);
const { title } = meta;
return title;
};

4
src/utils/merchant.ts Normal file
View File

@@ -0,0 +1,4 @@
// 获取商户名称
export const getMerchantName = () => {
return localStorage.getItem('MerchantName');
};

View File

@@ -0,0 +1,208 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
:width="500"
:visible="visible"
:maskClosable="false"
:title="isUpdate ? '编辑字段' : '添加字段'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="{ md: { span: 4 }, sm: { span: 4 }, xs: { span: 24 } }"
:wrapper-col="{ md: { span: 21 }, sm: { span: 22 }, xs: { span: 24 } }"
>
<a-form-item label="参数名称" name="name">
<a-input
allow-clear
placeholder="site_name"
v-model:value="form.name"
/>
</a-form-item>
<a-form-item label="参数值" name="value">
<a-input
allow-clear
placeholder="填写参数内容,如:淘宝网"
v-model:value="form.value"
/>
</a-form-item>
<a-form-item label="描述" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="填写参数用途"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="(选填)">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseImage"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { FormInstance } from 'ant-design-vue/es/form';
import { WebsiteField } from '@/api/cms/website/field/model';
import useFormData from '@/utils/use-form-data';
import { addWebsiteField, updateWebsiteField } from '@/api/cms/website/field';
import { message } from 'ant-design-vue/es';
import { removeSiteInfoCache } from '@/api/cms/website';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FileRecord } from '@/api/system/file/model';
import { uuid } from 'ele-admin-pro';
// 是否是修改
const isUpdate = ref(false);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
websiteId: number | null | undefined;
// 修改回显的数据
data?: WebsiteField | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
const images = ref<ItemType[]>([]);
const formRef = ref<FormInstance | null>(null);
const { form, resetFields, assignFields } = useFormData<WebsiteField>({
id: undefined,
type: 0,
name: undefined,
value: undefined,
comments: '',
status: 0,
sortNumber: 100
});
// 表单验证规则
const rules = reactive({
name: [
{
required: true,
type: 'string',
message: '请输入参数名称(英语)'
}
],
comments: [
{
required: true,
type: 'string',
message: '请输入描述'
}
],
value: [
{
required: true,
type: 'string',
message: '请填写参数值'
}
]
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.value = data.downloadUrl;
form.type = 1;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.value = '';
form.type = 0;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const data = {
...form,
// name: form.name?.toUpperCase(),
websiteId: props.websiteId
};
const saveOrUpdate = isUpdate.value
? updateWebsiteField
: addWebsiteField;
console.log(isUpdate.value);
saveOrUpdate(data)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
// 清除缓存
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId'));
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignFields(props.data);
form.comments = props.data.comments;
images.value.push({
uid: uuid(),
url: props.data.value,
status: 'done'
});
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
}
);
</script>

View File

@@ -0,0 +1,13 @@
<template>
<a-button @click="add">添加参数</a-button>
</template>
<script lang="ts" setup>
const emit = defineEmits<{
(e: 'add'): void;
}>();
const add = () => {
emit('add');
};
</script>

View File

@@ -0,0 +1,217 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<a-card :bordered="false">
<div class="website-field">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="websiteId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<WebsiteFieldSearch @add="openEdit" />
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<div class="ele-text-heading">{{ record.name }}</div>
<span class="ele-text-placeholder">{{ record.comments }}</span>
</template>
<template v-if="column.key === 'value'">
<a-image
v-if="record.type === 1"
:src="record.value"
:width="120"
/>
<span v-else>{{ record.value }}</span>
</template>
<template v-if="column.key === 'action'">
<a @click="moveUp(record)">上移<ArrowUpOutlined /></a>
<a-divider type="vertical" />
<a @click="openEdit(record)">编辑</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
<a-divider type="vertical" />
<a-popconfirm
title="确定要放回原处吗?"
@confirm="recovery(record)"
>
<a class="ele-text-danger">恢复</a>
</a-popconfirm>
</template>
</template>
</ele-pro-table>
<!-- 编辑弹窗 -->
<WebsiteFieldEdit
v-model:visible="showEdit"
:data="current"
@done="reload"
/>
</div>
</a-card>
</a-page-header>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { EleProTable } from 'ele-admin-pro';
import type { DatasourceFunction } from 'ele-admin-pro/es/ele-pro-table/types';
import WebsiteFieldSearch from './components/website-field-search.vue';
import { Website } from '@/api/cms/website/model';
import WebsiteFieldEdit from './components/website-field-edit.vue';
import {
WebsiteField,
WebsiteFieldParam
} from '@/api/cms/website/field/model';
import {
pageWebsiteField,
removeWebsiteField,
undeleteWebsiteField,
updateWebsiteField
} from '@/api/cms/website/field';
import { ArrowUpOutlined } from '@ant-design/icons-vue';
import { getPageTitle } from '@/utils/common';
const props = defineProps<{
websiteId: any;
data: Website;
}>();
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
const selection = ref<any[]>();
// 当前编辑数据
const current = ref<WebsiteField | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
// 搜索条件
where.deleted = 1;
return pageWebsiteField({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<any[]>([
{
title: '参数名称',
dataIndex: 'name',
key: 'name'
},
{
title: '参数值',
dataIndex: 'value',
key: 'value'
},
{
title: '排序',
dataIndex: 'sortNumber',
width: 180,
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
align: 'center',
hideInSetting: true
}
]);
const moveUp = (row?: WebsiteField) => {
updateWebsiteField({
id: row?.id,
sortNumber: Number(row?.sortNumber) + 1
}).then((msg) => {
message.success(msg);
reload();
});
};
/* 打开编辑弹窗 */
const openEdit = (row?: WebsiteField) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 搜索 */
const reload = (where?: WebsiteFieldParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 删除单个 */
const remove = (row: WebsiteField) => {
const hide = message.loading('请求中..', 0);
removeWebsiteField(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
// 从回收站放回原处
const recovery = (row: WebsiteField) => {
const hide = message.loading('请求中..', 0);
undeleteWebsiteField(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 自定义行属性 */
const customRow = (record: WebsiteField) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
watch(
() => props.websiteId,
(websiteId) => {
if (websiteId) {
reload();
}
},
{ immediate: true }
);
</script>
<script lang="ts">
export default {
name: 'WebsiteFieldIndex'
};
</script>

View File

@@ -136,19 +136,6 @@
<!-- </template>-->
</a-form>
</a-tab-pane>
<a-tab-pane tab="网站参数" key="developer">
<a-form
:label-col="
styleResponsive
? { lg: 2, md: 6, sm: 4, xs: 24 }
: { flex: '100px' }
"
:wrapper-col="styleResponsive ? { offset: 1 } : { offset: 1 }"
style="margin-top: 20px"
>
<WebsiteFieldForm :website-id="form.websiteId" :data="form" @done="query" />
</a-form>
</a-tab-pane>
</a-tabs>
</a-card>
<Field

191
src/views/help/index.vue Normal file
View File

@@ -0,0 +1,191 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<a-result title="内容整理中">
<template #icon>
<SmileTwoTone />
</template>
<div>内容整理中...</div>
</a-result>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import {
ExclamationCircleOutlined,
SmileTwoTone
} from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import {
pageChatMessage,
removeChatMessage,
removeBatchChatMessage
} from '@/api/system/chatMessage';
import type {
ChatMessage,
ChatMessageParam
} from '@/api/system/chatMessage/model';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
import { getPageTitle, getUserId } from '@/utils/common';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ChatMessage[]>([]);
// 当前编辑数据
const current = ref<ChatMessage | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 页面标题
const title = getPageTitle();
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where.toUserId = getUserId();
return pageChatMessage({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '未/已读',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 90
},
{
title: '消息内容',
dataIndex: 'content',
key: 'content'
},
{
title: '发送人',
dataIndex: 'formUserName',
key: 'formUserName',
width: 180,
align: 'center'
},
{
title: '发送时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
width: 180,
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
},
{
title: '操作',
key: 'action',
width: 120,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ChatMessageParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ChatMessage) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ChatMessage) => {
const hide = message.loading('请求中..', 0);
removeChatMessage(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchChatMessage(selection.value.map((d) => d.id))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: ChatMessage) => {
return {
// 行点击事件
onClick: () => {
// openEdit(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
watch(
currentRoute,
() => {
reload();
},
{ immediate: true }
);
</script>
<script lang="ts">
export default {
name: 'ChatMessage'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,428 +0,0 @@
<template>
<div class="login-wrapper">
<a-form class="login-form ele-bg-white">
<h4>忘记密码</h4>
<a-form-item v-bind="validateInfos.phone">
<a-input
placeholder="请输入绑定手机号"
v-model:value="form.phone"
:maxlength="11"
allow-clear
size="large"
>
<template #prefix>
<mobile-outlined />
</template>
</a-input>
</a-form-item>
<a-form-item v-bind="validateInfos.password">
<a-input-password
placeholder="请输入新的登录密码"
v-model:value="form.password"
size="large"
>
<template #prefix>
<lock-outlined />
</template>
</a-input-password>
</a-form-item>
<a-form-item v-bind="validateInfos.password2">
<a-input-password
placeholder="请再次输入登录密码"
v-model:value="form.password2"
size="large"
>
<template #prefix>
<key-outlined />
</template>
</a-input-password>
</a-form-item>
<a-form-item v-bind="validateInfos.code">
<div class="login-input-group">
<a-input
placeholder="请输入验证码"
v-model:value="form.code"
:maxlength="6"
allow-clear
size="large"
>
<template #prefix>
<safety-certificate-outlined />
</template>
</a-input>
<a-button
class="login-captcha"
:disabled="!!countdownTime"
@click="openImgCodeModal"
>
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</a-button>
</div>
</a-form-item>
<a-form-item>
<router-link
to="/login"
class="ele-pull-right"
style="line-height: 22px"
>
返回登录
</router-link>
</a-form-item>
<a-form-item>
<a-button
block
size="large"
type="primary"
:loading="loading"
@click="submit"
>
修改密码
</a-button>
</a-form-item>
</a-form>
<div class="login-copyright">
{{ t('layout.footer.copyright') }}
</div>
</div>
<!-- 编辑弹窗 -->
<a-modal
:width="340"
:footer="null"
title="发送验证码"
v-model:visible="visible"
>
<div class="login-input-group" style="margin-bottom: 16px">
<a-input
v-model:value="imgCode"
placeholder="请输入图形验证码"
allow-clear
size="large"
/>
<a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" />
</a-button>
</div>
<a-button
block
size="large"
type="primary"
:loading="codeLoading"
@click="sendCode"
>
立即发送
</a-button>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, onBeforeUnmount } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { Form, message } from 'ant-design-vue';
import {
MobileOutlined,
LockOutlined,
KeyOutlined,
SafetyCertificateOutlined
} from '@ant-design/icons-vue';
import type { RuleObject } from 'ant-design-vue/es/form';
import { getCaptcha, sendSmsCaptcha, updatePassword } from '@/api/login';
import { phoneReg } from 'ele-admin-pro';
const useForm = Form.useForm;
const { push } = useRouter();
const { t } = useI18n();
// 加载状态
const loading = ref(false);
// 表单数据
const form = reactive({
phone: '',
password: '',
password2: '',
code: '',
oldPassword: ''
});
// 表单验证规则
const rules = reactive({
phone: [
{
required: true,
message: '请输入绑定手机号',
type: 'string'
},
{
pattern: phoneReg,
message: '手机号格式不正确',
type: 'string'
}
],
password: [
{
required: true,
message: '请输入新的登录密码',
type: 'string'
}
],
password2: [
{
required: true,
validator: async (_rule: RuleObject, value: string) => {
if (!value) {
return Promise.reject('请再次输入新密码');
}
if (value !== form.password) {
return Promise.reject('两次输入密码不一致');
}
return Promise.resolve();
}
}
],
code: [
{
required: true,
message: '请输入验证码',
type: 'string'
}
]
});
// 是否显示图形验证码弹窗
const visible = ref(false);
// 图形验证码
const imgCode = ref('');
// 发送验证码按钮loading
const codeLoading = ref(false);
// 验证码倒计时时间
const countdownTime = ref(0);
// 图形验证码地址
const captcha = ref('');
// 验证码倒计时定时器
let countdownTimer: number | null = null;
const { validate, validateInfos, clearValidate } = useForm(form, rules);
/* 提交 */
const submit = () => {
validate()
.then(() => {
updatePassword(form)
.then((msg) => {
loading.value = false;
message.success(msg);
push('/login');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 获取图形验证码 */
const changeCaptcha = () => {
// 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面
getCaptcha()
.then((data) => {
captcha.value = data.base64;
// 实际项目后端一般会返回验证码的key而不是直接返回验证码的内容, 登录用key去验证, 你可以根据自己后端接口修改
// text.value = data.text;
// 自动回填验证码, 实际项目去掉这个
// form.code = data.text;
clearValidate();
})
.catch((e) => {
message.error(e.message);
});
};
/* 显示发送短信验证码弹窗 */
const openImgCodeModal = () => {
if (!form.phone) {
message.error('请输入手机号码');
return;
}
imgCode.value = '';
changeCaptcha();
visible.value = true;
};
/* 发送短信验证码 */
const sendCode = () => {
if (!imgCode.value) {
message.error('请输入图形验证码');
return;
}
codeLoading.value = true;
sendSmsCaptcha({ phone: form.phone }).then((res) => {
console.log(res);
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
});
// setTimeout(() => {
// message.success('短信验证码发送成功, 请注意查收!');
// visible.value = false;
// codeLoading.value = false;
// countdownTime.value = 30;
// // 开始对按钮进行倒计时
// countdownTimer = window.setInterval(() => {
// if (countdownTime.value <= 1) {
// countdownTimer && clearInterval(countdownTimer);
// countdownTimer = null;
// }
// countdownTime.value--;
// }, 1000);
// }, 1000);
};
onBeforeUnmount(() => {
countdownTimer && clearInterval(countdownTimer);
});
</script>
<style lang="less" scoped>
/* 背景 */
.login-wrapper {
padding: 48px 16px 0 16px;
position: relative;
box-sizing: border-box;
background-image: url('@/assets/bg-2.jpeg');
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2);
}
}
/* 卡片 */
.login-form {
width: 360px;
margin: 0 auto;
max-width: 100%;
padding: 0 28px 16px 28px;
box-sizing: border-box;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
border-radius: 2px;
position: relative;
z-index: 2;
h4 {
padding: 22px 0;
text-align: center;
}
}
.login-form-right .login-form {
margin: 0 15% 0 auto;
}
.login-form-left .login-form {
margin: 0 auto 0 15%;
}
/* 验证码 */
.login-input-group {
display: flex;
align-items: center;
:deep(.ant-input-affix-wrapper) {
flex: 1;
}
.login-captcha {
width: 102px;
height: 40px;
margin-left: 10px;
padding: 0;
& > img {
width: 100%;
height: 100%;
}
}
}
/* 第三方登录图标 */
.login-oauth-icon {
color: #fff;
padding: 5px;
margin: 0 12px;
font-size: 18px;
border-radius: 50%;
cursor: pointer;
}
/* 底部版权 */
.login-copyright {
color: #eee;
text-align: center;
padding: 48px 0 22px 0;
position: relative;
z-index: 1;
}
/* 响应式 */
@media screen and (min-height: 640px) {
.login-wrapper {
padding-top: 0;
}
.login-form {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%);
margin-top: -230px;
}
.login-form-right .login-form,
.login-form-left .login-form {
left: auto;
right: 15%;
transform: translateX(0);
margin: -230px auto auto auto;
}
.login-form-left .login-form {
right: auto;
left: 15%;
}
.login-copyright {
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
}
@media screen and (max-width: 768px) {
.login-form-right .login-form,
.login-form-left .login-form {
left: 50%;
right: auto;
margin-left: 0;
margin-right: auto;
transform: translateX(-50%);
}
}
</style>

View File

@@ -5,59 +5,53 @@
['', 'login-form-right', 'login-form-left'][direction]
]"
:style="{
backgroundImage: 'url(' + loginImg + ')'
backgroundImage: 'url(' + param.login_bg_img + ')'
}"
>
<div class="logo-login" v-if="config.shortName">
<img :src="config.companyLogo" class="logo" />
<h4>{{ config.shortName }}</h4>
</div>
<a-form class="login-form ele-bg-white">
<div class="company-name">{{ param.login_name }}</div>
<a-form
ref="formRef"
:model="form"
:rules="rules"
class="login-form ele-bg-white"
>
<a-space class="login-title">
<h4
class="title-btn"
:class="loginType === 'account' ? 'active' : ''"
@click="onLoginType('account')"
>账号登录</h4
>密码登录</h4
>
<a-divider type="vertical" style="height: 20px" />
<h4
class="title-btn"
:class="loginType === 'scan' ? 'active' : ''"
@click="onLoginType('scan')"
:class="loginType === 'sms' ? 'active' : ''"
@click="onLoginType('sms')"
>短信登录</h4
>
</a-space>
<template v-if="loginType === 'account'">
<a-form-item v-bind="validateInfos.tenantId" v-if="showTenantId">
<a-input
allow-clear
size="large"
v-model:value="form.tenantId"
:placeholder="`租户ID`"
>
<template #prefix>
<BankOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item v-bind="validateInfos.username">
<a-form-item name="username">
<a-input
allow-clear
size="large"
v-model:value="form.username"
:placeholder="`登录账号 | 手机号码`"
:placeholder="`请输入登录账号`"
>
<template #prefix>
<user-outlined />
<UserOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item v-bind="validateInfos.password">
<a-form-item name="password">
<a-input-password
size="large"
v-model:value="form.password"
placeholder="登录密码"
placeholder="请输入登录密码"
@pressEnter="submit"
>
<template #prefix>
@@ -65,7 +59,7 @@
</template>
</a-input-password>
</a-form-item>
<a-form-item v-bind="validateInfos.code">
<a-form-item name="code">
<div class="login-input-group">
<a-input
allow-clear
@@ -102,33 +96,27 @@
</a-button>
</a-form-item>
</template>
<template v-if="loginType === 'scan'">
<a-form-item v-bind="validateInfos.phone">
<template v-if="loginType === 'sms'">
<a-form-item name="phone">
<a-input
allow-clear
size="large"
maxlength="11"
v-model:value="form.phone"
:placeholder="`手机号码`"
:placeholder="`请输入手机号码`"
>
<template #prefix>
<user-outlined />
</template>
<template #addonBefore> +86 </template>
</a-input>
</a-form-item>
<a-form-item v-bind="validateInfos.code">
<a-form-item name="smsCode">
<div class="login-input-group">
<a-input
placeholder="请输入验证码"
v-model:value="form.code"
v-model:value="form.smsCode"
size="large"
:maxlength="6"
allow-clear
size="large"
>
<template #prefix>
<safety-certificate-outlined />
</template>
</a-input>
/>
<a-button
class="login-captcha"
:disabled="!!countdownTime"
@@ -139,18 +127,13 @@
</a-button>
</div>
</a-form-item>
<a-form-item>
<a-checkbox v-model:checked="form.remember">
{{ t('login.remember') }}
</a-checkbox>
</a-form-item>
<a-form-item>
<a-button
block
size="large"
type="primary"
:loading="loading"
@click="submit"
@click="onLoginBySms"
>
{{ loading ? t('login.loading') : t('login.login') }}
</a-button>
@@ -164,51 +147,52 @@
Rights Reserved.
</p>
</div>
</div>
<!-- 编辑弹窗 -->
<a-modal
:width="340"
:footer="null"
title="发送验证码"
v-model:visible="visible"
>
<div class="login-input-group" style="margin-bottom: 16px">
<a-input
v-model:value="imgCode"
placeholder="请输入图形验证码"
allow-clear
size="large"
/>
<a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" />
</a-button>
</div>
<a-button
block
size="large"
type="primary"
:loading="codeLoading"
@click="sendCode"
<!-- 编辑弹窗 -->
<a-modal
:width="340"
:footer="null"
title="发送验证码"
v-model:visible="visible"
>
立即发送
</a-button>
</a-modal>
<div class="login-input-group" style="margin-bottom: 16px">
<a-input
v-model:value="imgCode"
:maxlength="5"
size="large"
placeholder="请输入图形验证码"
allow-clear
@pressEnter="sendCode"
/>
<a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" />
</a-button>
</div>
<a-button
block
size="large"
type="primary"
:loading="codeLoading"
@click="sendCode"
>
立即发送
</a-button>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, unref, watch } from 'vue';
import { ref, reactive, unref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { getTenantId } from '@/utils/domain';
import { Form, message } from 'ant-design-vue';
import {
UserOutlined,
LockOutlined,
SafetyCertificateOutlined,
BankOutlined
UserOutlined,
SafetyCertificateOutlined
} from '@ant-design/icons-vue';
import { goHomeRoute, cleanPageTabs } from '@/utils/page-tab-util';
import { login, getCaptcha, registerUser } from '@/api/passport/login';
import { login, loginBySms, getCaptcha } from '@/api/passport/login';
import { User } from '@/api/system/user/model';
import { THEME_STORE_NAME } from '@/config/setting';
@@ -216,8 +200,18 @@
import { useTenantStore } from '@/store/modules/tenant';
import { Company } from '@/api/system/company/model';
import useFormData from '@/utils/use-form-data';
import { listWebsiteField } from '@/api/cms/website/field';
import { checkExistence } from '@/api/system/user';
// import { openUrl } from '@/utils/common';
// import { getDomain } from '@/utils/domain';
// import { getMerchantAccountByPhone } from '@/api/shop/merchantAccount';
// import {
// addBatchChatMessage,
// addChatMessage,
// updateChatMessage
// } from '@/api/system/chatMessage';
import { FormInstance } from 'ant-design-vue/es/form';
import { listWebsiteField } from '@/api/system/website/field';
import { WebsiteParam } from '@/api/cms/website/field/model';
import { phoneReg } from 'ele-admin-pro';
const useForm = Form.useForm;
const { currentRoute } = useRouter();
@@ -231,13 +225,16 @@
// 是否显示tenantId填写输入框
const showTenantId = ref(true);
const loginType = ref('account');
const param = ref<WebsiteParam>({});
// 配置信息
const { form } = useFormData<User>({
username: '',
phone: '',
password: '',
code: '',
smsCode: '',
remember: true,
tenantId: undefined
isAdmin: true
});
// 表单数据
const config = reactive<Company>({
@@ -246,7 +243,6 @@
companyLogo: '',
tenantId: undefined
});
const loginImg = ref('');
// 验证码 base64 数据
const captcha = ref('');
// 验证码内容, 实际项目去掉
@@ -262,48 +258,48 @@
// 验证码倒计时定时器
let countdownTimer: number | null = null;
// 表单验证规则
const rules = computed(() => {
return {
username: [
{
required: true,
message: t('login.username'),
type: 'string'
}
],
phone: [
{
required: true,
message: '请输入手机号码',
type: 'string'
}
],
password: [
{
required: true,
message: t('login.password'),
type: 'string'
}
],
code: [
{
required: true,
message: t('login.code'),
type: 'string'
}
]
};
});
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
listWebsiteField({ name: 'LOGIN_IMG' }).then((list) => {
// if (list.length == 0) {
// loginImg.value = String('@/assets/bg-2.jpeg');
// return;
// }
list.map((d) => {
loginImg.value = String(d.value);
});
// 表单验证规则
const rules = reactive({
username: [
{
required: true,
message: t('login.username'),
type: 'string'
}
],
phone: [
{
pattern: phoneReg,
required: true,
type: 'string',
message: '手机号格式不正确',
trigger: 'blur'
}
],
password: [
{
required: true,
message: t('login.password'),
type: 'string'
}
],
code: [
{
required: true,
message: t('login.code'),
type: 'string'
}
],
smsCode: [
{
required: true,
message: t('login.code'),
type: 'string'
}
]
});
/* 显示发送短信验证码弹窗 */
@@ -312,17 +308,9 @@
message.error('请输入手机号码');
return;
}
checkExistence('phone', form.phone)
.then(() => {
imgCode.value = '';
changeCaptcha();
visible.value = true;
return;
})
.catch(() => {
message.error('该手机号码不存在');
return;
});
imgCode.value = '';
changeCaptcha();
visible.value = true;
};
/* 发送短信验证码 */
@@ -336,28 +324,26 @@
return;
}
codeLoading.value = true;
sendSmsCaptcha({ phone: form.phone, key: imgCode.value })
.then(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 60;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
})
.catch((e) => {
codeLoading.value = false;
message.error(e.message);
});
sendSmsCaptcha({ phone: form.phone }).then((res) => {
console.log(res);
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
});
};
const { clearValidate, validate, validateInfos } = useForm(form, rules);
// const { clearValidate, validate, validateInfos } = useForm(form, rules);
const { resetFields } = useForm(form, rules);
const goHome = () => {
const { query } = unref(currentRoute);
@@ -365,45 +351,83 @@
localStorage.removeItem(THEME_STORE_NAME);
};
/* 提交 */
const submit = () => {
validate().then(() => {
if (form.code?.toLowerCase() !== text.value) {
message.error('验证码错误');
changeCaptcha();
return;
}
loading.value = true;
login(form)
.then((msg) => {
message.success(msg);
loading.value = false;
cleanPageTabs();
goHome();
})
.catch((e: Error) => {
message.error(e.message);
loading.value = false;
});
});
const onLoginBySms = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
form.code = form.smsCode;
loginBySms(form)
.then((msg) => {
message.success(msg);
loading.value = false;
resetFields();
cleanPageTabs();
goHome();
})
.catch((e: Error) => {
message.error(e.message);
loading.value = false;
});
})
.catch(() => {});
};
const doRegister = () => {
registerUser(form)
/* 保存编辑 */
const submit = () => {
console.log(formRef.value);
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = false;
message.success('注册成功');
if (form.code?.toLowerCase() !== text.value) {
message.error('验证码错误');
changeCaptcha();
return;
}
loading.value = true;
login(form)
.then((msg) => {
message.success(msg);
loading.value = false;
resetFields();
cleanPageTabs();
goHome();
})
.catch((e: Error) => {
message.error(e.message);
loading.value = false;
});
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
.catch(() => {});
};
// const doRegister = () => {
// registerUser(form)
// .then(() => {
// loading.value = false;
// message.success('注册成功');
// })
// .catch((e) => {
// loading.value = false;
// message.error(e.message);
// });
// };
const onLoginType = (text) => {
loginType.value = text;
};
// const goBack = () => {
// openUrl(getDomain());
// return;
// };
const loginConnect = () => {
// getWxWorkQrConnect().then((result) => {
// console.log(result);
@@ -413,6 +437,12 @@
/* 获取图形验证码 */
const changeCaptcha = () => {
listWebsiteField({}).then((list) => {
list.map((d) => {
const key = String(d.name);
param.value[key] = d.value;
});
});
// 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面
getCaptcha()
.then((data) => {
@@ -421,7 +451,7 @@
text.value = data.text;
// 自动回填验证码, 实际项目去掉这个
// form.code = data.text;
clearValidate();
// resetFields();
})
.catch((e) => {
message.error(e.message);
@@ -461,10 +491,9 @@
padding: 48px 16px 0 16px;
position: relative;
box-sizing: border-box;
background-color: var(--grey-5);
background-image: url('@/assets/bg-2.jpeg');
background-color: var(--grey-5);
background-repeat: no-repeat;
background-position: center;
background-size: cover;
min-height: 100vh;
@@ -475,9 +504,17 @@
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
background: rgba(0, 0, 0, 0.2);
}
}
.company-name {
position: absolute;
top: 17%;
left: 50%;
transform: translateX(-50%);
font-size: 24px;
color: #ffffff;
}
/* 卡片 */
.login-form {
width: 380px;

View File

@@ -1,384 +0,0 @@
<template>
<a-row type="flex" justify="center" align="top">
<a-col :span="10">
<div class="container">
<div class="ele-body ele-body-card">
<h2 style="text-align: center; font-size: 28px; line-height: 3em"
>申请免费体验</h2
>
<a-form
:label-col="{ md: { span: 4 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 18 }, sm: { span: 24 } }"
>
<a-form-item label="手机号码" v-bind="validateInfos.customerMobile">
<a-input
v-model:value="form.customerMobile"
placeholder="请输入手机号码"
:maxlength="11"
allow-clear
/>
</a-form-item>
<a-form-item label="验证码" v-bind="validateInfos.code">
<div class="login-input-group">
<a-input
placeholder="请输入验证码"
v-model:value="form.code"
:maxlength="6"
allow-clear
/>
<a-button
class="login-captcha"
:disabled="!!countdownTime"
@click="openImgCodeModal"
>
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</a-button>
</div>
</a-form-item>
<a-form-item label="企业名称" v-bind="validateInfos.customerName">
<a-input
v-model:value="form.customerName"
placeholder="请输入企业名称"
allow-clear
/>
</a-form-item>
<a-form-item
label="企业编号"
v-bind="validateInfos.customerCode"
style="display: none"
>
<a-input
allow-clear
:maxlength="20"
v-model:value="form.customerCode"
/>
</a-form-item>
<a-form-item :wrapper-col="{ md: { offset: 5 } }">
<a-button
type="primary"
style="width: 100px"
:loading="loading"
@click="submit"
>
{{ loading ? '提交..' : '提交' }}
</a-button>
</a-form-item>
</a-form>
</div>
</div>
</a-col>
</a-row>
<!-- 编辑弹窗 -->
<a-modal
:width="340"
:footer="null"
title="发送验证码"
v-model:visible="visible"
>
<div class="login-input-group" style="margin-bottom: 16px">
<a-input
v-model:value="imgCode"
placeholder="请输入图形验证码"
allow-clear
/>
<a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" />
</a-button>
</div>
<a-button
block
size="large"
type="primary"
:loading="codeLoading"
@click="sendCode"
>
立即发送
</a-button>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, onBeforeUnmount } from 'vue';
import { useRouter } from 'vue-router';
import { Form, message } from 'ant-design-vue';
import { getCaptcha } from '@/api/passport/login';
import { apply } from '@/api/oa/apply';
import { phoneReg } from 'ele-admin-pro';
import { Customer } from '@/api/oa/apply/model';
import { createCode } from '@/utils/common';
const useForm = Form.useForm;
const { push } = useRouter();
// 加载状态
const loading = ref(false);
// 表单数据
const form = reactive<Customer>({
customerCode: createCode(),
customerName: '',
customerType: undefined,
customerMobile: '',
customerAvatar: '',
customerPhone: '',
customerContacts: '',
customerAddress: '',
comments: '',
status: '0',
sortNumber: 100,
customerId: 0,
code: ''
});
// 表单验证规则
const rules = reactive({
customerName: [
{
required: true,
message: '请输入企业名称',
type: 'string'
}
],
customerMobile: [
{
required: true,
message: '请输入手机号',
type: 'string'
},
{
pattern: phoneReg,
message: '手机号格式不正确',
type: 'string'
}
],
code: [
{
required: true,
message: '请输入验证码',
type: 'string'
}
]
});
// 是否显示图形验证码弹窗
const visible = ref(false);
// 图形验证码
const imgCode = ref('');
// 发送验证码按钮loading
const codeLoading = ref(false);
// 验证码倒计时时间
const countdownTime = ref(0);
// 图形验证码地址
const captcha = ref('');
// 验证码倒计时定时器
let countdownTimer: number | null = null;
const { validate, validateInfos, clearValidate } = useForm(form, rules);
/* 提交 */
const submit = () => {
validate()
.then(() => {
loading.value = true;
apply({ ...form })
.then((msg) => {
loading.value = false;
message.success(msg);
push('/');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 获取图形验证码 */
const changeCaptcha = () => {
// 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面
getCaptcha()
.then((data) => {
captcha.value = data.base64;
// 实际项目后端一般会返回验证码的key而不是直接返回验证码的内容, 登录用key去验证, 你可以根据自己后端接口修改
// text.value = data.text;
// 自动回填验证码, 实际项目去掉这个
// form.code = data.text;
clearValidate();
})
.catch((e) => {
message.error(e.message);
});
};
/* 显示发送短信验证码弹窗 */
const openImgCodeModal = () => {
if (!form.customerMobile) {
message.error('请输入手机号码');
return;
}
imgCode.value = '';
changeCaptcha();
visible.value = true;
};
/* 发送短信验证码 */
const sendCode = () => {
if (!imgCode.value) {
message.error('请输入图形验证码');
return;
}
codeLoading.value = true;
setTimeout(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
}, 1000);
};
onBeforeUnmount(() => {
countdownTimer && clearInterval(countdownTimer);
});
</script>
<style lang="less" scoped>
/* 背景 */
.login-wrapper {
padding: 48px 16px 0 16px;
position: relative;
box-sizing: border-box;
background-image: url('@/assets/bg-2.jpeg');
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2);
}
}
/* 卡片 */
.login-form {
width: 360px;
margin: 0 auto;
max-width: 100%;
padding: 0 28px 16px 28px;
box-sizing: border-box;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
border-radius: 2px;
position: relative;
z-index: 2;
h4 {
padding: 22px 0;
text-align: center;
}
}
.login-form-right .login-form {
margin: 0 15% 0 auto;
}
.login-form-left .login-form {
margin: 0 auto 0 15%;
}
/* 验证码 */
.login-input-group {
display: flex;
align-items: center;
:deep(.ant-input-affix-wrapper) {
flex: 1;
}
.login-captcha {
width: 102px;
margin-left: 10px;
padding: 0;
& > img {
width: 100%;
height: 100%;
}
}
}
/* 第三方登录图标 */
.login-oauth-icon {
color: #fff;
padding: 5px;
margin: 0 12px;
font-size: 18px;
border-radius: 50%;
cursor: pointer;
}
/* 底部版权 */
.login-copyright {
color: #eee;
text-align: center;
padding: 48px 0 22px 0;
position: relative;
z-index: 1;
}
/* 响应式 */
@media screen and (min-height: 640px) {
.login-wrapper {
padding-top: 0;
}
.login-form {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%);
margin-top: -230px;
}
.login-form-right .login-form,
.login-form-left .login-form {
left: auto;
right: 15%;
transform: translateX(0);
margin: -230px auto auto auto;
}
.login-form-left .login-form {
right: auto;
left: 15%;
}
.login-copyright {
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
}
@media screen and (max-width: 768px) {
.login-form-right .login-form,
.login-form-left .login-form {
left: 50%;
right: auto;
margin-left: 0;
margin-right: auto;
transform: translateX(-50%);
}
}
</style>

View File

@@ -1,16 +0,0 @@
<template>
<div class="register">
<!-- 加载主体部分 -->
<!-- <register />-->
</div>
</template>
<script lang="ts" setup>
// import Register from './setp/index.vue';
</script>
<script lang="ts">
export default {
name: 'Index'
};
</script>

View File

@@ -1,276 +0,0 @@
<template>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '130px' }"
:wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }"
>
<a-form-item label="手机号码" name="phone">
<a-input
allow-clear
:maxlength="11"
v-model:value="form.phone"
placeholder="请输入手机号码"
>
<template #addonBefore> +86 </template>
</a-input>
</a-form-item>
<a-form-item label="验证码" name="code">
<div class="login-input-group">
<a-input
placeholder="请输入验证码"
v-model:value="form.code"
:maxlength="6"
allow-clear
/>
<a-button
class="login-captcha"
:disabled="!!countdownTime"
@click="openImgCodeModal"
>
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</a-button>
</div>
</a-form-item>
<a-form-item
:wrapper-col="styleResponsive ? { sm: { offset: 5 } } : { offset: 4 }"
style="margin-top: 24px"
>
<a-space size="middle">
<a-button @click="back">上一步</a-button>
<a-button type="primary" :loading="loading" @click="submit">
下一步
</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- 编辑弹窗 -->
<a-modal
:width="340"
:footer="null"
title="发送验证码"
v-model:visible="visible"
>
<div class="login-input-group" style="margin-bottom: 16px">
<a-input
v-model:value="imgCode"
:maxlength="5"
placeholder="请输入图形验证码"
allow-clear
@pressEnter="sendCode"
/>
<a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" />
</a-button>
</div>
<a-button
block
size="large"
type="primary"
:loading="codeLoading"
@click="sendCode"
>
立即发送
</a-button>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import type { StepForm } from '../model';
import { assignObject, phoneReg } from 'ele-admin-pro';
import { message } from 'ant-design-vue';
import { getCaptcha, sendSmsCaptcha, registerUser } from '@/api/login';
import { User } from '@/api/system/user/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 修改回显的数据
data?: StepForm | null;
}>();
const emit = defineEmits<{
(e: 'done', data: StepForm): void;
(e: 'back'): void;
}>();
// 是否显示图形验证码弹窗
const visible = ref(false);
// 图形验证码
const imgCode = ref('');
// 发送验证码按钮loading
const codeLoading = ref(false);
// 验证码倒计时时间
const countdownTime = ref(0);
// 图形验证码地址
const captcha = ref('');
const text = ref('');
// 验证码倒计时定时器
let countdownTimer: number | null = null;
const formRef = ref<FormInstance | null>(null);
/* 发送短信验证码 */
const sendCode = () => {
if (!imgCode.value) {
message.error('请输入图形验证码');
return;
}
if (text.value !== imgCode.value) {
message.error('图形验证码不正确');
return;
}
codeLoading.value = true;
sendSmsCaptcha({ phone: form.phone, key: imgCode.value })
.then(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 60;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
})
.catch((e) => {
codeLoading.value = false;
message.error(e.message);
});
};
// 提交状态
const loading = ref(false);
// 表单数据
const form = reactive<User>({
type: 10,
phone: '',
username: '',
nickname: '',
roles: [],
companyName: '',
organizationName: '',
email: '',
password: '',
password2: ''
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
phone: [
{
pattern: phoneReg,
message: '手机号格式不正确',
type: 'string'
}
],
code: [
{
required: true,
message: '请填写短信验证码',
type: 'string',
trigger: 'blur'
}
]
});
/* 获取图形验证码 */
const changeCaptcha = () => {
// 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面
getCaptcha()
.then((data) => {
captcha.value = data.base64;
// 实际项目后端一般会返回验证码的key而不是直接返回验证码的内容, 登录用key去验证, 你可以根据自己后端接口修改
text.value = data.text;
// 自动回填验证码, 实际项目去掉这个
// form.code = data.text;
})
.catch((e) => {
message.error(e.message);
});
};
/* 显示发送短信验证码弹窗 */
const openImgCodeModal = () => {
if (!form.phone) {
message.error('请输入手机号码');
return;
}
imgCode.value = '';
changeCaptcha();
visible.value = true;
};
const submit = () => {
if (!formRef.value) {
return;
}
formRef.value
?.validate()
.then(() => {
loading.value = true;
// roleId 6开发者 10商户
const addData = {
...form,
username: props.data?.username,
password: props.data?.password,
phone: props.data?.phone,
nickname: props.data?.nickname,
roles: [{ roleId: form.type }]
};
registerUser(addData)
.then(() => {
loading.value = false;
message.success('注册成功');
emit('done', form);
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
const back = () => {
emit('back');
};
if (props.data) {
assignObject(form, props.data);
}
</script>
<style lang="less" scoped>
/* 验证码 */
.login-input-group {
display: flex;
align-items: center;
:deep(.ant-input-affix-wrapper) {
flex: 1;
}
.login-captcha {
width: 102px;
margin-left: 10px;
padding: 0;
& > img {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@@ -1,302 +0,0 @@
<template>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '130px' }"
:wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }"
>
<!-- <a-form-item-->
<!-- label="账号类型"-->
<!-- name="type"-->
<!-- extra="请慎重选择,注册成功后不允许修改"-->
<!-- >-->
<!-- <a-radio-group v-model:value="form.type">-->
<!-- <a-radio :value="10">企业主</a-radio>-->
<!-- <a-radio :value="6">开发者</a-radio>-->
<!-- </a-radio-group>-->
<!-- </a-form-item>-->
<!-- <a-form-item label="企业名称" name="companyName" v-if="form.type === 10">-->
<!-- <a-input-->
<!-- placeholder="请输入企业名称"-->
<!-- v-model:value="form.companyName"-->
<!-- :maxlength="24"-->
<!-- allow-clear-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item label="开发者名称" name="companyName" v-if="form.type === 6">
<a-input
placeholder="请输入开发团队名称"
v-model:value="form.companyName"
:maxlength="25"
allow-clear
/>
</a-form-item>
<a-form-item label="电子邮箱" name="email">
<a-input
placeholder="注册成功自动发送邮件通知"
v-model:value="form.email"
:maxlength="24"
allow-clear
/>
</a-form-item>
<a-form-item label="手机号码" name="phone">
<a-input
placeholder="请输入手机号码"
v-model:value="form.phone"
:maxlength="11"
allow-clear
/>
</a-form-item>
<a-form-item label="登录密码" name="password">
<a-input
allow-clear
:maxlength="24"
type="password"
v-model:value="form.password"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item label="确认密码" name="password2">
<a-input
allow-clear
:maxlength="24"
type="password"
v-model:value="form.password2"
placeholder="请输入确认密码"
/>
</a-form-item>
<!-- <a-form-item label="昵称" name="nickname">-->
<!-- <a-input-->
<!-- placeholder="请输入昵称"-->
<!-- v-model:value="form.nickname"-->
<!-- :maxlength="24"-->
<!-- allow-clear-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label=" " name="register" v-if="form.type === 1">-->
<!-- 我已阅读并同意 <a>用户协议</a> <a>隐私权政策</a>-->
<!-- </a-form-item>-->
<a-form-item
:wrapper-col="styleResponsive ? { sm: { offset: 5 } } : { offset: 4 }"
>
<a-button type="primary" :loading="loading" @click="submit">
下一步
</a-button>
</a-form-item>
</a-form>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import type { StepForm } from '../model';
import { emailReg, phoneReg } from 'ele-admin-pro/es';
import type { RuleObject } from 'ant-design-vue/es/form';
import { createCode } from '@/utils/common';
import { checkExistence } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done', data: StepForm): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
}>();
const formRef = ref<FormInstance | null>(null);
// 提交状态
const loading = ref(false);
// 表单数据
const form = reactive<User>({
type: 10,
phone: '',
username: createCode(),
nickname: '',
organizationName: '',
companyName: '',
email: '',
password: '',
password2: ''
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
companyName: [
{
required: true,
type: 'string',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请填写企业名称或姓名');
}
checkExistence('realName', value)
.then(() => {
if (form.type === 10) {
reject('该企业名称已经存在');
}
})
.catch(() => {
resolve();
});
});
},
trigger: 'blur'
}
],
type: [
{
required: true,
message: '请选择账号类型',
type: 'number',
trigger: 'blur'
}
],
nickname: [
{
required: true,
message: '请选择账号类型',
type: 'string',
trigger: 'blur'
}
],
email: [
{
pattern: emailReg,
message: '邮箱格式不正确',
type: 'string',
trigger: 'blur'
},
{
required: true,
type: 'string',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请输入用户账号');
}
checkExistence('email', value)
.then(() => {
reject('该邮箱已经存在');
})
.catch(() => {
resolve();
});
});
},
trigger: 'blur'
}
],
phone: [
{
pattern: phoneReg,
message: '手机号格式不正确',
type: 'string',
trigger: 'blur'
},
{
required: true,
type: 'string',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请输入用户账号');
}
checkExistence('phone', value)
.then(() => {
reject('该手机号码已经存在');
})
.catch(() => {
resolve();
});
});
},
trigger: 'blur'
}
],
password: [
{
required: true,
type: 'string',
validator: async (_rule: Rule, value: string) => {
if (/^[\S]{8,32}$/.test(value)) {
return Promise.resolve();
}
return Promise.reject('密码必须为8-32位非空白字符');
},
trigger: 'blur'
}
],
password2: [
{
required: true,
validator: async (_rule: RuleObject, value: string) => {
if (!value) {
return Promise.reject('请再次输入新密码');
}
if (value !== form.password) {
return Promise.reject('两次输入密码不一致');
}
return Promise.resolve();
}
}
]
});
/* 步骤一提交 */
const submit = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
emit('done', form);
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
} else {
formRef.value?.clearValidate();
}
}
);
</script>
<style lang="less" scoped>
/* 验证码 */
.login-input-group {
display: flex;
align-items: center;
:deep(.ant-input-affix-wrapper) {
flex: 1;
}
.login-captcha {
width: 102px;
margin-left: 10px;
padding: 0;
& > img {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@@ -1,32 +0,0 @@
<template>
<div>
<a-result
title="注册成功"
status="success"
sub-title="注册资料已发到您预留的邮箱地址请查收"
/>
<div style="display: flex; justify-content: center">
<a-space>
<a-button
type="primary"
@click="openUrl('https://admin.gxwebsoft.com')"
>
后台管理系统
</a-button>
<a-button type="primary" @click="openUrl('/login')">
开发者中心
</a-button>
</a-space>
</div>
</div>
</template>
<script lang="ts" setup>
import type { StepForm } from '../model';
import { openUrl } from '@/utils/common';
defineProps<{
// 修改回显的数据
data?: StepForm | null;
}>();
</script>

View File

@@ -1,93 +0,0 @@
<template>
<div
:class="[
'login-wrapper',
['', 'login-form-right', 'login-form-left'][direction]
]"
>
<div class="ele-body" style="width: 1000px">
<a-card :bordered="false">
<div style="max-width: 700px; margin: 0 auto">
<div style="margin: 10px 0 30px 0">
<a-steps
:current="active"
direction="horizontal"
:responsive="styleResponsive"
>
<a-step title="第一步" description="基本信息" />
<a-step title="第二步" description="验证信息" />
<a-step title="第三步" description="注册成功" />
</a-steps>
</div>
<step-edit
v-model:visible="showEdit"
v-if="active === 0"
@done="onDone"
/>
<step-confirm
v-if="active === 1"
:data="form"
@done="onNext"
@back="onBack"
/>
<step-success v-if="active === 2" :data="form" @back="onBack" />
</div>
</a-card>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import StepEdit from './components/step-edit.vue';
import StepConfirm from './components/step-confirm.vue';
import StepSuccess from './components/step-success.vue';
import type { StepForm } from './model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
// 选中步骤
const active = ref(0);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 登录框方向, 0 居中, 1 居右, 2 居左
const direction = ref(0);
const form = reactive<StepForm>({});
//
const onDone = (data: StepForm) => {
Object.assign(form, data);
active.value = 1;
};
//
const onNext = (data: StepForm) => {
Object.assign(form, data);
active.value = 2;
};
//
const onBack = () => {
active.value = 0;
};
</script>
<script lang="ts">
export default {
name: 'FormStep'
};
</script>
<style lang="less" scoped>
.account-type {
margin-bottom: 10px;
font-weight: bold;
}
.account-desc {
margin-bottom: 15px;
}
</style>

View File

@@ -1,11 +0,0 @@
export interface StepForm {
type?: number;
apply?: string;
username?: string;
password?: string;
password2?: string;
phone?: string;
code?: string;
nickname?: string;
companyName?: string;
}

View File

@@ -1,32 +0,0 @@
<template>
<div id="token_login" style="width: 100%; height: 100%">登录中...</div>
</template>
<script setup>
import { unref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { setToken } from '@/utils/token-util';
import { openUrl } from '@/utils/common';
import { getRootDomain } from '@/utils/domain';
const { currentRoute } = useRouter();
const rootDomain = getRootDomain();
watch(
currentRoute,
(route) => {
const { query } = unref(route);
const { tid, token } = query;
if (rootDomain !== 'gxwebsoft.com') {
return false;
}
if (token) {
setToken(token, true);
localStorage.setItem('TenantId', tid);
openUrl('/');
}
},
{ immediate: true }
);
</script>

View File

@@ -1,41 +0,0 @@
<template>
<div id="ww_login" style="width: 100%; height: 100%"></div>
</template>
<script setup>
import * as ww from '@wecom/jssdk';
import { onMounted, ref } from 'vue';
const wwLoginInstance = ref(null);
const wwLogin = () => {
wwLoginInstance.value = ww.createWWLoginPanel({
el: '#ww_login',
params: {
login_type: 'ServiceApp',
appid: 'wx5400ab7aebf0f3c3',
agentid: '10000xx',
redirect_uri: 'https://oa.gxwebsoft.com/wx-work-login',
state: 'loginState',
redirect_type: 'callback',
panel_size: 'small'
},
onCheckWeComLogin({ isWeComLogin }) {
console.log('1111');
console.log(isWeComLogin);
},
onLoginSuccess({ code }) {
console.log('222');
console.log({ code });
},
onLoginFail(err) {
console.log('333');
console.log(err);
}
});
};
onMounted(() => {
wwLogin();
});
</script>

View File

@@ -0,0 +1,157 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type {
ChatConversation,
ChatConversationParam,
ChatMessage,
ChatMessageParam
} from '@/api/system/chat/model';
import { SERVER_API_URL } from '@/config/setting';
/**
* 查询聊天列表
*/
export async function pageChatConversation(params: ChatConversationParam) {
const res = await request.get<ApiResult<PageResult<ChatConversation>>>(
SERVER_API_URL + '/system/chat-conversation/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询聊天列表
*/
export async function pageChatMessage(params: ChatMessageParam) {
const res = await request.get<ApiResult<PageResult<ChatMessage>>>(
SERVER_API_URL + '/system/chat-message/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询日志列表
*/
export async function listChatConversation(params?: ChatConversationParam) {
const res = await request.get<ApiResult<ChatConversation[]>>(
SERVER_API_URL + '/system/chat-conversation',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加日志
*/
export async function addChatMessage(data: ChatMessage) {
const res = await request.post<ApiResult<ChatConversation>>(
SERVER_API_URL + '/system/chat-message',
data
);
if (res.data.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加日志
*/
export async function addChatConversation(data: ChatConversation) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-conversation',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改日志
*/
export async function updateChatConversation(data: any) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-conversation',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 绑定日志
*/
export async function bindChatConversation(data: ChatConversation) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-conversation/bind',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量添加
*/
export async function addBatchChatConversation(data: ChatConversation[]) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-conversation/batch',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除日志
*/
export async function removeChatConversation(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-conversation/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除日志
*/
export async function removeBatchChatConversation(
data: (number | undefined)[]
) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-conversation/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,49 @@
import type { PageParam } from '@/api';
import type { User } from '@/api/system/user/model';
export interface ChatConversation {
id?: number;
userId?: number;
friendId?: number;
userInfo?: User;
friendInfo?: User;
content: string;
messages: ChatMessage[];
unRead: number;
createTime?: string;
updateTime: string | number | Date;
}
export interface ChatMessage {
id?: number;
formUserId?: number;
formUserInfo?: User;
toUserInfo?: User;
toUserId?: number;
type: string;
content: string;
status?: number;
createTime?: number;
updateTime?: number;
}
/**
* 搜索条件
*/
export interface ChatConversationParam extends PageParam {
userId?: number;
status: number;
onlyFake: boolean;
keywords: string;
}
/**
* 搜索条件
*/
export interface ChatMessageParam extends PageParam {
formUserId?: number;
toUserId?: number;
type?: string;
status?: number;
keywords: string;
}

View File

@@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ChatConversation, ChatConversationParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询聊天消息表
*/
export async function pageChatConversation(params: ChatConversationParam) {
const res = await request.get<ApiResult<PageResult<ChatConversation>>>(
MODULES_API_URL + '/shop/chat-conversation/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询聊天消息表列表
*/
export async function listChatConversation(params?: ChatConversationParam) {
const res = await request.get<ApiResult<ChatConversation[]>>(
MODULES_API_URL + '/shop/chat-conversation',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加聊天消息表
*/
export async function addChatConversation(data: ChatConversation) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/chat-conversation',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改聊天消息表
*/
export async function updateChatConversation(data: ChatConversation) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/chat-conversation',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除聊天消息表
*/
export async function removeChatConversation(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/chat-conversation/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除聊天消息表
*/
export async function removeBatchChatConversation(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/chat-conversation/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询聊天消息表
*/
export async function getChatConversation(id: number) {
const res = await request.get<ApiResult<ChatConversation>>(
MODULES_API_URL + '/shop/chat-conversation/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,37 @@
import type { PageParam } from '@/api';
/**
* 聊天消息表
*/
export interface ChatConversation {
// 自增ID
id?: number;
// 用户ID
userId?: number;
// 好友ID
friendId?: number;
// 消息类型
type?: number;
// 消息内容
content?: string;
// 未读消息
unRead?: number;
// 状态, 0未读, 1已读
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 聊天消息表搜索条件
*/
export interface ChatConversationParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -0,0 +1,120 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ChatMessage, ChatMessageParam } from './model';
import { SERVER_API_URL } from '@/config/setting';
/**
* 分页查询聊天消息表
*/
export async function pageChatMessage(params: ChatMessageParam) {
const res = await request.get<ApiResult<PageResult<ChatMessage>>>(
SERVER_API_URL + '/system/chat-message/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询聊天消息表列表
*/
export async function listChatMessage(params?: ChatMessageParam) {
const res = await request.get<ApiResult<ChatMessage[]>>(
SERVER_API_URL + '/system/chat-message',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加聊天消息表
*/
export async function addChatMessage(data: ChatMessage) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-message',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加聊天消息表
*/
export async function addBatchChatMessage(data: ChatMessage[]) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-message/batch',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改聊天消息表
*/
export async function updateChatMessage(data: ChatMessage) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-message',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除聊天消息表
*/
export async function removeChatMessage(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-message/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除聊天消息表
*/
export async function removeBatchChatMessage(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/system/chat-message/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询聊天消息表
*/
export async function getChatMessage(id: number) {
const res = await request.get<ApiResult<ChatMessage>>(
SERVER_API_URL + '/system/chat-message/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,49 @@
import type { PageParam } from '@/api';
/**
* 聊天消息表
*/
export interface ChatMessage {
// 自增ID
id?: number;
// 发送人ID
formUserId?: number;
// 接收人ID
toUserId?: number;
// 消息类型
type?: string;
// 消息内容
content?: string;
// 屏蔽接收方
sideTo?: number;
// 屏蔽发送方
sideFrom?: number;
// 是否撤回
withdraw?: number;
// 文件信息
fileInfo?: string;
toUserName?: any;
formUserName?: string;
// 批量发送
toUserIds?: any[];
// 存在联系方式
hasContact?: number;
// 状态, 0未读, 1已读
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 聊天消息表搜索条件
*/
export interface ChatMessageParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -0,0 +1,175 @@
<!-- 订单编辑弹窗 -->
<template>
<ele-modal
:width="460"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '修改订单' : '添加订单'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="选择客户" name="companyId">
<SelectCompany v-model:value="form.tenantName" @done="onCompany" />
</a-form-item>
<a-form-item label="支付金额" name="money">
<a-input-number
allow-clear
max="1000000"
style="width: 200px"
placeholder="请输入订单金额"
v-model:value="form.money"
/>
</a-form-item>
<a-form-item label="到期时间" name="days">
<a-date-picker
v-model:value="form.expirationTime"
show-time
placeholder="到期时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</a-form-item>
<a-form-item label="备注">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { urlReg } from 'ele-admin-pro';
import { Order } from '@/api/system/order/model';
import { addOrder, updateOrder } from '@/api/system/order';
import { Company } from '@/api/system/company/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Order | null;
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Order>({
orderId: undefined,
money: undefined,
companyName: '',
companyId: 0,
tenantId: undefined,
tenantName: '',
comments: undefined
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
companyId: [
{
required: true,
message: '请选择客户',
pattern: urlReg,
type: 'number',
trigger: 'blur'
}
],
money: [
{
required: true,
message: '请输入订单金额',
type: 'number',
trigger: 'blur'
}
]
});
const onCompany = (item: Company) => {
console.log(item);
form.companyId = item.companyId;
form.tenantName = item.tenantName;
form.tenantId = item.tenantId;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateOrder : addOrder;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignFields(props.data);
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>

View File

@@ -0,0 +1,104 @@
<!-- 搜索表单 -->
<template>
<div style="display: flex; justify-content: space-between">
<a-space style="flex-wrap: wrap">
<!-- <a-button type="primary" class="ele-btn-icon" @click="add">-->
<!-- <template #icon>-->
<!-- <PlusOutlined />-->
<!-- </template>-->
<!-- <span>添加订单</span>-->
<!-- </a-button>-->
<a-button
danger
type="primary"
class="ele-btn-icon"
v-if="selection?.length > 0"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
</a-space>
<a-space :size="10" style="flex-wrap: wrap; margin-right: 20px">
<a-input-search
allow-clear
placeholder="请输入关键词"
v-model:value="searchText"
@pressEnter="search"
@search="search"
/>
</a-space>
</div>
</template>
<script lang="ts" setup>
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import useSearch from '@/utils/use-search';
import { ref, watch } from 'vue';
import { CompanyParam } from '@/api/system/company/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: CompanyParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'update', status?: number): void;
(e: 'import'): void;
}>();
// 表单数据
const { where, resetFields } = useSearch<CompanyParam>({
companyId: undefined,
companyName: undefined,
keywords: '',
authentication: undefined,
version: undefined,
province: '',
city: '',
region: ''
});
const tenantId = ref<number>();
if (localStorage.getItem('TenantId')) {
tenantId.value = Number(localStorage.getItem('TenantId'));
}
// 搜索内容
const searchText = ref('');
// 新增
const add = () => {
emit('add');
};
/* 搜索 */
const search = () => {
emit('search', {
...where
});
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,193 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="orderId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
:scroll="{ x: 800 }"
cache-key="proSystemOrderTable"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'money'">
<span class="ele-text-warning">
{{ formatNumber(record.money) }}
</span>
</template>
<template v-if="column.key === 'type'">
<a-tag v-if="record.type === 0">续费订单</a-tag>
<a-tag v-if="record.type === 1" color="purple">普通订单</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
placement="topRight"
title="确定要删除此模块吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<order-edit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { EleProTable, formatNumber } from 'ele-admin-pro/es';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import { messageLoading } from 'ele-admin-pro/es';
import OrderEdit from './components/order-edit.vue';
import { pageOrder, removeOrder } from '@/api/system/order';
import type { Order, OrderParam } from '@/api/system/order/model';
import { Menu } from '@/api/system/menu/model';
import Search from '@/views/system/order/components/search.vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { removeBatchCompany } from '@/api/system/company';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '订单号',
dataIndex: 'orderId',
width: 90
},
{
title: '订单类型',
dataIndex: 'type',
key: 'type',
align: 'center',
width: 120
},
{
title: '租户名称',
dataIndex: 'tenantName',
key: 'tenantName',
align: 'center'
},
{
title: '订单金额(元)',
dataIndex: 'money',
key: 'money',
align: 'center'
},
{
title: '备注',
dataIndex: 'comments',
align: 'center'
}
]);
// 表格选中数据
const selection = ref<Order[]>([]);
// 当前编辑数据
const current = ref<Order | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
return pageOrder({ ...where, ...orders, limit, page });
};
/* 搜索 */
const reload = (where?: OrderParam) => {
selection.value = [];
tableRef?.value?.reload({ page: 1, where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Order) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 自定义行属性 */
const customRow = (record: Menu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
/* 删除单个 */
const remove = (row: Order) => {
const hide = messageLoading('请求中..', 0);
removeOrder(row.orderId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchCompany(selection.value.map((d) => d.orderId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
</script>
<script lang="ts">
export default {
name: 'SystemOrder'
};
</script>

View File

@@ -173,7 +173,7 @@
import { listRoles } from '@/api/system/role';
import { listOrganizations } from '@/api/system/organization';
import { Organization } from '@/api/system/organization/model';
import {hasRole} from "@/utils/permission";
import { hasRole } from '@/utils/permission';
// 加载状态
const loading = ref(true);

View File

@@ -0,0 +1,225 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑聊天消息表' : '添加聊天消息表'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="用户ID" name="userId">
<a-input
allow-clear
placeholder="请输入用户ID"
v-model:value="form.userId"
/>
</a-form-item>
<a-form-item label="好友ID" name="friendId">
<a-input
allow-clear
placeholder="请输入好友ID"
v-model:value="form.friendId"
/>
</a-form-item>
<a-form-item label="消息类型" name="type">
<a-input
allow-clear
placeholder="请输入消息类型"
v-model:value="form.type"
/>
</a-form-item>
<a-form-item label="消息内容" name="content">
<a-input
allow-clear
placeholder="请输入消息内容"
v-model:value="form.content"
/>
</a-form-item>
<a-form-item label="未读消息" name="unRead">
<a-input
allow-clear
placeholder="请输入未读消息"
v-model:value="form.unRead"
/>
</a-form-item>
<a-form-item label="状态, 0未读, 1已读" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="是否删除, 0否, 1是" name="deleted">
<a-input
allow-clear
placeholder="请输入是否删除, 0否, 1是"
v-model:value="form.deleted"
/>
</a-form-item>
<a-form-item label="修改时间" name="updateTime">
<a-input
allow-clear
placeholder="请输入修改时间"
v-model:value="form.updateTime"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addChatConversation, updateChatConversation } from '@/api/system/chatConversation';
import { ChatConversation } from '@/api/system/chatConversation/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ChatConversation | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<ChatConversation>({
id: undefined,
userId: undefined,
friendId: undefined,
type: undefined,
content: undefined,
unRead: undefined,
status: undefined,
deleted: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
chatConversationId: undefined,
chatConversationName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
chatConversationName: [
{
required: true,
type: 'string',
message: '请填写聊天消息表名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateChatConversation : addChatConversation;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,263 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="chatConversationId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ChatConversationEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ChatConversationEdit from './components/chatConversationEdit.vue';
import { pageChatConversation, removeChatConversation, removeBatchChatConversation } from '@/api/system/chatConversation';
import type { ChatConversation, ChatConversationParam } from '@/api/system/chatConversation/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ChatConversation[]>([]);
// 当前编辑数据
const current = ref<ChatConversation | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageChatConversation({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '自增ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '用户ID',
dataIndex: 'userId',
key: 'userId',
align: 'center',
},
{
title: '好友ID',
dataIndex: 'friendId',
key: 'friendId',
align: 'center',
},
{
title: '消息类型',
dataIndex: 'type',
key: 'type',
align: 'center',
},
{
title: '消息内容',
dataIndex: 'content',
key: 'content',
align: 'center',
},
{
title: '未读消息',
dataIndex: 'unRead',
key: 'unRead',
align: 'center',
},
{
title: '状态, 0未读, 1已读',
dataIndex: 'status',
key: 'status',
align: 'center',
},
{
title: '是否删除, 0否, 1是',
dataIndex: 'deleted',
key: 'deleted',
align: 'center',
},
{
title: '注册时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '修改时间',
dataIndex: 'updateTime',
key: 'updateTime',
align: 'center',
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ChatConversationParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ChatConversation) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ChatConversation) => {
const hide = message.loading('请求中..', 0);
removeChatConversation(row.chatConversationId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchChatConversation(selection.value.map((d) => d.chatConversationId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: ChatConversation) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'ChatConversation'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,299 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? `【${form.formUserName}】给你发送的消息` : '发送消息'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
:ok-button-props="{ disabled: isUpdate }"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<template v-if="isUpdate">
<a-form-item label="消息类型" name="type">
<span class="ele-text-secondary">文本</span>
</a-form-item>
<a-form-item label="消息内容" name="content">
<div class="ele-text-secondary">
<byte-md-viewer :value="form.content" :plugins="plugins" />
</div>
</a-form-item>
<a-form-item label="发送时间" name="type">
<div class="ele-text-secondary">{{ form.createTime }}</div>
</a-form-item>
</template>
<template v-else>
<a-form-item label="接收对象" name="toUserIds" v-if="!isUpdate">
<SelectStaff
:placeholder="`选择用户`"
v-model:value="form.toUserName"
@done="onToUser"
/>
</a-form-item>
<a-form-item label="消息类型" name="type">
<DictSelect
dict-code="chatMessageType"
:placeholder="`选择消息类型`"
v-model:value="form.type"
:disabled="true"
@done="chooseType"
/>
</a-form-item>
<a-form-item label="消息内容" name="content">
<!-- 编辑器 -->
<byte-md-editor
v-model:value="content"
placeholder="请描述您的问题,支持图片粘贴"
mode="tab"
height="300px"
:locale="zh_Hans"
:plugins="plugins"
maxLength="500"
:editorConfig="{ lineNumbers: true }"
/>
</a-form-item>
</template>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import {
addBatchChatMessage,
addChatMessage,
updateChatMessage
} from '@/api/system/chatMessage';
import { ChatMessage } from '@/api/system/chatMessage/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance, RuleObject } from 'ant-design-vue/es/form';
import 'bytemd/dist/index.min.css';
import 'github-markdown-css/github-markdown-light.css';
// // 链接、删除线、复选框、表格等的插件
import gfm from '@bytemd/plugin-gfm';
// // 插件的中文语言文件
import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json';
// 中文语言文件
import zh_Hans from 'bytemd/locales/zh_Hans.json';
import 'bytemd/dist/index.min.css';
import highlight from '@bytemd/plugin-highlight-ssr';
import 'highlight.js/styles/default.css';
import { MerchantAccount } from '@/api/shop/merchantAccount/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ChatMessage | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
const content = ref('');
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
const merchantAccount = ref<MerchantAccount[]>([]);
const formDataBatch = ref<ChatMessage[]>([]);
// 用户信息
const form = reactive<ChatMessage>({
formUserId: undefined,
toUserId: undefined,
toUserIds: undefined,
type: 'text',
content: '',
sideTo: undefined,
sideFrom: undefined,
withdraw: undefined,
fileInfo: undefined,
hasContact: undefined,
status: 0,
formUserName: '',
createTime: ''
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
toUserIds: [
{
required: true,
type: 'any',
message: '请选择用户',
trigger: 'blur'
}
],
type: [
{
required: true,
type: 'string',
message: '请选择消息类型',
trigger: 'blur'
}
],
content: [
{
required: true,
type: 'string',
message: '请填写消息内容',
trigger: 'blur',
validator: async (_rule: RuleObject, value: string) => {
if (content.value == '') {
return Promise.reject('请填写消息内容');
}
return Promise.resolve();
}
}
]
});
// 插件
const plugins = ref([
gfm({
locale: zh_HansGfm
}),
highlight()
]);
const onToUser = (list: MerchantAccount[]) => {
merchantAccount.value = list;
form.toUserIds = list.map((d) => d.phone);
form.toUserName = list.map((d) => d.realName);
console.log(form);
// form.toUserId = item.userId;
// form.toUserName = item.nickname;
};
const chooseType = (item: any) => {
form.type = 'text';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
if (!isUpdate.value) {
merchantAccount.value.map((d) => {
formDataBatch.value.push({
toUserId: d.userId,
type: form.type,
content: content.value
});
});
addBatchChatMessage(formDataBatch.value)
.then((msg) => {
loading.value = false;
form.toUserIds = [];
formDataBatch.value = [];
merchantAccount.value = [];
form.toUserName = undefined;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
return;
}
const formData = {
...form,
status: isUpdate.value ? 1 : 0,
content: content.value
};
const saveOrUpdate = isUpdate.value
? updateChatMessage
: addChatMessage;
saveOrUpdate(formData)
.then(() => {
loading.value = false;
form.toUserName = undefined;
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
content.value = '';
if (props.data) {
assignObject(form, props.data);
// 标记已读
updateChatMessage({ id: props.data.id, status: 1 });
emit('done');
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.user-content {
max-width: 100%;
border-radius: 8px !important;
background-color: #a2ec71;
border: none;
}
.admin-content {
border-radius: 8px !important;
border: 3px solid #f1f1f1;
}
</style>

View File

@@ -0,0 +1,69 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button
type="primary"
class="ele-btn-icon"
@click="add"
v-any-role="['superAdmin', 'merchant']"
>
<template #icon>
<PlusOutlined />
</template>
<span>发消息</span>
</a-button>
<a-input-search
allow-clear
placeholder="请输入关键词"
v-model:value="where.keywords"
@pressEnter="search"
@search="search"
/>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
import useSearch from '@/utils/use-search';
import { ChatMessageParam } from '@/api/system/chat/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<ChatMessageParam>({
keywords: '',
formUserId: undefined
});
/* 搜索 */
const search = () => {
emit('search', {
...where
});
};
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,243 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="chatMessageId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'content'">
<span v-if="record.type === 'card'" class="ele-text-placeholder"
>[卡片]</span
>
<span
v-else-if="record.type === 'text'"
v-html="record.content"
@click="openEdit(record)"
></span>
<span v-else class="ele-text-placeholder">[其他]</span>
</template>
<template v-if="column.key === 'status'">
<a-badge dot v-if="record.status === 0" status="error" />
<a-badge dot v-else status="default" />
</template>
<template v-if="column.key === 'action'">
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ChatMessageEdit
v-model:visible="showEdit"
:data="current"
@done="reload"
/>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import {
ExclamationCircleOutlined,
NotificationOutlined
} from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ChatMessageEdit from './components/chatMessageEdit.vue';
import {
pageChatMessage,
removeChatMessage,
removeBatchChatMessage
} from '@/api/system/chatMessage';
import type {
ChatMessage,
ChatMessageParam
} from '@/api/system/chatMessage/model';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
import { getPageTitle, getUserId } from '@/utils/common';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ChatMessage[]>([]);
// 当前编辑数据
const current = ref<ChatMessage | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 页面标题
const title = getPageTitle();
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where.toUserId = getUserId();
return pageChatMessage({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '未/已读',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 90
},
{
title: '消息内容',
dataIndex: 'content',
key: 'content'
},
{
title: '发送人',
dataIndex: 'formUserName',
key: 'formUserName',
width: 180,
align: 'center'
},
{
title: '发送时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
width: 180,
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
},
{
title: '操作',
key: 'action',
width: 120,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ChatMessageParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ChatMessage) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ChatMessage) => {
const hide = message.loading('请求中..', 0);
removeChatMessage(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchChatMessage(selection.value.map((d) => d.id))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: ChatMessage) => {
return {
// 行点击事件
onClick: () => {
// openEdit(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
watch(
currentRoute,
() => {
reload();
},
{ immediate: true }
);
</script>
<script lang="ts">
export default {
name: 'ChatMessage'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,175 @@
<!-- 订单编辑弹窗 -->
<template>
<ele-modal
:width="460"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '修改订单' : '添加订单'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="选择客户" name="companyId">
<SelectCompany v-model:value="form.tenantName" @done="onCompany" />
</a-form-item>
<a-form-item label="支付金额" name="money">
<a-input-number
allow-clear
max="1000000"
style="width: 200px"
placeholder="请输入订单金额"
v-model:value="form.money"
/>
</a-form-item>
<a-form-item label="到期时间" name="days">
<a-date-picker
v-model:value="form.expirationTime"
show-time
placeholder="到期时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</a-form-item>
<a-form-item label="备注">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { urlReg } from 'ele-admin-pro';
import { Order } from '@/api/system/order/model';
import { addOrder, updateOrder } from '@/api/system/order';
import { Company } from '@/api/system/company/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Order | null;
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Order>({
orderId: undefined,
money: undefined,
companyName: '',
companyId: 0,
tenantId: undefined,
tenantName: '',
comments: undefined
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
companyId: [
{
required: true,
message: '请选择客户',
pattern: urlReg,
type: 'number',
trigger: 'blur'
}
],
money: [
{
required: true,
message: '请输入订单金额',
type: 'number',
trigger: 'blur'
}
]
});
const onCompany = (item: Company) => {
console.log(item);
form.companyId = item.companyId;
form.tenantName = item.tenantName;
form.tenantId = item.tenantId;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateOrder : addOrder;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignFields(props.data);
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>

View File

@@ -0,0 +1,104 @@
<!-- 搜索表单 -->
<template>
<div style="display: flex; justify-content: space-between">
<a-space style="flex-wrap: wrap">
<!-- <a-button type="primary" class="ele-btn-icon" @click="add">-->
<!-- <template #icon>-->
<!-- <PlusOutlined />-->
<!-- </template>-->
<!-- <span>添加订单</span>-->
<!-- </a-button>-->
<a-button
danger
type="primary"
class="ele-btn-icon"
v-if="selection?.length > 0"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
</a-space>
<a-space :size="10" style="flex-wrap: wrap; margin-right: 20px">
<a-input-search
allow-clear
placeholder="请输入关键词"
v-model:value="searchText"
@pressEnter="search"
@search="search"
/>
</a-space>
</div>
</template>
<script lang="ts" setup>
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import useSearch from '@/utils/use-search';
import { ref, watch } from 'vue';
import { CompanyParam } from '@/api/system/company/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: CompanyParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'update', status?: number): void;
(e: 'import'): void;
}>();
// 表单数据
const { where, resetFields } = useSearch<CompanyParam>({
companyId: undefined,
companyName: undefined,
keywords: '',
authentication: undefined,
version: undefined,
province: '',
city: '',
region: ''
});
const tenantId = ref<number>();
if (localStorage.getItem('TenantId')) {
tenantId.value = Number(localStorage.getItem('TenantId'));
}
// 搜索内容
const searchText = ref('');
// 新增
const add = () => {
emit('add');
};
/* 搜索 */
const search = () => {
emit('search', {
...where
});
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,195 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="orderId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
:scroll="{ x: 800 }"
cache-key="proSystemOrderTable"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'money'">
<span class="ele-text-warning">
{{ formatNumber(record.money) }}
</span>
</template>
<template v-if="column.key === 'type'">
<a-tag v-if="record.type === 0">续费订单</a-tag>
<a-tag v-if="record.type === 1" color="purple">普通订单</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
placement="topRight"
title="确定要删除此模块吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<order-edit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { EleProTable, formatNumber } from 'ele-admin-pro/es';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import { messageLoading } from 'ele-admin-pro/es';
import OrderEdit from './components/order-edit.vue';
import { pageOrder, removeOrder } from '@/api/system/order';
import type { Order, OrderParam } from '@/api/system/order/model';
import { Menu } from '@/api/system/menu/model';
import Search from '@/views/system/order/components/search.vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { removeBatchCompany } from '@/api/system/company';
import { getUserId } from "@/utils/common";
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '订单号',
dataIndex: 'orderId',
width: 90
},
{
title: '订单类型',
dataIndex: 'type',
key: 'type',
align: 'center',
width: 120
},
{
title: '租户名称',
dataIndex: 'tenantName',
key: 'tenantName',
align: 'center'
},
{
title: '订单金额(元)',
dataIndex: 'money',
key: 'money',
align: 'center'
},
{
title: '备注',
dataIndex: 'comments',
align: 'center'
}
]);
// 表格选中数据
const selection = ref<Order[]>([]);
// 当前编辑数据
const current = ref<Order | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where.userId = getUserId();
return pageOrder({ ...where, ...orders, limit, page });
};
/* 搜索 */
const reload = (where?: OrderParam) => {
selection.value = [];
tableRef?.value?.reload({ page: 1, where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Order) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 自定义行属性 */
const customRow = (record: Menu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
/* 删除单个 */
const remove = (row: Order) => {
const hide = messageLoading('请求中..', 0);
removeOrder(row.orderId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchCompany(selection.value.map((d) => d.orderId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
</script>
<script lang="ts">
export default {
name: 'SystemOrder'
};
</script>