引入tailwindCss
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"@antv/g2": "^5.1.22",
|
||||
"@bytemd/plugin-gfm": "^1.17.2",
|
||||
"@bytemd/plugin-highlight": "^1.17.4",
|
||||
"@bytemd/plugin-highlight-ssr": "^1.20.2",
|
||||
|
||||
106
src/api/booking/cooperate/index.ts
Normal file
106
src/api/booking/cooperate/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Cooperate, CooperateParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询商务合作
|
||||
*/
|
||||
export async function pageCooperate(params: CooperateParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Cooperate>>>(
|
||||
MODULES_API_URL + '/booking/cooperate/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询商务合作列表
|
||||
*/
|
||||
export async function listCooperate(params?: CooperateParam) {
|
||||
const res = await request.get<ApiResult<Cooperate[]>>(
|
||||
MODULES_API_URL + '/booking/cooperate',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加商务合作
|
||||
*/
|
||||
export async function addCooperate(data: Cooperate) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商务合作
|
||||
*/
|
||||
export async function updateCooperate(data: Cooperate) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商务合作
|
||||
*/
|
||||
export async function removeCooperate(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除商务合作
|
||||
*/
|
||||
export async function removeBatchCooperate(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询商务合作
|
||||
*/
|
||||
export async function getCooperate(id: number) {
|
||||
const res = await request.get<ApiResult<Cooperate>>(
|
||||
MODULES_API_URL + '/booking/cooperate/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
33
src/api/booking/cooperate/model/index.ts
Normal file
33
src/api/booking/cooperate/model/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 商务合作
|
||||
*/
|
||||
export interface Cooperate {
|
||||
// ID
|
||||
cooperateId?: number;
|
||||
// 部门名称
|
||||
name?: string;
|
||||
// 咨询电话
|
||||
phone?: string;
|
||||
// 图片
|
||||
image?: string;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态
|
||||
status?: number;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 商务合作搜索条件
|
||||
*/
|
||||
export interface CooperateParam extends PageParam {
|
||||
cooperateId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
106
src/api/booking/cooperateLog/index.ts
Normal file
106
src/api/booking/cooperateLog/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { CooperateLog, CooperateLogParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询商务合作留言记录
|
||||
*/
|
||||
export async function pageCooperateLog(params: CooperateLogParam) {
|
||||
const res = await request.get<ApiResult<PageResult<CooperateLog>>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询商务合作留言记录列表
|
||||
*/
|
||||
export async function listCooperateLog(params?: CooperateLogParam) {
|
||||
const res = await request.get<ApiResult<CooperateLog[]>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加商务合作留言记录
|
||||
*/
|
||||
export async function addCooperateLog(data: CooperateLog) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商务合作留言记录
|
||||
*/
|
||||
export async function updateCooperateLog(data: CooperateLog) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商务合作留言记录
|
||||
*/
|
||||
export async function removeCooperateLog(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除商务合作留言记录
|
||||
*/
|
||||
export async function removeBatchCooperateLog(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询商务合作留言记录
|
||||
*/
|
||||
export async function getCooperateLog(id: number) {
|
||||
const res = await request.get<ApiResult<CooperateLog>>(
|
||||
MODULES_API_URL + '/booking/cooperate-log/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
35
src/api/booking/cooperateLog/model/index.ts
Normal file
35
src/api/booking/cooperateLog/model/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 商务合作留言记录
|
||||
*/
|
||||
export interface CooperateLog {
|
||||
// ID
|
||||
logId?: number;
|
||||
// 关联ID
|
||||
cooperateId?: number;
|
||||
// 部门名称
|
||||
name?: string;
|
||||
// 咨询电话
|
||||
phone?: string;
|
||||
// 图片
|
||||
image?: string;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态
|
||||
status?: number;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 商务合作留言记录搜索条件
|
||||
*/
|
||||
export interface CooperateLogParam extends PageParam {
|
||||
logId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
106
src/api/booking/match/index.ts
Normal file
106
src/api/booking/match/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Match, MatchParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询比赛信息表
|
||||
*/
|
||||
export async function pageMatch(params: MatchParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Match>>>(
|
||||
MODULES_API_URL + '/booking/match/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询比赛信息表列表
|
||||
*/
|
||||
export async function listMatch(params?: MatchParam) {
|
||||
const res = await request.get<ApiResult<Match[]>>(
|
||||
MODULES_API_URL + '/booking/match',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加比赛信息表
|
||||
*/
|
||||
export async function addMatch(data: Match) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改比赛信息表
|
||||
*/
|
||||
export async function updateMatch(data: Match) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除比赛信息表
|
||||
*/
|
||||
export async function removeMatch(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除比赛信息表
|
||||
*/
|
||||
export async function removeBatchMatch(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询比赛信息表
|
||||
*/
|
||||
export async function getMatch(id: number) {
|
||||
const res = await request.get<ApiResult<Match>>(
|
||||
MODULES_API_URL + '/booking/match/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
73
src/api/booking/match/model/index.ts
Normal file
73
src/api/booking/match/model/index.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 比赛信息表
|
||||
*/
|
||||
export interface Match {
|
||||
// 赛事ID
|
||||
matchId?: number;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 比赛类型 0常规比赛
|
||||
type?: number;
|
||||
// 活动开始时间
|
||||
matchStartTime?: number;
|
||||
// 活动结束时间
|
||||
matchEndTime?: number;
|
||||
// 报名时间
|
||||
bmStartTime?: number;
|
||||
// 报名时间
|
||||
bmEndTime?: number;
|
||||
// 文章分类ID
|
||||
categoryId?: number;
|
||||
// 封面图
|
||||
image?: string;
|
||||
// 虚拟阅读量(仅用作展示)
|
||||
virtualViews?: number;
|
||||
// 实际阅读量
|
||||
actualViews?: number;
|
||||
// 文章附件
|
||||
files?: string;
|
||||
// 视频地址
|
||||
video?: string;
|
||||
// 退费规则
|
||||
refundRule?: string;
|
||||
// 活动介绍
|
||||
content?: string;
|
||||
// 经度
|
||||
longitude?: string;
|
||||
// 纬度
|
||||
latitude?: string;
|
||||
// 比赛活动地点
|
||||
address?: string;
|
||||
// 报名费用
|
||||
price?: string;
|
||||
// 已报名人数
|
||||
users?: number;
|
||||
// 报名人数限制
|
||||
maxUsers?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0未开始, 1进行中,2已结束
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比赛信息表搜索条件
|
||||
*/
|
||||
export interface MatchParam extends PageParam {
|
||||
matchId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
106
src/api/booking/matchOrder/index.ts
Normal file
106
src/api/booking/matchOrder/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { MatchOrder, MatchOrderParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询比赛报名记录表
|
||||
*/
|
||||
export async function pageMatchOrder(params: MatchOrderParam) {
|
||||
const res = await request.get<ApiResult<PageResult<MatchOrder>>>(
|
||||
MODULES_API_URL + '/booking/match-order/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询比赛报名记录表列表
|
||||
*/
|
||||
export async function listMatchOrder(params?: MatchOrderParam) {
|
||||
const res = await request.get<ApiResult<MatchOrder[]>>(
|
||||
MODULES_API_URL + '/booking/match-order',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加比赛报名记录表
|
||||
*/
|
||||
export async function addMatchOrder(data: MatchOrder) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match-order',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改比赛报名记录表
|
||||
*/
|
||||
export async function updateMatchOrder(data: MatchOrder) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match-order',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除比赛报名记录表
|
||||
*/
|
||||
export async function removeMatchOrder(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match-order/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除比赛报名记录表
|
||||
*/
|
||||
export async function removeBatchMatchOrder(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/booking/match-order/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询比赛报名记录表
|
||||
*/
|
||||
export async function getMatchOrder(id: number) {
|
||||
const res = await request.get<ApiResult<MatchOrder>>(
|
||||
MODULES_API_URL + '/booking/match-order/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
47
src/api/booking/matchOrder/model/index.ts
Normal file
47
src/api/booking/matchOrder/model/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 比赛报名记录表
|
||||
*/
|
||||
export interface MatchOrder {
|
||||
// 赛事ID
|
||||
matchOrderId?: number;
|
||||
// 赛事ID
|
||||
matchId?: number;
|
||||
// 场次
|
||||
session?: number;
|
||||
// 比赛活动地点
|
||||
address?: string;
|
||||
// 姓名
|
||||
name?: number;
|
||||
// 性别
|
||||
sex?: number;
|
||||
// 身份证号码
|
||||
idCardNo?: string;
|
||||
// 手机号码
|
||||
phone?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0未开始, 1进行中,2已结束
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比赛报名记录表搜索条件
|
||||
*/
|
||||
export interface MatchOrderParam extends PageParam {
|
||||
matchOrderId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
185
src/api/dashboard/analysis/20200610/analysis-hot-search.json
Executable file
185
src/api/dashboard/analysis/20200610/analysis-hot-search.json
Executable file
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"code": 0,
|
||||
"data": [
|
||||
{
|
||||
"name": "软妹子",
|
||||
"value": 23
|
||||
},
|
||||
{
|
||||
"name": "汪星人",
|
||||
"value": 23
|
||||
},
|
||||
{
|
||||
"name": "长腿欧巴",
|
||||
"value": 23
|
||||
},
|
||||
{
|
||||
"name": "萝莉",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"name": "辣~",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"name": "K歌",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"name": "大长腿",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"name": "川妹子",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"name": "女神",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"name": "米粉",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"name": "专注设计",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"name": "逛街",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"name": "黑长直",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"name": "海纳百川",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"name": "萌萌哒",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"name": "坚持",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"name": "话唠",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"name": "果粉",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"name": "喵星人",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"name": "花粉",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"name": "衬衫控",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"name": "宅男",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"name": "小清新",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"name": "眼镜男",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"name": "琼瑶",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"name": "穷游党",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"name": "铲屎官",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"name": "正太",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"name": "中二病",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"name": "夜猫子",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"name": "逗比",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"name": "腹黑",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"name": "吃鸡",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"name": "为了联盟",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"name": "背包客",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"name": "民谣",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"name": "为了部落",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"name": "懒癌患者",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"name": "追剧",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"name": "IT民工",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"name": "CNB成员",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"name": "选择困难",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"name": "锤粉",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"name": "欧皇",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"name": "仙气十足",
|
||||
"value": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
73
src/api/dashboard/analysis/20200610/analysis-pay-num.json
Executable file
73
src/api/dashboard/analysis/20200610/analysis-pay-num.json
Executable file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"code": 0,
|
||||
"data": [
|
||||
{
|
||||
"date": "2020-06-12",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"date": "2020-06-13",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"date": "2020-06-14",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"date": "2020-06-15",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"date": "2020-06-16",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"date": "2020-06-17",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"date": "2020-06-18",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"date": "2020-06-19",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"date": "2020-06-20",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"date": "2020-06-21",
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"date": "2020-06-22",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"date": "2020-06-23",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"date": "2020-06-24",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"date": "2020-06-25",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"date": "2020-06-26",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"date": "2020-06-27",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"date": "2020-06-18",
|
||||
"value": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
105
src/api/dashboard/analysis/20200610/analysis-saleroom.json
Executable file
105
src/api/dashboard/analysis/20200610/analysis-saleroom.json
Executable file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list1": [
|
||||
{
|
||||
"month": "1月",
|
||||
"value": 816
|
||||
},
|
||||
{
|
||||
"month": "2月",
|
||||
"value": 542
|
||||
},
|
||||
{
|
||||
"month": "3月",
|
||||
"value": 914
|
||||
},
|
||||
{
|
||||
"month": "4月",
|
||||
"value": 781
|
||||
},
|
||||
{
|
||||
"month": "5月",
|
||||
"value": 355
|
||||
},
|
||||
{
|
||||
"month": "6月",
|
||||
"value": 796
|
||||
},
|
||||
{
|
||||
"month": "7月",
|
||||
"value": 714
|
||||
},
|
||||
{
|
||||
"month": "8月",
|
||||
"value": 1195
|
||||
},
|
||||
{
|
||||
"month": "9月",
|
||||
"value": 1055
|
||||
},
|
||||
{
|
||||
"month": "10月",
|
||||
"value": 271
|
||||
},
|
||||
{
|
||||
"month": "11月",
|
||||
"value": 384
|
||||
},
|
||||
{
|
||||
"month": "12月",
|
||||
"value": 1098
|
||||
}
|
||||
],
|
||||
"list2": [
|
||||
{
|
||||
"month": "1月",
|
||||
"value": 1098
|
||||
},
|
||||
{
|
||||
"month": "2月",
|
||||
"value": 384
|
||||
},
|
||||
{
|
||||
"month": "3月",
|
||||
"value": 271
|
||||
},
|
||||
{
|
||||
"month": "4月",
|
||||
"value": 1055
|
||||
},
|
||||
{
|
||||
"month": "5月",
|
||||
"value": 1195
|
||||
},
|
||||
{
|
||||
"month": "6月",
|
||||
"value": 714
|
||||
},
|
||||
{
|
||||
"month": "7月",
|
||||
"value": 796
|
||||
},
|
||||
{
|
||||
"month": "8月",
|
||||
"value": 355
|
||||
},
|
||||
{
|
||||
"month": "9月",
|
||||
"value": 781
|
||||
},
|
||||
{
|
||||
"month": "10月",
|
||||
"value": 914
|
||||
},
|
||||
{
|
||||
"month": "11月",
|
||||
"value": 542
|
||||
},
|
||||
{
|
||||
"month": "12月",
|
||||
"value": 816
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
60
src/api/dashboard/analysis/20200610/analysis-visits.json
Executable file
60
src/api/dashboard/analysis/20200610/analysis-visits.json
Executable file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"code": 0,
|
||||
"data": [
|
||||
{
|
||||
"time": "16:00",
|
||||
"visits": 15,
|
||||
"views": 45
|
||||
},
|
||||
{
|
||||
"time": "16:05",
|
||||
"visits": 39,
|
||||
"views": 169
|
||||
},
|
||||
{
|
||||
"time": "16:10",
|
||||
"visits": 152,
|
||||
"views": 400
|
||||
},
|
||||
{
|
||||
"time": "16:15",
|
||||
"visits": 94,
|
||||
"views": 285
|
||||
},
|
||||
{
|
||||
"time": "16:20",
|
||||
"visits": 102,
|
||||
"views": 316
|
||||
},
|
||||
{
|
||||
"time": "16:25",
|
||||
"visits": 86,
|
||||
"views": 148
|
||||
},
|
||||
{
|
||||
"time": "16:30",
|
||||
"visits": 39,
|
||||
"views": 150
|
||||
},
|
||||
{
|
||||
"time": "16:35",
|
||||
"visits": 38,
|
||||
"views": 234
|
||||
},
|
||||
{
|
||||
"time": "16:40",
|
||||
"visits": 95,
|
||||
"views": 158
|
||||
},
|
||||
{
|
||||
"time": "16:45",
|
||||
"visits": 30,
|
||||
"views": 100
|
||||
},
|
||||
{
|
||||
"time": "16:50",
|
||||
"visits": 86,
|
||||
"views": 266
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult } from '@/api';
|
||||
import type { PayNumData, SaleroomResult, VisitData, CloudData } from './model';
|
||||
import type { PayNumData, VisitData, CloudData } from './model';
|
||||
|
||||
/**
|
||||
* 获取支付笔数数据
|
||||
*/
|
||||
export async function getPayNumList() {
|
||||
const res = await request.get<ApiResult<PayNumData[]>>(
|
||||
'https://www.gxwebsoft.com/20200610/analysis-pay-num.json'
|
||||
'./20200610/analysis-pay-num.json'
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
@@ -19,9 +19,10 @@ export async function getPayNumList() {
|
||||
* 获取销售量数据
|
||||
*/
|
||||
export async function getSaleroomList() {
|
||||
const res = await request.get<ApiResult<SaleroomResult>>(
|
||||
'https://www.gxwebsoft.com/20200610/analysis-saleroom.json'
|
||||
);
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
const res = await readFile('./20200610/analysis-saleroom.json');
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
@@ -34,7 +35,7 @@ export async function getSaleroomList() {
|
||||
*/
|
||||
export async function getVisitHourList() {
|
||||
const res = await request.get<ApiResult<VisitData[]>>(
|
||||
'https://www.gxwebsoft.com/20200610/analysis-visits.json'
|
||||
'./20200610/analysis-visits.json'
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
@@ -47,7 +48,7 @@ export async function getVisitHourList() {
|
||||
*/
|
||||
export async function getWordCloudList() {
|
||||
const res = await request.get<ApiResult<CloudData[]>>(
|
||||
'https://www.gxwebsoft.com/20200610/analysis-hot-search.json'
|
||||
'./20200610/analysis-hot-search.json'
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function getChinaMapData() {
|
||||
*/
|
||||
export async function getUserCountList() {
|
||||
const res = await request.get<ApiResult<UserCount[]>>(
|
||||
'https://www.gxwebsoft.com/20200610/monitor-user-count.json'
|
||||
'https://cdn.eleadmin.com/20200610/monitor-user-count.json'
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
@@ -35,7 +35,7 @@ export async function getUserCountList() {
|
||||
*/
|
||||
export async function getBrowserCountList() {
|
||||
const res = await request.get<ApiResult<BrowserCount[]>>(
|
||||
'https://www.gxwebsoft.com/20200610/monitor-browser-count.json'
|
||||
'https://cdn.eleadmin.com/20200610/monitor-browser-count.json'
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<!-- 最新动态 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '6px 0' }">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div
|
||||
style="height: 346px; padding: 22px 20px 0 20px"
|
||||
class="ele-scrollbar-hover"
|
||||
>
|
||||
<a-timeline>
|
||||
<a-timeline-item
|
||||
v-for="item in activities"
|
||||
:key="item.id"
|
||||
:color="item.color"
|
||||
>
|
||||
<em>{{ item.time }}</em>
|
||||
<em>{{ item.title }}</em>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
interface Activitie {
|
||||
id: number;
|
||||
title: string;
|
||||
time: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 最新动态数据
|
||||
const activities = ref<Activitie[]>([]);
|
||||
|
||||
/* 查询最新动态 */
|
||||
const queryActivities = () => {
|
||||
activities.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'SunSmile 解决了bug 登录提示操作失败',
|
||||
time: '20:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Jasmine 解决了bug 按钮颜色与设计不符',
|
||||
time: '19:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '项目经理 指派了任务 解决项目一的bug',
|
||||
time: '18:30'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '项目经理 指派了任务 解决项目二的bug',
|
||||
time: '17:30'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '项目经理 指派了任务 解决项目三的bug',
|
||||
time: '16:30'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '项目经理 指派了任务 解决项目四的bug',
|
||||
time: '15:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '项目经理 指派了任务 解决项目五的bug',
|
||||
time: '14:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '项目经理 指派了任务 解决项目六的bug',
|
||||
time: '12:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: '项目经理 指派了任务 解决项目七的bug',
|
||||
time: '11:30'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: '项目经理 指派了任务 解决项目八的bug',
|
||||
time: '10:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: '项目经理 指派了任务 解决项目九的bug',
|
||||
time: '09:30',
|
||||
color: 'green'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: '项目经理 指派了任务 解决项目十的bug',
|
||||
time: '08:30',
|
||||
color: 'red'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryActivities();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-scrollbar-hover
|
||||
:deep(.ant-timeline-item-last > .ant-timeline-item-content) {
|
||||
min-height: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px' }">
|
||||
<template #extra
|
||||
><a @click="openUrl('/oa/app/index')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<div class="app-box">
|
||||
<a-image
|
||||
:height="45"
|
||||
:width="45"
|
||||
:preview="false"
|
||||
:src="item.appIcon"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<div class="app-info">
|
||||
<a
|
||||
class="ele-text-heading"
|
||||
@click="openNew('/oa/app/detail/' + item.appId)"
|
||||
>
|
||||
{{ item.appName }}
|
||||
</a>
|
||||
<span class="ele-text-placeholder">
|
||||
{{ item.appCode }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Article } from '@/api/cms/article/model';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { pageApp } from '@/api/oa/app';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const list = ref<Article[]>([]);
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { title } = props;
|
||||
// 加载文章列表
|
||||
pageApp({
|
||||
limit: 5,
|
||||
status: 0,
|
||||
appStatus: '开发中'
|
||||
}).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-box {
|
||||
display: flex;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 5px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px' }">
|
||||
<template #extra
|
||||
><a
|
||||
@click="openNew('/cms/category/' + categoryId)"
|
||||
class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a
|
||||
class="ele-text-secondary"
|
||||
@click="openUrl('/cms/article/' + item.articleId)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { pageArticle } from '@/api/cms/article';
|
||||
import { Article } from '@/api/cms/article/model';
|
||||
import { openNew, openUrl } from '@/utils/common';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
const list = ref<Article[]>([]);
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
categoryId: number;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { categoryId } = props;
|
||||
// 加载文章列表
|
||||
pageArticle({ categoryId, limit: 6 }).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="小组成员">
|
||||
<template #extra>
|
||||
<a-tooltip>
|
||||
<template #title>邀请加入</template>
|
||||
<UserAddOutlined @click="onShowQrcode" :style="{ fontSize: '18px' }" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-list
|
||||
class="demo-loadmore-list"
|
||||
item-layout="horizontal"
|
||||
:data-source="list"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a-popover>
|
||||
<template #content> 待处理 </template>
|
||||
<a class="ele-text-danger">{{ item.pending }}</a>
|
||||
</a-popover>
|
||||
<a-popover>
|
||||
<template #content> 本月已处理 </template>
|
||||
<a class="ele-text-secondary">{{ item.month }}</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<a-skeleton avatar :title="false" :loading="!!item.loading" active>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
{{ item.nickname }}
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.avatar" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-skeleton>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<!-- 工单二维码 -->
|
||||
<Qrcode v-model:visible="showQrcode" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { pageTaskCount } from '@/api/oa/task-count';
|
||||
import { TaskCount } from '@/api/oa/task-count/model';
|
||||
import { UserAddOutlined } from '@ant-design/icons-vue';
|
||||
import Qrcode from './qrcode.vue';
|
||||
|
||||
const showQrcode = ref(false);
|
||||
const list = ref<TaskCount[]>([]);
|
||||
|
||||
pageTaskCount({ limit: 10, roleCode: 'commander' }).then((res) => {
|
||||
if (res) {
|
||||
list.value = res?.list;
|
||||
}
|
||||
});
|
||||
|
||||
const onShowQrcode = () => {
|
||||
showQrcode.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-evaluate-text {
|
||||
width: 90px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
opacity: 0.8;
|
||||
|
||||
& > .anticon {
|
||||
font-size: 12px;
|
||||
margin: 0 6px 0 8px;
|
||||
}
|
||||
}
|
||||
/deep/.ant-list-item {
|
||||
padding: 7px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,70 +0,0 @@
|
||||
<!-- 本月目标 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div class="workplace-goal-group">
|
||||
<a-progress
|
||||
:width="180"
|
||||
:percent="80"
|
||||
type="dashboard"
|
||||
:stroke-width="4"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div class="workplace-goal-content">
|
||||
<ele-tag color="blue" size="large" shape="circle">
|
||||
<trophy-outlined />
|
||||
</ele-tag>
|
||||
<div class="workplace-goal-num">285</div>
|
||||
</div>
|
||||
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TrophyOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-goal-group {
|
||||
height: 310px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.workplace-goal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 180px;
|
||||
margin: -50px 0 0 -90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workplace-goal-num {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,70 +0,0 @@
|
||||
<!-- 本月目标 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div class="workplace-goal-group">
|
||||
<a-progress
|
||||
:width="180"
|
||||
:percent="80"
|
||||
type="dashboard"
|
||||
:stroke-width="4"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div class="workplace-goal-content">
|
||||
<ele-tag color="blue" size="large" shape="circle">
|
||||
<trophy-outlined />
|
||||
</ele-tag>
|
||||
<div class="workplace-goal-num">285</div>
|
||||
</div>
|
||||
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TrophyOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-goal-group {
|
||||
height: 310px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.workplace-goal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 180px;
|
||||
margin: -50px 0 0 -90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workplace-goal-num {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
72
src/views/system/dashboard/components/hot-search.vue
Normal file
72
src/views/system/dashboard/components/hot-search.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="热门搜索">
|
||||
<v-chart
|
||||
ref="hotSearchChartRef"
|
||||
:option="hotSearchChartOption"
|
||||
style="height: 330px"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart, BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import 'echarts-wordcloud';
|
||||
import { wordCloudColor } from 'ele-admin-pro/es';
|
||||
import { getWordCloudList } from '@/api/dashboard/analysis';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]);
|
||||
|
||||
//
|
||||
const hotSearchChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([hotSearchChartRef]);
|
||||
|
||||
// 词云图表配置
|
||||
const hotSearchChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取词云数据 */
|
||||
const getWordCloudData = () => {
|
||||
getWordCloudList()
|
||||
.then((data) => {
|
||||
Object.assign(hotSearchChartOption, {
|
||||
tooltip: {
|
||||
show: true,
|
||||
confine: true,
|
||||
borderWidth: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
sizeRange: [12, 24],
|
||||
gridSize: 6,
|
||||
textStyle: {
|
||||
color: wordCloudColor
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 8,
|
||||
shadowColor: 'rgba(0, 0, 0, .15)'
|
||||
}
|
||||
},
|
||||
data: data
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getWordCloudData();
|
||||
</script>
|
||||
@@ -1,178 +0,0 @@
|
||||
<!-- 快捷方式 -->
|
||||
<template>
|
||||
<a-row :gutter="16" ref="wrapRef">
|
||||
<a-col v-for="item in data" :key="item.url" :lg="3" :md="6" :sm="9" :xs="8">
|
||||
<a-card :bordered="false" hoverable :body-style="{ padding: 0 }">
|
||||
<div class="app-link-block" @click="navTo(item)">
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="app-link-icon"
|
||||
:style="{ color: item.color }"
|
||||
/>
|
||||
<div class="app-link-title">{{ item.title }}</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import SortableJs from 'sortablejs';
|
||||
import type { Row as ARow } from 'ant-design-vue';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { getOriginDomain } from '@/utils/domain';
|
||||
const CACHE_KEY = 'workplace-links';
|
||||
// 当前开发环境
|
||||
const env = import.meta.env.MODE;
|
||||
|
||||
interface LinkItem {
|
||||
icon: string;
|
||||
title: string;
|
||||
url: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 默认顺序
|
||||
const DEFAULT: LinkItem[] = [
|
||||
{
|
||||
icon: 'settingOutlined',
|
||||
title: '系统设置',
|
||||
url: '/system/setting'
|
||||
},
|
||||
{
|
||||
icon: 'AntDesignOutlined',
|
||||
title: '项目管理',
|
||||
url: '/oa/app/index'
|
||||
},
|
||||
{
|
||||
icon: 'TeamOutlined',
|
||||
title: '用户管理',
|
||||
url: '/system/user'
|
||||
},
|
||||
{
|
||||
icon: 'ShoppingOutlined',
|
||||
title: '产品管理',
|
||||
url: '/product/index'
|
||||
},
|
||||
{
|
||||
icon: 'FileSearchOutlined',
|
||||
title: '文章管理',
|
||||
url: '/cms/article'
|
||||
},
|
||||
{
|
||||
icon: 'ChromeOutlined',
|
||||
title: '网址导航',
|
||||
url: '/oa/link'
|
||||
},
|
||||
{
|
||||
icon: 'AppstoreAddOutlined',
|
||||
title: '扩展插件',
|
||||
url: '/system/plug'
|
||||
},
|
||||
{
|
||||
icon: 'DesktopOutlined',
|
||||
title: '应用主页',
|
||||
url: `http://${localStorage.getItem('TenantId')}.${localStorage.getItem(
|
||||
'domain'
|
||||
)}`
|
||||
}
|
||||
];
|
||||
|
||||
// 获取缓存的顺序
|
||||
const cache = (() => {
|
||||
const str = localStorage.getItem(CACHE_KEY);
|
||||
try {
|
||||
return str ? JSON.parse(str) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const data = ref<LinkItem[]>([...(cache ?? DEFAULT)]);
|
||||
|
||||
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
|
||||
|
||||
let sortableIns: SortableJs | null = null;
|
||||
|
||||
/* 重置布局 */
|
||||
const reset = () => {
|
||||
data.value = [...DEFAULT];
|
||||
cacheData();
|
||||
};
|
||||
|
||||
/* 缓存布局 */
|
||||
const cacheData = () => {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
|
||||
};
|
||||
|
||||
const navTo = (item) => {
|
||||
if (item.icon == 'DesktopOutlined') {
|
||||
if (env == 'development') {
|
||||
return openUrl(getOriginDomain());
|
||||
}
|
||||
return openUrl(`http://www.${domain.value}`);
|
||||
}
|
||||
openUrl(item.url);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const isTouchDevice = 'ontouchstart' in document.documentElement;
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
sortableIns = new SortableJs(wrapRef.value?.$el, {
|
||||
animation: 300,
|
||||
onUpdate: ({ oldIndex, newIndex }) => {
|
||||
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
|
||||
const temp = [...data.value];
|
||||
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
|
||||
data.value = temp;
|
||||
cacheData();
|
||||
}
|
||||
},
|
||||
setData: () => {}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sortableIns) {
|
||||
sortableIns.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ reset });
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from './link-icons';
|
||||
import { getSiteInfo } from '@/api/layout';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const tenantId = ref<number>();
|
||||
const domain = ref<string>();
|
||||
|
||||
getSiteInfo().then((data) => {
|
||||
tenantId.value = data.tenantId;
|
||||
domain.value = data.domain;
|
||||
});
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-link-block {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
color: inherit;
|
||||
|
||||
.app-link-icon {
|
||||
color: #666666;
|
||||
font-size: 30px;
|
||||
margin: 6px 0 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +0,0 @@
|
||||
export {
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
FileSearchOutlined,
|
||||
ChromeOutlined,
|
||||
ShoppingOutlined,
|
||||
LaptopOutlined,
|
||||
AppstoreAddOutlined,
|
||||
DesktopOutlined,
|
||||
AntDesignOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
@@ -1,54 +0,0 @@
|
||||
<template>
|
||||
<a-card :title="linkType" :bordered="false" :body-style="{ padding: '2px' }">
|
||||
<template #extra
|
||||
><a @click="openNew('/oa/link')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.icon" />
|
||||
</template>
|
||||
<template #title>
|
||||
<a @click="openUrl(item.url)">{{ item.name }}</a>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { openNew, openUrl } from '@/utils/common';
|
||||
import { pageLink } from '@/api/oa/link';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
const props = defineProps<{
|
||||
linkType: string;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { linkType } = props;
|
||||
// 加载文章列表
|
||||
pageLink({ linkType, limit: 5 }).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<a-dropdown placement="bottomRight">
|
||||
<more-outlined class="ele-text-secondary" style="font-size: 18px" />
|
||||
<template #overlay>
|
||||
<a-menu :selectable="false" @click="onClick">
|
||||
<a-menu-item key="edit">
|
||||
<div class="ele-cell">
|
||||
<edit-outlined />
|
||||
<div class="ele-cell-content">编辑</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="remove">
|
||||
<div class="ele-cell ele-text-danger">
|
||||
<delete-outlined />
|
||||
<div class="ele-cell-content">删除</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
MoreOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'edit'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const onClick = ({ key }) => {
|
||||
emit(key);
|
||||
};
|
||||
</script>
|
||||
@@ -1,121 +0,0 @@
|
||||
<!-- 用户信息 -->
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ padding: '20px' }">
|
||||
<div class="ele-cell workplace-user-card">
|
||||
<div class="ele-cell-content ele-cell">
|
||||
<a-avatar :size="68" :src="loginUser.avatar">
|
||||
<template v-if="!loginUser.avatar" #icon>
|
||||
<user-outlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<div class="ele-cell-content">
|
||||
<h4 class="ele-elip">
|
||||
早安, {{ loginUser.nickname }}, 开始您一天的工作吧!
|
||||
</h4>
|
||||
<div class="ele-elip ele-text-secondary">
|
||||
<cloud-outlined />
|
||||
<em>{{ elip[Math.floor(Math.random() * elip.length)] }}</em>
|
||||
<!-- <em>今日多云转阴,18℃ - 22℃,出门记得穿外套哦~</em>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workplace-count-group">
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="blue" shape="circle" size="small">-->
|
||||
<!-- <appstore-filled />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">项目数</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>0</h2>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="orange" shape="circle" size="small">-->
|
||||
<!-- <check-square-outlined />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">待办项</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>6 / 24</h2>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="green" shape="circle" size="small">-->
|
||||
<!-- <bell-filled />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">消息</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>0</h2>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
CloudOutlined,
|
||||
AppstoreFilled,
|
||||
CheckSquareOutlined,
|
||||
BellFilled
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 当前登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
const elip = ref<string[]>([
|
||||
'小事成就大事,细节成就完美~',
|
||||
'心态决定命运,自信走向成功',
|
||||
'人生能有几回博,今日不博何时博',
|
||||
'成功需要成本,时间也是一种成本,对时间的珍惜就是对成本的节约',
|
||||
'有志者自有千方百计,无志者只感千难万难',
|
||||
'积一时之跬步,臻千里之遥程'
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-user-card {
|
||||
.ele-cell-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.workplace-count-group {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workplace-count-item {
|
||||
display: inline-block;
|
||||
margin: 0 4px 0 24px;
|
||||
}
|
||||
|
||||
.workplace-count-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.workplace-count-item {
|
||||
margin: 0 2px 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.workplace-user-card {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.workplace-count-group {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,66 +0,0 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="400"
|
||||
:visible="visible"
|
||||
:title="`邀请新成员`"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
@ok="save"
|
||||
>
|
||||
<div
|
||||
class="qrcode-list"
|
||||
style="display: flex; justify-content: space-around"
|
||||
>
|
||||
<div>
|
||||
<img :src="qrcode" width="240" height="240" />
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 26px;
|
||||
padding-top: 20px;
|
||||
"
|
||||
>使用微信扫一扫</div
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { User } from '@/api/system/user/model';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { taskJoinQRCode } from '@/api/oa/task';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', user: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const qrcode = ref('');
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<User>({
|
||||
userId: undefined,
|
||||
nickname: undefined
|
||||
});
|
||||
|
||||
const save = () => {
|
||||
emit('done', form);
|
||||
};
|
||||
|
||||
taskJoinQRCode({}).then((text) => {
|
||||
qrcode.value = String(text);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
||||
247
src/views/system/dashboard/components/sale-card.vue
Normal file
247
src/views/system/dashboard/components/sale-card.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ padding: 0 }">
|
||||
<a-tabs
|
||||
size="large"
|
||||
v-model:activeKey="saleSearch.type"
|
||||
class="monitor-card-tabs"
|
||||
@change="onSaleTypeChange"
|
||||
>
|
||||
<a-tab-pane tab="销售额" key="saleroom" />
|
||||
<template #rightExtra>
|
||||
<a-space
|
||||
size="middle"
|
||||
:class="[
|
||||
'analysis-tabs-extra',
|
||||
{ 'hidden-lg-and-down': styleResponsive }
|
||||
]"
|
||||
>
|
||||
<a-radio-group v-model:value="saleSearch.dateType">
|
||||
<a-radio-button value="1">今天</a-radio-button>
|
||||
<a-radio-button value="2">本周</a-radio-button>
|
||||
<a-radio-button value="3">本月</a-radio-button>
|
||||
<a-radio-button value="4">本年</a-radio-button>
|
||||
</a-radio-group>
|
||||
<div style="width: 300px">
|
||||
<a-range-picker
|
||||
value-format="YYYY-MM-DD"
|
||||
v-model:value="saleSearch.datetime"
|
||||
/>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-tabs>
|
||||
<div style="padding-bottom: 10px">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 17, md: 15, sm: 24, xs: 24 } : { span: 17 }
|
||||
"
|
||||
>
|
||||
<div v-if="saleSearch.type === 'saleroom'" class="demo-monitor-title">
|
||||
销售量趋势
|
||||
</div>
|
||||
<div v-else class="demo-monitor-title">访问量趋势</div>
|
||||
<v-chart
|
||||
ref="saleChartRef"
|
||||
:option="saleChartOption"
|
||||
style="height: 320px"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 7, md: 9, sm: 24, xs: 24 } : { span: 7 }
|
||||
"
|
||||
>
|
||||
<div v-if="saleSearch.type === 'saleroom'">
|
||||
<div class="demo-monitor-title">门店销售额排名</div>
|
||||
<div
|
||||
v-for="(item, index) in saleroomRankData"
|
||||
:key="index"
|
||||
class="demo-monitor-rank-item ele-cell"
|
||||
>
|
||||
<ele-tag
|
||||
shape="circle"
|
||||
:color="index < 3 ? '#314659' : ''"
|
||||
style="border: none"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</ele-tag>
|
||||
<div class="ele-cell-content ele-elip">{{ item.name }}</div>
|
||||
<div class="ele-text-secondary">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="demo-monitor-title">门店访问量排名</div>
|
||||
<div
|
||||
v-for="(item, index) in visitsRankData"
|
||||
:key="index"
|
||||
class="demo-monitor-rank-item ele-cell"
|
||||
>
|
||||
<ele-tag
|
||||
shape="circle"
|
||||
:color="index < 3 ? '#314659' : ''"
|
||||
style="border: none"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</ele-tag>
|
||||
<div class="ele-cell-content ele-elip">{{ item.name }}</div>
|
||||
<div class="ele-text-secondary">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getSaleroomList } from '@/api/dashboard/analysis';
|
||||
import type { SaleroomData } from '@/api/dashboard/analysis/model';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]);
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
//
|
||||
const saleChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([saleChartRef]);
|
||||
|
||||
// 销售额柱状图配置
|
||||
const saleChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
// 门店销售排名数据
|
||||
const saleroomRankData = ref([
|
||||
{ name: '工专路 1 号店', value: '323,234' },
|
||||
{ name: '工专路 2 号店', value: '323,234' },
|
||||
{ name: '工专路 3 号店', value: '323,234' },
|
||||
{ name: '工专路 4 号店', value: '323,234' },
|
||||
{ name: '工专路 5 号店', value: '323,234' },
|
||||
{ name: '工专路 6 号店', value: '323,234' },
|
||||
{ name: '工专路 7 号店', value: '323,234' }
|
||||
]);
|
||||
|
||||
// 门店访问排名数据
|
||||
const visitsRankData = ref([
|
||||
{ name: '工专路 1 号店', value: '323,234' },
|
||||
{ name: '工专路 2 号店', value: '323,234' },
|
||||
{ name: '工专路 3 号店', value: '323,234' },
|
||||
{ name: '工专路 4 号店', value: '323,234' },
|
||||
{ name: '工专路 5 号店', value: '323,234' },
|
||||
{ name: '工专路 6 号店', value: '323,234' },
|
||||
{ name: '工专路 7 号店', value: '323,234' }
|
||||
]);
|
||||
|
||||
// 销售量趋势数据
|
||||
const saleroomData1 = ref<SaleroomData[]>([]);
|
||||
|
||||
// 访问量趋势数据
|
||||
const saleroomData2 = ref<SaleroomData[]>([]);
|
||||
|
||||
interface SaleSearchType {
|
||||
type: string;
|
||||
dateType: string;
|
||||
datetime: [string, string];
|
||||
}
|
||||
|
||||
// 销售量搜索参数
|
||||
const saleSearch = reactive<SaleSearchType>({
|
||||
type: 'saleroom',
|
||||
dateType: '1',
|
||||
datetime: ['2022-01-08', '2022-02-12']
|
||||
});
|
||||
|
||||
/* 获取销售量数据 */
|
||||
const getSaleroomData = () => {
|
||||
getSaleroomList()
|
||||
.then((data) => {
|
||||
saleroomData1.value = data.list1;
|
||||
saleroomData2.value = data.list2;
|
||||
onSaleTypeChange();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 销售量tab选择改变事件 */
|
||||
const onSaleTypeChange = () => {
|
||||
if (saleSearch.type === 'saleroom') {
|
||||
Object.assign(saleChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: saleroomData1.value.map((d) => d.month)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: saleroomData1.value.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
Object.assign(saleChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: saleroomData2.value.map((d) => d.month)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: saleroomData2.value.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getSaleroomData();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-card-tabs :deep(.ant-tabs-nav) {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.demo-monitor-title {
|
||||
padding: 6px 20px;
|
||||
}
|
||||
|
||||
.demo-monitor-rank-item {
|
||||
padding: 0 20px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
||||
246
src/views/system/dashboard/components/statistics-card.vue
Normal file
246
src/views/system/dashboard/components/statistics-card.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<!-- 统计卡片 -->
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">总销售额</div>
|
||||
<a-tooltip title="指标说明">
|
||||
<question-circle-outlined />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">¥ 126,560</h1>
|
||||
<div class="analysis-chart-card-content" style="padding-top: 16px">
|
||||
<a-space size="middle">
|
||||
<span class="analysis-trend-text">
|
||||
周同比12% <caret-up-outlined class="ele-text-danger" />
|
||||
</span>
|
||||
<span class="analysis-trend-text">
|
||||
日同比11% <caret-down-outlined class="ele-text-success" />
|
||||
</span>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div>日销售额 ¥12,423</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">访问量</div>
|
||||
<ele-tag color="red">日</ele-tag>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">8,846</h1>
|
||||
<v-chart
|
||||
ref="visitChartRef"
|
||||
:option="visitChartOption"
|
||||
style="height: 40px"
|
||||
/>
|
||||
<a-divider />
|
||||
<div>日访问量 1,234</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">支付笔数</div>
|
||||
<ele-tag color="blue">月</ele-tag>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">6,560</h1>
|
||||
<v-chart
|
||||
ref="payNumChartRef"
|
||||
:option="payNumChartOption"
|
||||
style="height: 40px"
|
||||
/>
|
||||
<a-divider />
|
||||
<div>转化率 60%</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">活动运营效果</div>
|
||||
<ele-tag color="green">周</ele-tag>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">78%</h1>
|
||||
<div class="analysis-chart-card-content" style="padding-top: 16px">
|
||||
<a-progress
|
||||
:percent="78"
|
||||
:show-info="false"
|
||||
stroke-color="#13c2c2"
|
||||
status="active"
|
||||
/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<a-space size="middle">
|
||||
<span class="analysis-trend-text">
|
||||
周同比12% <caret-up-outlined class="ele-text-danger" />
|
||||
</span>
|
||||
<span class="analysis-trend-text">
|
||||
日同比11% <caret-down-outlined class="ele-text-success" />
|
||||
</span>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import {
|
||||
QuestionCircleOutlined,
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart, BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getPayNumList } from '@/api/dashboard/analysis';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]);
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
//
|
||||
const visitChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
const payNumChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([visitChartRef, payNumChartRef]);
|
||||
|
||||
// 访问量折线图配置
|
||||
const visitChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
// 支付笔数柱状图配置
|
||||
const payNumChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取支付笔数数据 */
|
||||
const getPayNumData = () => {
|
||||
getPayNumList()
|
||||
.then((data) => {
|
||||
Object.assign(visitChartOption, {
|
||||
color: '#975fe5',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter:
|
||||
'<i class="ele-chart-dot" style="background: #975fe5;"></i>{b0}: {c0}'
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.map((d) => d.date)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
data: data.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Object.assign(payNumChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter:
|
||||
'<i class="ele-chart-dot" style="background: #5b8ff9;"></i>{b0}: {c0}'
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'category',
|
||||
data: data.map((d) => d.date)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: data.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getPayNumData();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.analysis-chart-card {
|
||||
:deep(.ant-card-body) {
|
||||
padding: 16px 22px 12px 22px;
|
||||
}
|
||||
|
||||
:deep(.ant-divider) {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-chart-card-num {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.analysis-chart-card-content {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.analysis-trend-text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px' }">
|
||||
<template #extra
|
||||
><a @click="openUrl('/oa/task')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<div class="app-box">
|
||||
<div class="app-info">
|
||||
<a
|
||||
class="ele-text-secondary"
|
||||
@click="openNew('/oa/task/detail/' + item.taskId)"
|
||||
>
|
||||
<a-typography-paragraph
|
||||
ellipsis
|
||||
:content="`【${item.taskType}】${item.name}`"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<a class="ele-text-placeholder">
|
||||
<a-tag v-if="item.progress === TOBEARRANGED" color="red"
|
||||
>待安排</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === PENDING" color="orange"
|
||||
>待处理</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === PROCESSING" color="purple"
|
||||
>处理中</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === TOBECONFIRMED" color="cyan"
|
||||
>待评价</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === COMPLETED" color="green"
|
||||
>已完成</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === CLOSED">已关闭</a-tag>
|
||||
<div class="ele-text-danger" v-if="item.overdueDays">
|
||||
已逾期{{ item.overdueDays }}天
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { Task } from '@/api/oa/task/model';
|
||||
import { pageTask } from '@/api/oa/task';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import {
|
||||
CLOSED,
|
||||
COMPLETED,
|
||||
PENDING,
|
||||
PROCESSING,
|
||||
TOBEARRANGED,
|
||||
TOBECONFIRMED
|
||||
} from '@/api/oa/task/model/progress';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const list = ref<Task[]>([]);
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { title } = props;
|
||||
const userStore = useUserStore();
|
||||
const where = {
|
||||
userId: undefined,
|
||||
commander: userStore.info?.userId,
|
||||
limit: 6,
|
||||
status: 0
|
||||
};
|
||||
// 加载列表
|
||||
pageTask(where).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 5px;
|
||||
flex-direction: column;
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,84 +0,0 @@
|
||||
<!-- 小组成员 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px 0px' }">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div
|
||||
v-for="(item, index) in userList"
|
||||
:key="index"
|
||||
class="ele-cell user-list-item"
|
||||
>
|
||||
<div style="flex-shrink: 0">
|
||||
<a-avatar :size="46" :src="item.avatar" />
|
||||
</div>
|
||||
<div class="ele-cell-content">
|
||||
<span class="ele-cell-title ele-elip">{{ item.nickname }}</span>
|
||||
<div class="ele-cell-desc ele-elip">{{ item.phone }}</div>
|
||||
</div>
|
||||
<div style="flex-shrink: 0">
|
||||
<a-tag :color="['green', 'red'][item.status]">
|
||||
{{ ['在线', '离线'][item.status] }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
// 小组成员数据
|
||||
const userList = ref<User[]>([]);
|
||||
|
||||
/* 查询小组成员 */
|
||||
const queryUserList = () => {
|
||||
pageUsers({ parentId: 11, limit: 5 }).then((data: any) => {
|
||||
userList.value = data.list;
|
||||
});
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryUserList();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-list-item {
|
||||
padding: 12px 18px;
|
||||
|
||||
& + .user-list-item {
|
||||
border-top: 1px solid hsla(0, 0%, 60%, 0.15);
|
||||
}
|
||||
|
||||
.ele-cell-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ele-cell-desc {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
src/views/system/dashboard/components/visit-hour.vue
Normal file
101
src/views/system/dashboard/components/visit-hour.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
title="最近1小时访问情况"
|
||||
:body-style="{ padding: '16px 6px 0 0' }"
|
||||
>
|
||||
<v-chart
|
||||
ref="visitHourChartRef"
|
||||
:option="visitHourChartOption"
|
||||
style="height: 362px"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
} from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { getVisitHourList } from '@/api/dashboard/analysis';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
LineChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
]);
|
||||
|
||||
//
|
||||
const visitHourChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([visitHourChartRef]);
|
||||
|
||||
// 最近 1 小时访问情况折线图配置
|
||||
const visitHourChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取最近 1 小时访问情况数据 */
|
||||
const getVisitHourData = () => {
|
||||
getVisitHourList()
|
||||
.then((data) => {
|
||||
Object.assign(visitHourChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['浏览量', '访问量'],
|
||||
right: 20
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.map((d) => d.time)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '浏览量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
data: data.map((d) => d.views)
|
||||
},
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
data: data.map((d) => d.visits)
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getVisitHourData();
|
||||
</script>
|
||||
@@ -1,359 +1,25 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<SoundOutlined class="ele-text-danger" />
|
||||
<span class="gg-title ele-text-heading">公告</span>
|
||||
<a @click="openNew('/cms/article/374')" class="ele-text-heading"
|
||||
>系统内测中,如何获取体验账号?</a
|
||||
>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a @click="openNew('/cms/category/92')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined
|
||||
/></a>
|
||||
</template>
|
||||
</a-card>
|
||||
<!-- <profile-card />-->
|
||||
<LinkCard />
|
||||
<a-row :gutter="16" ref="wrapRef">
|
||||
<a-col :md="9">
|
||||
<App title="我的项目" />
|
||||
</a-col>
|
||||
<a-col :md="9">
|
||||
<Task title="我的工单" :categoryId="92" />
|
||||
</a-col>
|
||||
<a-col :md="6">
|
||||
<Article title="通知公告" :categoryId="92" />
|
||||
</a-col>
|
||||
<a-col :md="6">
|
||||
<Article title="公司动态" :categoryId="37" />
|
||||
</a-col>
|
||||
<a-col :md="6">
|
||||
<Article title="经验分享" :categoryId="93" />
|
||||
</a-col>
|
||||
<a-col :md="6">
|
||||
<Article title="API文档" :categoryId="90" />
|
||||
</a-col>
|
||||
<a-col :md="6">
|
||||
<Link linkType="实用工具" />
|
||||
</a-col>
|
||||
<!-- <a-col :md="12">-->
|
||||
<!-- <a-card title="商品管理" :bordered="false" />-->
|
||||
<!-- </a-col>-->
|
||||
</a-row>
|
||||
<!-- <a-row :gutter="16" ref="wrapRef">-->
|
||||
<!-- <a-col-->
|
||||
<!-- v-for="(item, index) in data"-->
|
||||
<!-- :key="item.name"-->
|
||||
<!-- :lg="item.lg"-->
|
||||
<!-- :md="item.md"-->
|
||||
<!-- :sm="item.sm"-->
|
||||
<!-- :xs="item.xs"-->
|
||||
<!-- >-->
|
||||
<!-- <component-->
|
||||
<!-- :is="item.name"-->
|
||||
<!-- :title="item.title"-->
|
||||
<!-- @remove="onRemove(index)"-->
|
||||
<!-- @edit="onEdit(index)"-->
|
||||
<!-- />-->
|
||||
<!-- </a-col>-->
|
||||
<!-- </a-row>-->
|
||||
<a-card :bordered="false" :body-style="{ padding: 0 }">
|
||||
<div class="ele-cell" style="line-height: 42px">
|
||||
<div
|
||||
class="ele-cell-content ele-text-primary workplace-bottom-btn"
|
||||
@click="add"
|
||||
>
|
||||
<PlusCircleOutlined /> 添加视图
|
||||
</div>
|
||||
<a-divider type="vertical" />
|
||||
<div
|
||||
class="ele-cell-content ele-text-primary workplace-bottom-btn"
|
||||
@click="reset"
|
||||
>
|
||||
<UndoOutlined /> 重置布局
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<ele-modal
|
||||
:width="680"
|
||||
v-model:visible="visible"
|
||||
title="未添加的视图"
|
||||
:footer="null"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-for="item in notAddedData"
|
||||
:key="item.name"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
>
|
||||
<div
|
||||
class="workplace-card-item ele-border-split"
|
||||
@click="addView(item)"
|
||||
>
|
||||
<div class="workplace-card-header ele-border-split">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="workplace-card-body ele-text-placeholder">
|
||||
<plus-circle-outlined />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-empty v-if="!notAddedData.length" description="已添加所有视图" />
|
||||
</ele-modal>
|
||||
<statistics-card />
|
||||
<sale-card />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import SortableJs from 'sortablejs';
|
||||
import type { Row as ARow } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import Article from './components/article-list.vue';
|
||||
import Link from './components/link-list.vue';
|
||||
import App from './components/app-list.vue';
|
||||
import Task from './components/task-card.vue';
|
||||
import {
|
||||
PlusCircleOutlined,
|
||||
SoundOutlined,
|
||||
RightOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { openNew } from '@/utils/common';
|
||||
const CACHE_KEY = 'workplace-layout';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import StatisticsCard from './components/statistics-card.vue';
|
||||
import SaleCard from './components/sale-card.vue';
|
||||
import VisitHour from './components/visit-hour.vue';
|
||||
import HotSearch from './components/hot-search.vue';
|
||||
|
||||
interface ViewItem {
|
||||
name: string;
|
||||
title: string;
|
||||
lg: number;
|
||||
md: number;
|
||||
sm: number;
|
||||
xs: number;
|
||||
}
|
||||
|
||||
// 默认布局
|
||||
const DEFAULT: ViewItem[] = [
|
||||
{
|
||||
name: 'link',
|
||||
title: '网址导航',
|
||||
lg: 24,
|
||||
md: 24,
|
||||
sm: 24,
|
||||
xs: 24
|
||||
},
|
||||
{
|
||||
name: 'task-card',
|
||||
title: '我的工单',
|
||||
lg: 18,
|
||||
md: 24,
|
||||
sm: 24,
|
||||
xs: 24
|
||||
},
|
||||
// {
|
||||
// name: 'project-card',
|
||||
// title: '项目管理',
|
||||
// lg: 16,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
{
|
||||
name: 'user-list',
|
||||
title: '小组成员',
|
||||
lg: 6,
|
||||
md: 24,
|
||||
sm: 24,
|
||||
xs: 24
|
||||
}
|
||||
// {
|
||||
// name: 'activities-card',
|
||||
// title: '最新动态',
|
||||
// lg: 6,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'goal-card',
|
||||
// title: '本月目标',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'docs',
|
||||
// title: '知识库',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// }
|
||||
];
|
||||
|
||||
// 获取缓存的顺序
|
||||
const cache = (() => {
|
||||
const str = localStorage.getItem(CACHE_KEY);
|
||||
try {
|
||||
return str ? JSON.parse(str) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const data = ref<ViewItem[]>([...(cache ?? DEFAULT)]);
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
|
||||
|
||||
let sortableIns: SortableJs | null = null;
|
||||
|
||||
// 未添加的数据
|
||||
const notAddedData = computed(() => {
|
||||
return DEFAULT.filter((d) => !data.value.some((t) => t.name === d.name));
|
||||
});
|
||||
|
||||
/* 添加 */
|
||||
const add = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
/* 重置布局 */
|
||||
const reset = () => {
|
||||
data.value = [...DEFAULT];
|
||||
cacheData();
|
||||
message.success('已重置');
|
||||
};
|
||||
|
||||
/* 缓存布局 */
|
||||
const cacheData = () => {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
|
||||
};
|
||||
|
||||
/* 删除视图 */
|
||||
const onRemove = (index: number) => {
|
||||
data.value = data.value.filter((_d, i) => i !== index);
|
||||
cacheData();
|
||||
};
|
||||
|
||||
/* 编辑视图 */
|
||||
const onEdit = (index: number) => {
|
||||
data.value.map((d) => {
|
||||
if (d.name == 'user-list') {
|
||||
}
|
||||
});
|
||||
// message.info('点击了编辑');
|
||||
};
|
||||
|
||||
/* 添加视图 */
|
||||
const addView = (item) => {
|
||||
data.value.push(item);
|
||||
cacheData();
|
||||
message.success('已添加');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const isTouchDevice = 'ontouchstart' in document.documentElement;
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
sortableIns = new SortableJs(wrapRef.value?.$el, {
|
||||
handle: '.ant-card-head',
|
||||
animation: 300,
|
||||
onUpdate: ({ oldIndex, newIndex }) => {
|
||||
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
|
||||
const temp = [...data.value];
|
||||
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
|
||||
data.value = temp;
|
||||
cacheData();
|
||||
}
|
||||
},
|
||||
setData: () => {}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sortableIns) {
|
||||
sortableIns.destroy();
|
||||
}
|
||||
});
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import ActivitiesCard from './components/activities-card.vue';
|
||||
import TaskCard from './components/task-card.vue';
|
||||
import GoalCard from './components/goal-card.vue';
|
||||
import UserList from './components/count-user.vue';
|
||||
import Docs from './components/docs.vue';
|
||||
import LinkCard from './components/link-card.vue';
|
||||
import ProfileCard from './components/profile-card.vue';
|
||||
|
||||
export default {
|
||||
name: 'DashboardWorkplace',
|
||||
components: {
|
||||
LinkCard,
|
||||
UserList,
|
||||
ActivitiesCard,
|
||||
TaskCard,
|
||||
GoalCard,
|
||||
Docs,
|
||||
ProfileCard
|
||||
}
|
||||
name: 'DashboardAnalysis'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-body :deep(.ant-card-head) {
|
||||
cursor: move;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ele-body :deep(.ant-row > .ant-col.sortable-chosen > .ant-card) {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.workplace-bottom-btn {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.workplace-bottom-btn:hover {
|
||||
background: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
|
||||
/* 添加弹窗 */
|
||||
.workplace-card-item {
|
||||
margin-bottom: 15px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s, background-color 0.2s;
|
||||
}
|
||||
|
||||
.workplace-card-item:hover {
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
|
||||
background: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
|
||||
.workplace-card-item .workplace-card-header {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
padding: 8px;
|
||||
}
|
||||
.gg-title {
|
||||
padding: 0 5px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.workplace-card-body {
|
||||
font-size: 26px;
|
||||
padding: 24px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user