Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec9710643 | ||
|
|
6f35fde8c3 | ||
|
|
17e64c2c3f | ||
|
|
18115cb30a | ||
|
|
31461499ad | ||
|
|
2106a4a6ae | ||
|
|
dae0eeab69 | ||
|
|
ff1f5b4cab | ||
|
|
8b5bfa316a | ||
|
|
a5defe482e |
@@ -77,3 +77,11 @@ export const ApiAccountsDelete = (id: string) => {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取招聘筛选项(来自网站同步快照)
|
||||
export const ApiRecruitFilterOptions = (accountId: string) => {
|
||||
return request({
|
||||
url: `/api/filters/options?account_id=${accountId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,9 +5,45 @@ import request from '@/utils/request'
|
||||
* */
|
||||
|
||||
// 查询所有联系记录(含电话、微信号)
|
||||
export const ApiContacts = (page: number, page_size: number) => {
|
||||
export const ApiContacts = (data: any) => {
|
||||
let start_date = ''
|
||||
let end_date = ''
|
||||
if (data.times && data.times.length) {
|
||||
start_date = data.times[0]
|
||||
end_date = data.times[1]
|
||||
}
|
||||
return request({
|
||||
url: `/api/contacts?page=${page}&page_size=${page_size}`,
|
||||
url: `/api/contacts?page=${data.pageNum}&page_size=${data.pageSize}&search=${data.search}&start_date=${start_date}&end_date=${end_date}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出联系记录为 Excel
|
||||
export const ApiContactsExport = (data: any) => {
|
||||
let start_date = ''
|
||||
let end_date = ''
|
||||
if (data.times && data.times.length) {
|
||||
start_date = data.times[0]
|
||||
end_date = data.times[1]
|
||||
}
|
||||
return request({
|
||||
url: `/api/contacts/export?search=${data.search}&start_date=${start_date}&end_date=${end_date}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取总览统计数据
|
||||
export const ApiStats = (data: any) => {
|
||||
return request({
|
||||
url: `/api/stats?period=${data.period}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取总览统计数据
|
||||
export const ApiStatsDaily = (data: any) => {
|
||||
return request({
|
||||
url: `/api/stats/daily?days=${data.days}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
109
src/api/ReconciliationManagement/index.ts
Normal file
109
src/api/ReconciliationManagement/index.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/*
|
||||
* 复聊管理
|
||||
* */
|
||||
|
||||
// 获取复聊配置列表
|
||||
export const ApiFollowupConfigs = () => {
|
||||
return request({
|
||||
url: `/api/followup-configs`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取单个复聊配置
|
||||
export const ApiFollowupConfigsId = (id: string) => {
|
||||
return request({
|
||||
url: `/api/followup-configs/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 创建复聊配置
|
||||
export const ApiFollowupConfigsAdd = (data: any) => {
|
||||
const formData = new FormData()
|
||||
formData.append('name', data.name)
|
||||
formData.append('position', data.position)
|
||||
formData.append('followup_days', data.followup_days)
|
||||
formData.append('is_active', data.is_active)
|
||||
return request({
|
||||
url: `/api/followup-configs`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新复聊配置
|
||||
export const ApiFollowupConfigsEditor = (data: any) => {
|
||||
const formData = new FormData()
|
||||
if (data.followup_days) formData.append('followup_days', data.followup_days)
|
||||
formData.append('is_active', data.is_active)
|
||||
return request({
|
||||
url: `/api/followup-configs/${data.id}`,
|
||||
method: 'put',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除复聊配置
|
||||
export const ApiFollowupConfigsDelete = (id: string) => {
|
||||
return request({
|
||||
url: `/api/followup-configs/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取复聊话术列表
|
||||
export const ApiFollowupScripts = (data: any) => {
|
||||
return request({
|
||||
url: `/api/followup-scripts?config_id=${data.config_id || ''}&day_number=${data.day_number || ''}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 创建复聊话术
|
||||
export const ApiFollowupScriptsAdd = (data: any) => {
|
||||
const formData = new FormData()
|
||||
formData.append('config_id', data.config_id)
|
||||
formData.append('day_number', data.day_number)
|
||||
formData.append('order', data.order)
|
||||
formData.append('content', data.content)
|
||||
return request({
|
||||
url: `/api/followup-scripts`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新复聊话术
|
||||
export const ApiFollowupScriptsEditor = (data: any) => {
|
||||
const formData = new FormData()
|
||||
formData.append('content', data.content)
|
||||
formData.append('order', data.order)
|
||||
return request({
|
||||
url: `/api/followup-scripts/${data.id}`,
|
||||
method: 'put',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除复聊话术
|
||||
export const ApiFollowupScriptsDelete = (id: string) => {
|
||||
return request({
|
||||
url: `/api/followup-scripts/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
71
src/api/ScreeningManagement/index.ts
Normal file
71
src/api/ScreeningManagement/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/*
|
||||
* 筛选管理
|
||||
* */
|
||||
|
||||
// 获取筛选配置列表
|
||||
export const ApiFilters = () => {
|
||||
return request({
|
||||
url: `/api/filters`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取单个复聊配置
|
||||
export const ApiFiltersId = (id: string) => {
|
||||
return request({
|
||||
url: `/api/filters/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 创建筛选配置
|
||||
export const ApiFiltersAdd = (data: any) => {
|
||||
const formData = new FormData()
|
||||
formData.append('name', data.name)
|
||||
formData.append('position_keywords', data.position_keywords)
|
||||
formData.append('city', data.city)
|
||||
formData.append('salary_min', data.salary_min)
|
||||
formData.append('salary_max', data.salary_max)
|
||||
formData.append('experience', data.experience)
|
||||
formData.append('education', data.education)
|
||||
formData.append('is_active', data.is_active)
|
||||
return request({
|
||||
url: `/api/filters`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新筛选配置
|
||||
export const ApiFiltersEditor = (data: any) => {
|
||||
const formData = new FormData()
|
||||
if (data.name) formData.append('name', data.name)
|
||||
if (data.position_keywords) formData.append('position_keywords', data.position_keywords)
|
||||
if (data.city) formData.append('city', data.city)
|
||||
if (data.salary_min) formData.append('salary_min', data.salary_min)
|
||||
if (data.salary_max) formData.append('salary_max', data.salary_max)
|
||||
if (data.experience) formData.append('experience', data.experience)
|
||||
if (data.education) formData.append('education', data.education)
|
||||
formData.append('is_active', data.is_active)
|
||||
return request({
|
||||
url: `/api/filters/${data.id}`,
|
||||
method: 'put',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除筛选配置
|
||||
export const ApiFiltersDelete = (id: string) => {
|
||||
return request({
|
||||
url: `/api/filters/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -27,7 +27,12 @@ export const ApiTasksAdd = (data: any) => {
|
||||
// formData.append('worker_id', data.worker_id)
|
||||
// formData.append('account_name', data.account_name)
|
||||
formData.append('id', data.boss_id)
|
||||
formData.append('params', data.params)
|
||||
const rawParams = data?.params
|
||||
if (typeof rawParams === 'string') {
|
||||
formData.append('params', rawParams)
|
||||
} else {
|
||||
formData.append('params', JSON.stringify(rawParams || {}))
|
||||
}
|
||||
return request({
|
||||
url: `/api/tasks`,
|
||||
method: 'post',
|
||||
|
||||
@@ -24,13 +24,30 @@ defineProps({
|
||||
width: 100%;
|
||||
height: $navbar-height;
|
||||
background-color: $sidebar-logo-background;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1); // 添加底部分隔线
|
||||
padding: 0 20px; // 增加内边距
|
||||
|
||||
.title {
|
||||
flex-shrink: 0;
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: $sidebar-logo-text-color;
|
||||
//margin-left: 10px;
|
||||
font-size: 22px; // 增大字体,匹配示例模板
|
||||
font-weight: 700; // 加粗
|
||||
color: #60a5fa; // 使用亮蓝色,匹配示例模板
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); // 添加文字阴影
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::after {
|
||||
content: '专业自动化系统'; // 添加副标题
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
opacity: 0.8;
|
||||
margin-top: 2px;
|
||||
color: $sidebar-logo-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -72,7 +72,8 @@ const menuThemeProps = computed(() => {
|
||||
theme.value === 'dark' || sidebarColorScheme.value === SidebarColor.CLASSIC_BLUE
|
||||
|
||||
return {
|
||||
backgroundColor: isDarkOrClassicBlue ? variables['menu-background'] : undefined,
|
||||
// 对于深蓝色侧边栏配色,设置背景为透明以显示伪元素的渐变效果
|
||||
backgroundColor: isDarkOrClassicBlue ? 'transparent' : undefined,
|
||||
textColor: isDarkOrClassicBlue ? variables['menu-text'] : undefined,
|
||||
activeTextColor: isDarkOrClassicBlue ? variables['menu-active-text'] : undefined
|
||||
}
|
||||
|
||||
@@ -184,9 +184,51 @@ html.dark {
|
||||
}
|
||||
}
|
||||
|
||||
// 深蓝色侧边栏配色(示例模板风格)- 优化对比度
|
||||
html.sidebar-color-blue {
|
||||
.el-menu-item:hover {
|
||||
background-color: $menu-hover;
|
||||
.el-menu {
|
||||
// 普通菜单项文字 - 提高对比度
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
color: #e2e8f0 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
.menu-icon,
|
||||
.el-icon {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 激活菜单项 - 更明显
|
||||
.el-menu-item.is-active {
|
||||
background-color: rgba(96, 165, 250, 0.2) !important;
|
||||
border-left: 4px solid #60a5fa !important;
|
||||
color: #60a5fa !important;
|
||||
|
||||
.menu-icon,
|
||||
.el-icon {
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 子菜单标题悬停样式
|
||||
.el-sub-menu__title:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
// 父菜单激活状态
|
||||
.el-sub-menu.has-active-child > .el-sub-menu__title {
|
||||
color: #60a5fa !important;
|
||||
background-color: rgba(96, 165, 250, 0.15) !important;
|
||||
|
||||
.menu-icon,
|
||||
.el-icon {
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ const { routes } = useLayoutMenu()
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
width: $sidebar-width;
|
||||
background-color: $menu-background;
|
||||
// 对于深蓝色配色方案,不设置背景色以显示渐变效果
|
||||
background-color: transparent;
|
||||
transition: width 0.28s;
|
||||
|
||||
&--collapsed {
|
||||
@@ -63,7 +64,8 @@ const { routes } = useLayoutMenu()
|
||||
.layout-sidebar {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background-color: var(--menu-background);
|
||||
// 使用 CSS 变量作为后备,但会被全局样式的伪元素覆盖
|
||||
background-color: transparent;
|
||||
transition: width 0.28s;
|
||||
|
||||
&.has-logo {
|
||||
|
||||
@@ -324,6 +324,63 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/data',
|
||||
name: 'Data',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '数据统计',
|
||||
icon: 'setting'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'dataStatistics',
|
||||
name: 'DataStatistics',
|
||||
component: () => import('@/views/DataStatistics/index.vue'),
|
||||
meta: {
|
||||
title: '数据统计'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/reconciliation',
|
||||
name: 'Reconciliation',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '复聊管理',
|
||||
icon: 'setting'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'reconciliationManagement',
|
||||
name: 'ReconciliationManagement',
|
||||
component: () => import('@/views/ReconciliationManagement/index.vue'),
|
||||
meta: {
|
||||
title: '复聊管理'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/screening',
|
||||
name: 'Screening',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '筛选管理',
|
||||
icon: 'setting'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'screeningManagement',
|
||||
name: 'ScreeningManagement',
|
||||
component: () => import('@/views/ScreeningManagement/index.vue'),
|
||||
meta: {
|
||||
title: '筛选管理'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
// 注册平台登记
|
||||
// {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const defaultSettings: AppSettings = {
|
||||
// 语言
|
||||
language: LanguageEnum.ZH_CN,
|
||||
// 主题颜色 - 修改此值时需同步修改 src/styles/variables.scss
|
||||
themeColor: '#4080FF',
|
||||
themeColor: '#1e40af',
|
||||
// 是否显示水印
|
||||
showWatermark: false,
|
||||
// 水印内容
|
||||
@@ -51,17 +51,18 @@ export const authConfig = {
|
||||
enableTokenRefresh: true
|
||||
} as const
|
||||
|
||||
// 主题色预设 - 经典配色方案
|
||||
// 主题色预设 - 现代配色方案(基于示例模板风格)
|
||||
// 注意:修改默认主题色时,需要同步修改 src/styles/variables.scss 中的 primary.base 值
|
||||
export const themeColorPresets = [
|
||||
'#4080FF', // Arco Design 蓝 - 现代感强
|
||||
'#1890FF', // Ant Design 蓝 - 经典商务
|
||||
'#409EFF', // Element Plus 蓝 - 清新自然
|
||||
'#FA8C16', // 活力橙 - 温暖友好
|
||||
'#722ED1', // 优雅紫 - 高端大气
|
||||
'#13C2C2', // 青色 - 科技感
|
||||
'#52C41A', // 成功绿 - 活力清新
|
||||
'#F5222D', // 警示红 - 醒目强烈
|
||||
'#2F54EB', // 深蓝 - 稳重专业
|
||||
'#EB2F96' // 品红 - 时尚个性
|
||||
'#1e40af', // Primary Blue - 示例模板主色
|
||||
'#3b82f6', // Tailwind Blue - 现代科技感
|
||||
'#2563eb', // Deep Blue - 深邃专业
|
||||
'#0ea5e9', // Sky Blue - 清新明亮
|
||||
'#6366f1', // Indigo - 优雅紫色
|
||||
'#14b8a6', // Teal - 青绿高级感
|
||||
'#f97316', // Orange - 活力温暖
|
||||
'#8b5cf6', // Violet - 时尚创意
|
||||
'#ec4899', // Pink - 年轻潮流
|
||||
'#10b981', // Emerald - 自然清新
|
||||
'#06b6d4' // Cyan - 科技未来感
|
||||
]
|
||||
|
||||
@@ -1,45 +1,407 @@
|
||||
$border: 1px solid var(--el-border-color-light);
|
||||
|
||||
// 全局变量
|
||||
$border-radius-base: 12px; // 增大圆角,匹配示例模板
|
||||
$border-radius-small: 8px;
|
||||
$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
$shadow-base: 0 4px 6px rgba(0, 0, 0, 0.05); // 调整阴影
|
||||
$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
$transition-base: all 0.3s ease; // 调整过渡时间
|
||||
|
||||
/* el-dialog */
|
||||
.el-dialog {
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: $shadow-lg;
|
||||
overflow: hidden;
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 15px 20px;
|
||||
padding: 20px 24px;
|
||||
margin: 0;
|
||||
border-bottom: $border;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #fff 100%);
|
||||
|
||||
.el-dialog__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 15px;
|
||||
padding: 16px 24px;
|
||||
border-top: $border;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
}
|
||||
|
||||
/** el-drawer */
|
||||
.el-drawer {
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: $shadow-lg;
|
||||
|
||||
.el-drawer__header {
|
||||
padding: 15px 20px;
|
||||
padding: 20px 24px;
|
||||
margin: 0;
|
||||
color: inherit;
|
||||
border-bottom: $border;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #fff 100%);
|
||||
|
||||
> :first-child {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.el-drawer__footer {
|
||||
padding: 15px;
|
||||
padding: 16px 24px;
|
||||
border-top: $border;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
}
|
||||
|
||||
// 抽屉和对话框底部按钮区域
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
|
||||
.el-button {
|
||||
min-width: 88px;
|
||||
transition: $transition-base;
|
||||
}
|
||||
}
|
||||
|
||||
// el-card 样式优化
|
||||
.el-card {
|
||||
border-radius: $border-radius-base;
|
||||
border: none;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: $transition-base;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-base;
|
||||
}
|
||||
|
||||
.el-card__header {
|
||||
padding: 18px 20px;
|
||||
border-bottom: $border;
|
||||
background-color: #f9fafb;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// el-table 样式优化
|
||||
.el-table {
|
||||
border-radius: $border-radius-small;
|
||||
overflow: hidden;
|
||||
|
||||
th.el-table__cell {
|
||||
background-color: #f3f4f6;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
border-bottom: $border;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
td.el-table__cell {
|
||||
border-bottom: $border;
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__empty-block {
|
||||
padding: 60px 0;
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
|
||||
// el-button 样式优化
|
||||
.el-button {
|
||||
border-radius: $border-radius-small;
|
||||
font-weight: 500;
|
||||
transition: $transition-base;
|
||||
|
||||
&--primary {
|
||||
background: linear-gradient(135deg, #1e40af, #1e3a8a);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(30, 64, 175, 0.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&--success {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-input 样式优化
|
||||
.el-input {
|
||||
.el-input__wrapper {
|
||||
border-radius: $border-radius-small;
|
||||
transition: $transition-base;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 2px rgba(30, 64, 175, 0.1);
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
box-shadow: 0 0 0 2px rgba(30, 64, 175, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-select 样式优化
|
||||
.el-select {
|
||||
.el-input__wrapper {
|
||||
border-radius: $border-radius-small;
|
||||
}
|
||||
}
|
||||
|
||||
// el-pagination 样式优化
|
||||
.el-pagination {
|
||||
padding: 20px 0;
|
||||
|
||||
.el-pager li {
|
||||
border-radius: $border-radius-small;
|
||||
font-weight: 500;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: #1e40af;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pagination__jump {
|
||||
.el-input__wrapper {
|
||||
border-radius: $border-radius-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-switch 样式优化
|
||||
.el-switch {
|
||||
&.is-checked {
|
||||
.el-switch__core {
|
||||
background-color: #10b981;
|
||||
}
|
||||
}
|
||||
|
||||
.el-switch__core {
|
||||
border-radius: 10px;
|
||||
transition: $transition-base;
|
||||
}
|
||||
}
|
||||
|
||||
// el-message 样式优化
|
||||
.el-message {
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: $shadow-lg;
|
||||
padding: 16px 20px;
|
||||
font-weight: 500;
|
||||
|
||||
&--success {
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #fff 100%);
|
||||
border: 1px solid #d1fae5;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background: linear-gradient(135deg, #fef2f2 0%, #fff 100%);
|
||||
border: 1px solid #fee2e2;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: linear-gradient(135deg, #fffbeb 0%, #fff 100%);
|
||||
border: 1px solid #fef3c7;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background: linear-gradient(135deg, #f3f4f6 0%, #fff 100%);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
// el-tag 样式优化
|
||||
.el-tag {
|
||||
border-radius: $border-radius-small;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
// el-form 样式优化
|
||||
.el-form {
|
||||
.el-form-item__label {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
.el-input,
|
||||
.el-select,
|
||||
.el-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-dropdown 样式优化
|
||||
.el-dropdown-menu {
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: $shadow-base;
|
||||
padding: 8px 0;
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
padding: 10px 16px;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-progress 样式优化
|
||||
.el-progress {
|
||||
.el-progress__text {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&--line {
|
||||
.el-progress-bar {
|
||||
border-radius: 10px;
|
||||
|
||||
.el-progress-bar__outer {
|
||||
border-radius: 10px;
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.el-progress-bar__inner {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(90deg, #1e40af, #3b82f6); // 使用渐变色
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-radio & checkbox 样式优化
|
||||
.el-radio,
|
||||
.el-checkbox {
|
||||
.el-radio__input.is-checked,
|
||||
.el-checkbox__input.is-checked {
|
||||
.el-radio__inner,
|
||||
.el-checkbox__inner {
|
||||
background-color: #1e40af;
|
||||
border-color: #1e40af;
|
||||
}
|
||||
}
|
||||
|
||||
.el-radio__input.is-checked + .el-radio__label,
|
||||
.el-checkbox__input.is-checked + .el-checkbox__label {
|
||||
color: #1e40af;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// el-alert 样式优化
|
||||
.el-alert {
|
||||
border-radius: $border-radius-base;
|
||||
border: none;
|
||||
padding: 16px 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// el-collapse 样式优化
|
||||
.el-collapse {
|
||||
border: none;
|
||||
|
||||
.el-collapse-item__header {
|
||||
border: none;
|
||||
margin-bottom: 8px;
|
||||
border-radius: $border-radius-base;
|
||||
padding: 14px 18px;
|
||||
background-color: #f9fafb;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
}
|
||||
|
||||
.el-collapse-item__wrap {
|
||||
border: none;
|
||||
margin-bottom: 8px;
|
||||
border-radius: $border-radius-base;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
@use "./reset";
|
||||
@use "./element-plus";
|
||||
@use "./template-style"; // 示例模板风格
|
||||
// Vxe Table
|
||||
@use "./vxe-table";
|
||||
@import url("./vxe-table.css");
|
||||
|
||||
// 全局变量
|
||||
$border-radius-base: 12px; // 匹配示例模板的大圆角
|
||||
$border-radius-small: 8px;
|
||||
$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
$shadow-base: 0 4px 6px rgba(0, 0, 0, 0.05); // 柔和阴影
|
||||
$transition-base: all 0.3s ease;
|
||||
|
||||
.app-container {
|
||||
padding: 15px;
|
||||
padding: 25px; // 增加内边距,更加疏朗
|
||||
}
|
||||
|
||||
// 进度条颜色
|
||||
#nprogress .bar {
|
||||
background-color: var(--el-color-primary);
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
// 混合布局左侧菜单的hover样式
|
||||
// 混合布局左侧菜单的 hover 样式
|
||||
.layout-mix .layout__sidebar--left .el-menu {
|
||||
.el-menu-item {
|
||||
&:hover {
|
||||
@@ -30,24 +39,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 深色主题或深蓝色侧边栏配色下的左侧菜单hover样式
|
||||
// 深色主题或深蓝色侧边栏配色下的左侧菜单 hover 样式
|
||||
html.dark .layout-mix .layout__sidebar--left .el-menu,
|
||||
html.sidebar-color-blue .layout-mix .layout__sidebar--left .el-menu {
|
||||
.el-menu-item {
|
||||
&:hover {
|
||||
// 深色背景:使用CSS变量
|
||||
// 深色背景:使用 CSS 变量
|
||||
background-color: var(--menu-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
// 深色背景:使用CSS变量
|
||||
// 深色背景:使用 CSS 变量
|
||||
background-color: var(--menu-hover) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深蓝色侧边栏渐变背景(示例模板风格)- 修复空白问题
|
||||
html.sidebar-color-blue {
|
||||
// 侧边栏容器应用渐变背景
|
||||
.layout__sidebar,
|
||||
.layout-sidebar {
|
||||
background: linear-gradient(180deg, #1e293b, #0f172a) !important;
|
||||
}
|
||||
|
||||
// 菜单容器设置为透明
|
||||
.el-menu.el-menu--vertical {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 窄屏时隐藏菜单文字,只显示图标
|
||||
.hideSidebar {
|
||||
// Top布局和Mix布局的水平菜单
|
||||
@@ -76,46 +99,95 @@ html.sidebar-color-blue .layout-mix .layout__sidebar--left .el-menu {
|
||||
|
||||
// 全局搜索区域样式
|
||||
.search-container {
|
||||
padding: 18px 16px 0;
|
||||
margin-bottom: 16px;
|
||||
padding: 25px 20px; // 增加内边距
|
||||
margin-bottom: 25px;
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 4px;
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: $shadow-base; // 使用更明显的阴影
|
||||
|
||||
.search-buttons {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格区域样式
|
||||
.data-table {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 25px;
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
border-radius: $border-radius-base;
|
||||
padding: 25px;
|
||||
box-shadow: $shadow-base;
|
||||
|
||||
// 表格工具栏区域
|
||||
&__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&--actions,
|
||||
&--tools {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: 12px; // 增大按钮间距
|
||||
}
|
||||
}
|
||||
|
||||
// 表格内容区域
|
||||
&__content {
|
||||
margin: 8px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 分页区域
|
||||
.el-pagination {
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 菜单样式优化 - 提高对比度和可见性
|
||||
html.sidebar-color-blue {
|
||||
.el-menu {
|
||||
// 普通菜单项文字 - 提高对比度
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
color: #e2e8f0 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
.menu-icon,
|
||||
.el-icon {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 激活菜单项 - 更明显
|
||||
.el-menu-item.is-active {
|
||||
background-color: rgba(96, 165, 250, 0.2) !important;
|
||||
border-left: 4px solid #60a5fa !important;
|
||||
color: #60a5fa !important;
|
||||
|
||||
.menu-icon,
|
||||
.el-icon {
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 父菜单激活状态
|
||||
.el-sub-menu.has-active-child > .el-sub-menu__title {
|
||||
color: #60a5fa !important;
|
||||
background-color: rgba(96, 165, 250, 0.15) !important;
|
||||
|
||||
.menu-icon,
|
||||
.el-icon {
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
300
src/styles/template-style.scss
Normal file
300
src/styles/template-style.scss
Normal file
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* 示例模板风格卡片样式
|
||||
* 基于页面模板的现代简约设计风格
|
||||
*/
|
||||
|
||||
// 全局变量
|
||||
$card-border-radius: 12px;
|
||||
$card-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
$card-hover-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
$transition-base: all 0.3s ease;
|
||||
|
||||
// 通用卡片容器
|
||||
.page-card {
|
||||
background: #ffffff;
|
||||
border-radius: $card-border-radius;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: $card-shadow;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $card-hover-shadow;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
|
||||
h3 {
|
||||
color: #1e40af; // 主题色
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统计卡片 - 仿示例模板的 stats-grid
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.stat-card {
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: $card-border-radius;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: $card-hover-shadow;
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.stat-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
|
||||
&.icon-blue {
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
}
|
||||
|
||||
&.icon-green {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
}
|
||||
|
||||
&.icon-orange {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
}
|
||||
|
||||
&.icon-purple {
|
||||
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮样式 - 仿示例模板的渐变效果
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: $transition-base;
|
||||
font-size: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
|
||||
&-primary {
|
||||
background: linear-gradient(135deg, #1e40af, #1e3a8a);
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(30, 64, 175, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&-success {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&-danger {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&-warning {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&-secondary {
|
||||
background: #f8fafc;
|
||||
color: #1e293b;
|
||||
border: 1px solid #e2e8f0;
|
||||
|
||||
&:hover {
|
||||
background: #e2e8f0;
|
||||
}
|
||||
}
|
||||
|
||||
&-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格样式 - 仿示例模板
|
||||
.data-table-modern {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
|
||||
th {
|
||||
background: #f8fafc;
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgba(30, 64, 175, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 进度条样式
|
||||
.progress-bar-modern {
|
||||
height: 8px;
|
||||
background: #e2e8f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #1e40af, #3b82f6);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// 状态标签
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
|
||||
&.status-running {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
&.status-paused {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
&.status-stopped {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
}
|
||||
|
||||
// 状态指示点
|
||||
.status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 10px #10b981;
|
||||
}
|
||||
|
||||
&.status-offline {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
// 动画效果
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
// 批量操作栏
|
||||
.batch-controls {
|
||||
background: #ffffff;
|
||||
border-radius: $card-border-radius;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #e2e8f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.batch-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
@@ -2,49 +2,54 @@
|
||||
$colors: (
|
||||
"primary": (
|
||||
// 默认主题色 - 修改此值时需同步修改 src/settings.ts 中的 themeColor
|
||||
"base": #4080ff,
|
||||
"base": #1e40af,
|
||||
),
|
||||
"success": (
|
||||
"base": #23c343,
|
||||
"base": #10b981,
|
||||
),
|
||||
"warning": (
|
||||
"base": #ff9a2e,
|
||||
"base": #f59e0b,
|
||||
),
|
||||
"danger": (
|
||||
"base": #f76560,
|
||||
"base": #ef4444,
|
||||
),
|
||||
"info": (
|
||||
"base": #a9aeb8,
|
||||
"base": #0ea5e9,
|
||||
),
|
||||
),
|
||||
|
||||
$bg-color: (
|
||||
"page": #f5f8fd,
|
||||
"page": #f8fafc,
|
||||
)
|
||||
);
|
||||
|
||||
/** 全局SCSS变量 */
|
||||
|
||||
:root {
|
||||
--menu-background: #fff; // 菜单背景色
|
||||
--menu-text: #212121; // 菜单文字颜色 浅色主题-白色侧边栏配色下仅占位,实际颜色由 el-menu-item 组件决定
|
||||
--menu-active-text: var(
|
||||
--el-menu-active-color
|
||||
); // 菜单激活文字颜色 浅色主题-白色侧边栏配色下仅占位,实际颜色由 el-menu-item 组件决定
|
||||
|
||||
--menu-hover: #e6f4ff; // 菜单悬停背景色 浅色主题-白色侧边栏配色下仅占位,实际颜色由 el-menu-item 组件决定
|
||||
--sidebar-logo-background: #f5f5f5; // 侧边栏 Logo 背景色
|
||||
--sidebar-logo-text-color: #333; // 侧边栏 Logo 文字颜色
|
||||
--menu-background: #fff;
|
||||
--menu-text: #1f2937;
|
||||
--menu-active-text: var(--el-menu-active-color);
|
||||
--menu-hover: #eff6ff;
|
||||
--sidebar-logo-background: #f8fafc;
|
||||
--sidebar-logo-text-color: #1e293b;
|
||||
// 卡片和边框样式
|
||||
--card-border-radius: 12px;
|
||||
--card-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
--border-color: #e2e8f0;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
}
|
||||
|
||||
/** 浅色主题-深蓝色侧边栏配色 */
|
||||
/** 浅色主题 - 深蓝色侧边栏配色(仿示例模板风格) */
|
||||
html.sidebar-color-blue {
|
||||
--menu-background: #304156; // 菜单背景色
|
||||
--menu-text: #bfcbd9; // 菜单文字颜色
|
||||
--menu-active-text: var(--el-menu-active-color); // 菜单激活文字颜色
|
||||
--menu-hover: #263445; // 菜单悬停背景色
|
||||
--sidebar-logo-background: #2d3748; // 侧边栏 Logo 背景色
|
||||
--sidebar-logo-text-color: #fff; // 侧边栏 Logo 文字颜色
|
||||
--menu-background: linear-gradient(180deg, #1e293b, #0f172a);
|
||||
--menu-text: #94a3b8;
|
||||
--menu-active-text: #60a5fa;
|
||||
--menu-hover: rgba(255, 255, 255, 0.08);
|
||||
--sidebar-logo-background: #0f172a;
|
||||
--sidebar-logo-text-color: #f1f5f9;
|
||||
// 激活项的左边框颜色
|
||||
--menu-active-border: #60a5fa;
|
||||
}
|
||||
|
||||
/** 暗黑主题 */
|
||||
@@ -87,7 +92,7 @@ $menu-hover: var(--menu-hover); // 菜单悬停背景色
|
||||
$sidebar-logo-background: var(--sidebar-logo-background); // 侧边栏 Logo 背景色
|
||||
$sidebar-logo-text-color: var(--sidebar-logo-text-color); // 侧边栏 Logo 文字颜色
|
||||
|
||||
$sidebar-width: 210px; // 侧边栏宽度
|
||||
$sidebar-width: 260px; // 侧边栏宽度
|
||||
$sidebar-width-collapsed: 54px; // 侧边栏收缩宽度
|
||||
$navbar-height: 50px; // 导航栏高度
|
||||
$navbar-height: 100px; // 导航栏高度
|
||||
$tags-view-height: 34px; // TagsView 高度
|
||||
|
||||
@@ -187,11 +187,19 @@ export function deepCloneByJSON(obj: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 ISO 8601 时间格式转换为年月日时分秒格式
|
||||
* 将 ISO 8601 时间格式转换为指定格式
|
||||
* @param isoString ISO 8601 格式的时间字符串,如 "2026-02-28T14:10:51.966269"
|
||||
* @returns 格式化后的时间字符串,如 "2026-02-28 14:10:51"
|
||||
* @param format 格式化模板,默认 'YYYY-MM-DD HH:mm:ss'
|
||||
* @returns 格式化后的时间字符串
|
||||
*
|
||||
* @example
|
||||
* formatISOToDateTime('2026-02-28T14:10:51.966269') // '2026-02-28 14:10:51'
|
||||
* formatISOToDateTime('2026-02-28T14:10:51.966269', 'YYYY-MM-DD') // '2026-02-28'
|
||||
* formatISOToDateTime('2026-02-28T14:10:51.966269', 'YYYY/MM/DD HH:mm') // '2026/02/28 14:10'
|
||||
* formatISOToDateTime('2026-02-28T14:10:51.966269', 'MM-DD HH:mm') // '02-28 14:10'
|
||||
* formatISOToDateTime('2026-02-28T14:10:51.966269', 'YYYY 年 MM 月 DD 日') // '2026 年 02 月 28 日'
|
||||
*/
|
||||
export function formatISOToDateTime(isoString: string): string {
|
||||
export function formatISOToDateTime(isoString: string, format = 'YYYY-MM-DD HH:mm:ss'): string {
|
||||
if (!isoString) return ''
|
||||
|
||||
try {
|
||||
@@ -210,7 +218,14 @@ export function formatISOToDateTime(isoString: string): string {
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
// 替换格式化模板中的占位符
|
||||
return format
|
||||
.replace('YYYY', String(year))
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds)
|
||||
} catch (error) {
|
||||
console.error('日期格式化错误:', error)
|
||||
return isoString
|
||||
|
||||
133
src/views/BoosAccountManagement/components/RecruitTaskForm.vue
Normal file
133
src/views/BoosAccountManagement/components/RecruitTaskForm.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="recruit-task-form">
|
||||
<el-alert
|
||||
v-if="syncedAt"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
:title="`筛选项同步时间:${syncedAt}`"
|
||||
/>
|
||||
<el-alert
|
||||
v-else
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
title="当前账号暂无筛选项,请先启动客户端同步筛选项"
|
||||
/>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="auto" label-position="top">
|
||||
<el-form-item label="筛选条件(可多选)" prop="selected_filters">
|
||||
<el-select
|
||||
v-model="formData.selected_filters"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:loading="optionsLoading"
|
||||
placeholder="请选择筛选条件"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in filterOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="招聘人数" prop="greet_target">
|
||||
<el-input-number
|
||||
v-model="formData.greet_target"
|
||||
:min="1"
|
||||
:max="500"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiRecruitFilterOptions } from '@/api/BoosAccountManagement'
|
||||
|
||||
const props = defineProps({
|
||||
accountId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const optionsLoading = ref(false)
|
||||
const filterOptions = ref<string[]>([])
|
||||
const syncedAt = ref('')
|
||||
|
||||
const formData = reactive({
|
||||
selected_filters: [] as string[],
|
||||
greet_target: 20
|
||||
})
|
||||
|
||||
const formRules = reactive<any>({
|
||||
selected_filters: [{ required: true, message: '请至少选择一个筛选条件', trigger: 'change' }],
|
||||
greet_target: [{ required: true, message: '请输入招聘人数', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const fetchOptions = () => {
|
||||
if (!props.accountId) return
|
||||
optionsLoading.value = true
|
||||
ApiRecruitFilterOptions(props.accountId)
|
||||
.then((res: any) => {
|
||||
filterOptions.value = (res.data?.flat_options || []) as string[]
|
||||
syncedAt.value = res.data?.synced_at || ''
|
||||
})
|
||||
.finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchOptions()
|
||||
})
|
||||
|
||||
const getForm = () => {
|
||||
return {
|
||||
selected_filters: [...formData.selected_filters],
|
||||
greet_target: Number(formData.greet_target) || 20
|
||||
}
|
||||
}
|
||||
|
||||
const submit = (): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (filterOptions.value.length === 0) {
|
||||
ElMessage.error('暂无可选筛选项,请先启动客户端同步筛选项')
|
||||
reject(false)
|
||||
return
|
||||
}
|
||||
formRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(true)
|
||||
} else {
|
||||
ElMessage.error('请完善招聘参数')
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.recruit-task-form {
|
||||
width: 100%;
|
||||
padding-right: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
>
|
||||
<el-table-column label="任务类型" prop="task_type">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.task_type === 'check_login' ? '检查登录' : '招聘' }}</span>
|
||||
<span>{{ taskTypeLabel(scope.row.task_type) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="指定电脑" prop="worker_id" />
|
||||
@@ -30,6 +30,10 @@
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === 'success'" type="success">成功</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'failed'" type="danger">失败</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'pending'" type="primary">已创建</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'dispatched'" type="primary">已派发</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'running'" type="primary">执行中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'cancelled'" type="info">已取消</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="created_at">
|
||||
@@ -54,7 +58,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 表格数据
|
||||
import { ApiTasks } from '@/api/TaskManagement'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
import { createTimer } from '@/utils/TimerManager'
|
||||
@@ -72,22 +75,33 @@ const queryParams = reactive<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
// 获取数据
|
||||
|
||||
const taskTypeLabel = (taskType: string) => {
|
||||
if (taskType === 'check_login') return '检查登录'
|
||||
if (taskType === 'boss_recruit') return '招聘'
|
||||
if (taskType === 'boss_reply') return '回复'
|
||||
return taskType || '--'
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
ApiTasks(props.bossId, queryParams.pageNum, queryParams.pageSize).then((res: any) => {
|
||||
roleList.value = res.data.results
|
||||
total.value = res.data.total
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
fetchData
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
startTimer(fetchData, 5000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
|
||||
@@ -139,6 +139,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="300">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="onApiTasksAdd(scope.row.id, 'boss_reply', {})"
|
||||
>
|
||||
回复
|
||||
</el-button>
|
||||
<!-- <el-button-->
|
||||
<!-- type="primary"-->
|
||||
<!-- size="small"-->
|
||||
@@ -160,14 +167,14 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="onApiTasksAdd(scope.row.id, 'check_login', '')"
|
||||
@click="onApiTasksAdd(scope.row.id, 'check_login', {})"
|
||||
>
|
||||
检查登录
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="onApiTasksAdd(scope.row.id, 'boss_recruit', '')"
|
||||
@click="openRecruitDialog(scope.row.id)"
|
||||
>
|
||||
招聘
|
||||
</el-button>
|
||||
@@ -217,6 +224,7 @@
|
||||
import { RolePageVO } from '@/api/system/role-api'
|
||||
import { functionDialogBox } from '@/utils/functionDialogBox'
|
||||
import BoosAccountForm from './components/BoosAccountForm.vue'
|
||||
import RecruitTaskForm from './components/RecruitTaskForm.vue'
|
||||
import { BusinessEditApplication } from '@/api/calibration/applicationForSealApproval'
|
||||
import { ApiAccounts, ApiAccountsAdd, ApiAccountsDelete } from '@/api/BoosAccountManagement'
|
||||
import ViewTheTaskListDetails from './components/ViewTheTaskListDetails.vue'
|
||||
@@ -370,7 +378,23 @@ const onUserDeleteDepartment = (id: string) => {
|
||||
// )
|
||||
// }
|
||||
|
||||
const onApiTasksAdd = (id: string, task_type: string, params: string) => {
|
||||
const openRecruitDialog = (id: string) => {
|
||||
functionDialogBox(
|
||||
RecruitTaskForm,
|
||||
{
|
||||
accountId: id
|
||||
},
|
||||
{
|
||||
title: '招聘参数',
|
||||
width: '620',
|
||||
ok(value: any) {
|
||||
onApiTasksAdd(id, 'boss_recruit', value || {})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const onApiTasksAdd = (id: string, task_type: string, params: any = {}) => {
|
||||
// functionDialogBox(
|
||||
// TaskForm,
|
||||
// {},
|
||||
@@ -387,7 +411,7 @@ const onApiTasksAdd = (id: string, task_type: string, params: string) => {
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
loading.value = true
|
||||
ApiTasksAdd({ params, task_type, boss_id: id })
|
||||
.then((res: any) => {
|
||||
ElMessage.success(res.msg)
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="任务类型" prop="task_type">
|
||||
<!-- <el-input v-model="formData.task_type" placeholder="请输入" />-->
|
||||
<el-select v-model="formData.task_type" placeholder="请选择">
|
||||
<el-option label="检查登录" value="check_login" />
|
||||
<el-option label="招聘" value="boss_recruit" />
|
||||
<el-option label="回复" value="boss_reply" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务参数" prop="params">
|
||||
@@ -33,7 +33,6 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = reactive<any>({
|
||||
task_type: '',
|
||||
worker_id: '',
|
||||
@@ -56,7 +55,6 @@ const getForm = () => {
|
||||
const setFormData = (data: any) => {
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const data1 = deepCloneByJSON(data)
|
||||
|
||||
Object.assign(formData, data1)
|
||||
}
|
||||
}
|
||||
@@ -73,6 +71,7 @@ const submit = (): Promise<boolean> => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
@@ -108,3 +107,4 @@ defineExpose({
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,47 +1,42 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<!-- <div class="search-container">-->
|
||||
<!-- <el-form ref="queryFormRef" :model="queryParams" :inline="true">-->
|
||||
<!-- <el-form-item prop="seal_type" label="用印用途">-->
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="queryParams.seal_type"-->
|
||||
<!-- placeholder="请输入"-->
|
||||
<!-- clearable-->
|
||||
<!-- @keyup.enter="handleQuery"-->
|
||||
<!-- />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item prop="CaseNumber" label="合同编号">-->
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="queryParams.CaseNumber"-->
|
||||
<!-- placeholder="请输入"-->
|
||||
<!-- clearable-->
|
||||
<!-- @keyup.enter="handleQuery"-->
|
||||
<!-- />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item label="创建时间" prop="times">-->
|
||||
<!-- <el-date-picker-->
|
||||
<!-- v-model="queryParams.times"-->
|
||||
<!-- type="daterange"-->
|
||||
<!-- value-format="YYYY-MM-DD"-->
|
||||
<!-- placeholder="请选择创建时间"-->
|
||||
<!-- range-separator="至"-->
|
||||
<!-- start-placeholder="开始时间"-->
|
||||
<!-- end-placeholder="结束时间"-->
|
||||
<!-- />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item class="search-buttons">-->
|
||||
<!-- <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>-->
|
||||
<!-- <el-button icon="refresh" @click="handleResetQuery">重置</el-button>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-form>-->
|
||||
<!-- </div>-->
|
||||
<div class="search-container">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item prop="search" label="姓名">
|
||||
<el-input
|
||||
v-model="queryParams.search"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="times">
|
||||
<el-date-picker
|
||||
v-model="queryParams.times"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择创建时间"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="search-buttons">
|
||||
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="refresh" @click="handleResetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-card shadow="hover" class="data-table">
|
||||
<!-- <div class="data-table__toolbar">-->
|
||||
<!-- <div class="data-table__toolbar--actions">-->
|
||||
<!-- <el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<div class="data-table__toolbar">
|
||||
<div class="data-table__toolbar--actions">
|
||||
<!-- <el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>-->
|
||||
</div>
|
||||
<div class="data-table__toolbar--actions">
|
||||
<el-button type="success" icon="Download" @click="onApiContactsExport()">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
@@ -80,6 +75,11 @@
|
||||
{{ formatISOToDateTime(scope.row?.contacted_at || '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="created_at">
|
||||
<template #default="scope">
|
||||
{{ formatISOToDateTime(scope.row?.created_at || '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column fixed="right" label="操作" width="140">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <el-button-->
|
||||
@@ -116,7 +116,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ApiContacts } from '@/api/ContactInformation'
|
||||
import { ApiContacts, ApiContactsExport } from '@/api/ContactInformation'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
|
||||
defineOptions({
|
||||
@@ -124,7 +124,7 @@ defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
// const queryFormRef = ref()
|
||||
const queryFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const ids = ref<number[]>([])
|
||||
@@ -134,8 +134,7 @@ const queryParams = reactive<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
times: [],
|
||||
seal_type: '',
|
||||
CaseNumber: ''
|
||||
search: ''
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
@@ -150,7 +149,7 @@ const roleList = ref<any[]>()
|
||||
// 获取数据
|
||||
function fetchData() {
|
||||
loading.value = true
|
||||
ApiContacts(queryParams.pageNum, queryParams.pageSize)
|
||||
ApiContacts(queryParams)
|
||||
.then((res: any) => {
|
||||
roleList.value = res.data.results
|
||||
total.value = res.data.total
|
||||
@@ -167,17 +166,31 @@ function handleQuery() {
|
||||
}
|
||||
|
||||
// // 重置查询
|
||||
// function handleResetQuery() {
|
||||
// if (queryFormRef.value) queryFormRef.value?.resetFields()
|
||||
// queryParams.pageNum = 1
|
||||
// fetchData()
|
||||
// }
|
||||
function handleResetQuery() {
|
||||
if (queryFormRef.value) queryFormRef.value?.resetFields()
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 行复选框选中
|
||||
function handleSelectionChange(selection: any) {
|
||||
ids.value = selection.map((item: any) => item.id)
|
||||
}
|
||||
|
||||
const onApiContactsExport = () => {
|
||||
ApiContactsExport(queryParams).then((res: any) => {
|
||||
if (res.data.download_url) {
|
||||
const link = document.createElement('a')
|
||||
link.href = res.data.download_url
|
||||
link.download = res.data.filename || '联系人信息.xlsx'
|
||||
link.target = '_blank'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// // 打开角色弹窗
|
||||
// function handleOpenDialog(data: any = null) {
|
||||
// dialog.visible = true
|
||||
|
||||
199
src/views/DataStatistics/components/DailyDataBreakdown.vue
Normal file
199
src/views/DataStatistics/components/DailyDataBreakdown.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">每日数据明细</div>
|
||||
<el-radio-group v-model="queryParams.days" size="small" @change="handleTimeChange">
|
||||
<el-radio-button v-for="item in timeOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-card shadow="hover" class="data-table">
|
||||
<div class="data-table__toolbar">
|
||||
<div class="data-table__toolbar--actions">
|
||||
<!-- <el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>-->
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="roleList"
|
||||
highlight-current-row
|
||||
border
|
||||
class="data-table__content"
|
||||
>
|
||||
<el-table-column label="联系人数" prop="contacts" />
|
||||
<el-table-column label="回复人数" prop="replied" />
|
||||
<el-table-column label="微信相关数量" prop="wechat"></el-table-column>
|
||||
<el-table-column label="回复率" prop="reply_rate" />
|
||||
<el-table-column label="日期时间" prop="date">
|
||||
<template #default="scope">
|
||||
{{ formatISOToDateTime(scope.row?.date || '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="fetchData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ApiStatsDaily } from '@/api/ContactInformation'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Role',
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
// const queryFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
|
||||
const queryParams = reactive<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
days: '1'
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const roleList = ref<any[]>()
|
||||
|
||||
const timeOptions = [
|
||||
{ label: '今日', value: '1' },
|
||||
{ label: '近 7 天', value: '7' },
|
||||
{ label: '近 30 天', value: '30' }
|
||||
]
|
||||
|
||||
// // 弹窗
|
||||
// const dialog = reactive({
|
||||
// title: '',
|
||||
// visible: false
|
||||
// })
|
||||
|
||||
// 获取数据
|
||||
function fetchData() {
|
||||
loading.value = true
|
||||
ApiStatsDaily(queryParams)
|
||||
.then((res: any) => {
|
||||
roleList.value = res.data
|
||||
// total.value = res.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 查询(重置页码后获取数据)
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// // 重置查询
|
||||
// function handleResetQuery() {
|
||||
// if (queryFormRef.value) queryFormRef.value?.resetFields()
|
||||
// queryParams.pageNum = 1
|
||||
// fetchData()
|
||||
// }
|
||||
|
||||
const handleTimeChange = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.headline-statistics {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 16px;
|
||||
height: 140px;
|
||||
}
|
||||
::v-deep(.statistics-box) {
|
||||
display: flex;
|
||||
//justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
gap: 20px;
|
||||
.statistics-box-img {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.img-bg1 {
|
||||
background: linear-gradient(135deg, #7f7fd5 0%, #9156e8 100%);
|
||||
}
|
||||
.img-bg2 {
|
||||
background: linear-gradient(135deg, #f199ee 0%, #ef6b8b 100%);
|
||||
}
|
||||
.img-bg3 {
|
||||
background: linear-gradient(135deg, #56bafd 0%, #16e9fd 100%);
|
||||
}
|
||||
.img-bg4 {
|
||||
background: linear-gradient(135deg, #51eb90 0%, #49f7d2 100%);
|
||||
}
|
||||
.statistics-box-text {
|
||||
//flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: #adb0b3;
|
||||
& > span:nth-child(1) {
|
||||
font-size: 14px;
|
||||
}
|
||||
& > span:nth-child(2) {
|
||||
font-size: 26px;
|
||||
color: #000;
|
||||
font-weight: 700;
|
||||
}
|
||||
& > span:nth-child(3) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.time-filter {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.el-button) {
|
||||
padding: 5px 12px;
|
||||
font-size: 13px;
|
||||
|
||||
&.el-button--primary {
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
224
src/views/DataStatistics/components/DataTrendChart.vue
Normal file
224
src/views/DataStatistics/components/DataTrendChart.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="chart-container">
|
||||
<div ref="chartRef" class="trend-chart"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { ApiStatsDaily } from '@/api/ContactInformation'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
|
||||
interface Props {
|
||||
height?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
height: '350px'
|
||||
})
|
||||
|
||||
const data = ref<any>({
|
||||
xAxis: [] as string[],
|
||||
series: [
|
||||
{
|
||||
name: '联系人数',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '已回复',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '微信交换',
|
||||
data: []
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null)
|
||||
let chartInstance: echarts.ECharts | null = null
|
||||
|
||||
// 颜色配置
|
||||
const seriesColors = ['#5B9BFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
chartInstance = echarts.init(chartRef.value)
|
||||
|
||||
const option: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.value.xAxis,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#D7DDE3'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#909399',
|
||||
margin: 20,
|
||||
rotate: 45,
|
||||
interval: 0,
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 1,
|
||||
interval: 0.2,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#E8E8E8',
|
||||
type: 'solid'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#909399',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
series: data.value.series.map((item: any, index: number) => ({
|
||||
name: item.name,
|
||||
type: 'bar',
|
||||
barGap: '10%',
|
||||
data: item.data,
|
||||
itemStyle: {
|
||||
color: seriesColors[index % seriesColors.length],
|
||||
borderRadius: [2, 2, 0, 0]
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
})),
|
||||
legend: {
|
||||
data: data.value.series.map((item: any) => item.name),
|
||||
bottom: 0,
|
||||
icon: 'rect',
|
||||
itemWidth: 14,
|
||||
itemHeight: 14,
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
color: '#606266',
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chartInstance.setOption(option)
|
||||
}
|
||||
|
||||
const onApiStatsDaily = (days: string) => {
|
||||
ApiStatsDaily({ days }).then((res: { data: any[] }) => {
|
||||
data.value = {
|
||||
xAxis: res.data?.map((item) => formatISOToDateTime(item.date, 'YYYY-MM-DD')),
|
||||
series: [
|
||||
{
|
||||
name: '联系人数',
|
||||
data: res.data?.map((item) => item.contacts)
|
||||
},
|
||||
{
|
||||
name: '已回复',
|
||||
data: res.data?.map((item) => item.replied)
|
||||
},
|
||||
{
|
||||
name: '微信交换',
|
||||
data: res.data?.map((item) => item.wechat)
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => data.value,
|
||||
(newData: any) => {
|
||||
if (chartInstance) {
|
||||
chartInstance.setOption({
|
||||
xAxis: { data: newData.xAxis },
|
||||
series: newData.series.map((item: any, index: number) => ({
|
||||
name: item.name,
|
||||
data: item.data,
|
||||
itemStyle: {
|
||||
color: seriesColors[index % seriesColors.length]
|
||||
}
|
||||
})),
|
||||
legend: {
|
||||
data: newData.series.map((item: any) => item.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
chartInstance = null
|
||||
}
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
resize: handleResize,
|
||||
onApiStatsDaily
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.trend-chart {
|
||||
width: 100%;
|
||||
height: v-bind(height);
|
||||
}
|
||||
</style>
|
||||
7
src/views/DataStatistics/components/TaskDetails.vue
Normal file
7
src/views/DataStatistics/components/TaskDetails.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div>TaskDetails</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
110
src/views/DataStatistics/components/TaskForm.vue
Normal file
110
src/views/DataStatistics/components/TaskForm.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="pre-registration-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="auto"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="任务类型" prop="task_type">
|
||||
<el-select v-model="formData.task_type" placeholder="请选择">
|
||||
<el-option label="检查登录" value="check_login" />
|
||||
<el-option label="招聘" value="boss_recruit" />
|
||||
<el-option label="回复" value="boss_reply" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务参数" prop="params">
|
||||
<el-input v-model="formData.params" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { deepCloneByJSON } from '@/utils/auxiliaryFunction'
|
||||
|
||||
const props = defineProps({
|
||||
newData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const formData = reactive<any>({
|
||||
task_type: '',
|
||||
worker_id: '',
|
||||
account_name: '',
|
||||
params: ''
|
||||
})
|
||||
const formRules = reactive<any>({
|
||||
task_type: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
params: [{ required: true, message: '请输入', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setFormData(props.newData)
|
||||
})
|
||||
|
||||
const getForm = () => {
|
||||
return formData
|
||||
}
|
||||
|
||||
const setFormData = (data: any) => {
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const data1 = deepCloneByJSON(data)
|
||||
Object.assign(formData, data1)
|
||||
}
|
||||
}
|
||||
|
||||
const submit = (): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
formRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(true)
|
||||
} else {
|
||||
ElMessage.error('请完善必填信息')
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre-registration-form {
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
197
src/views/DataStatistics/index.vue
Normal file
197
src/views/DataStatistics/index.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="headline-statistics">
|
||||
<el-card shadow="hover" body-class="statistics-box" style="flex: 1">
|
||||
<div class="statistics-box-img img-bg1">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="statistics-box-text">
|
||||
<span>总联系人数</span>
|
||||
<span>{{ apiStatsData.contacts?.total || 0 }}</span>
|
||||
<span>今日:{{ apiStatsData.contacts?.today || 0 }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover" body-class="statistics-box" style="flex: 1">
|
||||
<div class="statistics-box-img img-bg2">
|
||||
<el-icon><ChatDotRound /></el-icon>
|
||||
</div>
|
||||
<div class="statistics-box-text">
|
||||
<span>已回复人数</span>
|
||||
<span>{{ apiStatsData.contacts?.replied || 0 }}</span>
|
||||
<span>回复率:{{ apiStatsData.contacts?.reply_rate || 0 }}%</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover" body-class="statistics-box" style="flex: 1">
|
||||
<div class="statistics-box-img img-bg3">
|
||||
<el-icon><ChatLineRound /></el-icon>
|
||||
</div>
|
||||
<div class="statistics-box-text">
|
||||
<span>微信交换数</span>
|
||||
<span>{{ apiStatsData.wechat?.total || 0 }}</span>
|
||||
<span>成功率:{{ apiStatsData.wechat?.success_rate || 0 }}%</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover" body-class="statistics-box" style="flex: 1">
|
||||
<div class="statistics-box-img img-bg4">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
</div>
|
||||
<div class="statistics-box-text">
|
||||
<span>在线Worker</span>
|
||||
<span>{{ apiStatsData?.accounts?.logged_in || 0 }}</span>
|
||||
<span>总数:{{ apiStatsData?.accounts?.total || 0 }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-card shadow="hover">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">数据趋势</div>
|
||||
<el-radio-group v-model="currentTime" size="small" @change="handleTimeChange">
|
||||
<el-radio-button v-for="item in timeOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<DataTrendChart ref="dataTrendChartRef" />
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<DailyDataBreakdown></DailyDataBreakdown>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ApiStats } from '@/api/ContactInformation'
|
||||
import { Monitor, User, ChatDotRound, ChatLineRound } from '@element-plus/icons-vue'
|
||||
import DataTrendChart from './components/DataTrendChart.vue'
|
||||
import { ref } from 'vue'
|
||||
import DailyDataBreakdown from '@/views/DataStatistics/components/DailyDataBreakdown.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Role',
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
// const queryFormRef = ref()
|
||||
const dataTrendChartRef = ref()
|
||||
|
||||
// 数据
|
||||
const currentTime = ref('today')
|
||||
const timeOptions = [
|
||||
{ label: '今日', value: 'today' },
|
||||
{ label: '近 7 天', value: 'week' },
|
||||
{ label: '近 30 天', value: 'month' },
|
||||
{ label: '全部', value: 'all' }
|
||||
]
|
||||
const apiStatsData = ref<any>({})
|
||||
// // 弹窗
|
||||
// const dialog = reactive({
|
||||
// title: '',
|
||||
// visible: false
|
||||
// })
|
||||
|
||||
// 获取数据
|
||||
const onApiStats = () => {
|
||||
ApiStats({ period: currentTime.value }).then((res: any) => {
|
||||
apiStatsData.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
// // 重置查询
|
||||
// function handleResetQuery() {
|
||||
// if (queryFormRef.value) queryFormRef.value?.resetFields()
|
||||
// queryParams.pageNum = 1
|
||||
// fetchData()
|
||||
// }
|
||||
|
||||
const handleTimeChange = () => {
|
||||
onApiStats()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onApiStats()
|
||||
dataTrendChartRef.value?.onApiStatsDaily('7')
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.headline-statistics {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 16px;
|
||||
height: 140px;
|
||||
}
|
||||
::v-deep(.statistics-box) {
|
||||
display: flex;
|
||||
//justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
gap: 20px;
|
||||
.statistics-box-img {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.img-bg1 {
|
||||
background: linear-gradient(135deg, #7f7fd5 0%, #9156e8 100%);
|
||||
}
|
||||
.img-bg2 {
|
||||
background: linear-gradient(135deg, #f199ee 0%, #ef6b8b 100%);
|
||||
}
|
||||
.img-bg3 {
|
||||
background: linear-gradient(135deg, #56bafd 0%, #16e9fd 100%);
|
||||
}
|
||||
.img-bg4 {
|
||||
background: linear-gradient(135deg, #51eb90 0%, #49f7d2 100%);
|
||||
}
|
||||
.statistics-box-text {
|
||||
//flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: #adb0b3;
|
||||
& > span:nth-child(1) {
|
||||
font-size: 14px;
|
||||
}
|
||||
& > span:nth-child(2) {
|
||||
font-size: 26px;
|
||||
color: #000;
|
||||
font-weight: 700;
|
||||
}
|
||||
& > span:nth-child(3) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.time-filter {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.el-button) {
|
||||
padding: 5px 12px;
|
||||
font-size: 13px;
|
||||
|
||||
&.el-button--primary {
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="pre-registration-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="auto"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item v-if="!newData?.id" label="配置名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入" />
|
||||
<!-- <el-select v-model="formData.task_type" placeholder="请选择">-->
|
||||
<!-- <el-option label="检查登录" value="check_login" />-->
|
||||
<!-- <el-option label="招聘" value="boss_recruit" />-->
|
||||
<!-- </el-select>-->
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!newData?.id" label="岗位类型" prop="position">
|
||||
<el-input v-model="formData.position" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="复聊间隔天数" prop="followup_days">
|
||||
<el-input v-model="formData.followup_days" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="is_active">
|
||||
<el-switch v-model="formData.is_active" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { deepCloneByJSON } from '@/utils/auxiliaryFunction'
|
||||
|
||||
const props = defineProps({
|
||||
newData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = reactive<any>({
|
||||
name: '',
|
||||
position: '',
|
||||
followup_days: '',
|
||||
is_active: false
|
||||
})
|
||||
const formRules = reactive<any>({
|
||||
name: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
position: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
followup_days: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
is_active: [{ required: true, message: '请输入', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setFormData(props.newData)
|
||||
})
|
||||
|
||||
const getForm = () => {
|
||||
return formData
|
||||
}
|
||||
|
||||
const setFormData = (data: any) => {
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const data1 = deepCloneByJSON(data)
|
||||
|
||||
Object.assign(formData, data1)
|
||||
}
|
||||
}
|
||||
|
||||
const submit = (): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
formRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(true)
|
||||
} else {
|
||||
ElMessage.error('请完善必填信息')
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre-registration-form {
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<h3>复聊配置</h3>
|
||||
<el-card shadow="hover" class="data-table">
|
||||
<div class="data-table__toolbar">
|
||||
<div class="data-table__toolbar--actions">
|
||||
<el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="roleList"
|
||||
highlight-current-row
|
||||
row-key="id"
|
||||
border
|
||||
class="data-table__content"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<el-table-column type="expand">
|
||||
<template #default="scope">
|
||||
<RevisedConversationScript
|
||||
v-if="expandedRowKeys.includes(scope.row.id)"
|
||||
:config-id="scope.row.id"
|
||||
></RevisedConversationScript>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="配置名称" prop="name" />
|
||||
<el-table-column label="岗位类型" prop="position" />
|
||||
<el-table-column label="复聊间隔" prop="followup_days" />
|
||||
<el-table-column label="启用状态" prop="is_active">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.is_active" @change="onStatusModification(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="created_at">
|
||||
<template #default="scope">
|
||||
{{ formatISOToDateTime(scope.row?.created_at || '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="220">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleOpenDialog(scope.row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleExpandTask(scope.row.id)"
|
||||
>
|
||||
展开话术
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="onUserDeleteDepartment(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="fetchData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RolePageVO } from '@/api/system/role-api'
|
||||
import { functionDialogBox } from '@/utils/functionDialogBox'
|
||||
import ReconfigurationSettingsForm from './components/ReconfigurationSettingsForm.vue'
|
||||
import {
|
||||
ApiFollowupConfigs,
|
||||
ApiFollowupConfigsAdd,
|
||||
ApiFollowupConfigsDelete,
|
||||
ApiFollowupConfigsEditor
|
||||
} from '@/api/ReconciliationManagement'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
import RevisedConversationScript from '../RevisedConversationScript/index.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Role',
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
const dataTableRef = ref()
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const expandedRowKeys = ref<string[]>([])
|
||||
|
||||
const queryParams = reactive<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
times: [],
|
||||
seal_type: '',
|
||||
CaseNumber: ''
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const roleList = ref<RolePageVO[]>()
|
||||
|
||||
// 弹窗
|
||||
const dialog = reactive({
|
||||
title: '',
|
||||
visible: false
|
||||
})
|
||||
|
||||
// 获取数据
|
||||
function fetchData() {
|
||||
loading.value = true
|
||||
ApiFollowupConfigs()
|
||||
.then((res: any) => {
|
||||
roleList.value = res.data
|
||||
// total.value = res.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 查询(重置页码后获取数据)
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
function handleResetQuery() {
|
||||
if (queryFormRef.value) queryFormRef.value.resetFields()
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 打开角色弹窗
|
||||
function handleOpenDialog(data: any = null) {
|
||||
dialog.visible = true
|
||||
if (data) {
|
||||
functionDialogBox(
|
||||
ReconfigurationSettingsForm,
|
||||
{
|
||||
newData: data
|
||||
},
|
||||
{
|
||||
title: '编辑复聊配置',
|
||||
width: '900',
|
||||
ok(value: any) {
|
||||
handleSubmit({ id: data.id, ...value })
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
functionDialogBox(
|
||||
ReconfigurationSettingsForm,
|
||||
{},
|
||||
{
|
||||
title: '创建复聊配置',
|
||||
width: '900',
|
||||
ok(value: any) {
|
||||
handleSubmit(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交角色表单
|
||||
function handleSubmit(data: any) {
|
||||
loading.value = true
|
||||
const roleId = data.id
|
||||
if (roleId) {
|
||||
ApiFollowupConfigsEditor(data)
|
||||
.then(() => {
|
||||
ElMessage.success('修改成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
} else {
|
||||
ApiFollowupConfigsAdd(data)
|
||||
.then(() => {
|
||||
ElMessage.success('新增成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
}
|
||||
}
|
||||
|
||||
const onUserDeleteDepartment = (id: string) => {
|
||||
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(
|
||||
() => {
|
||||
loading.value = true
|
||||
ApiFollowupConfigsDelete(id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
},
|
||||
() => {
|
||||
ElMessage.info('已取消删除')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function handleExpandChange(row: any, expandedRows: any[]) {
|
||||
expandedRowKeys.value = expandedRows.map((item) => item.id)
|
||||
}
|
||||
|
||||
const handleExpandTask = (rowId: string) => {
|
||||
// 找到对应的行数据
|
||||
const targetRow = roleList.value?.find((item) => item.id === rowId)
|
||||
if (targetRow && dataTableRef.value) {
|
||||
dataTableRef.value.toggleRowExpansion(targetRow)
|
||||
}
|
||||
}
|
||||
|
||||
const onStatusModification = (data: any) => {
|
||||
ElMessageBox.confirm(
|
||||
data.is_active ? '确认启用已选中的数据项?' : '确认禁用已选中的数据项?',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
loading.value = true
|
||||
ApiFollowupConfigsEditor({ is_active: data.is_active, id: data.id })
|
||||
.then(() => {
|
||||
ElMessage.success('修改成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
})
|
||||
.catch(() => {
|
||||
handleResetQuery()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="pre-registration-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="auto"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="排序" prop="order">
|
||||
<el-input v-model="formData.order" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="话术内容" prop="content">
|
||||
<el-input v-model="formData.content" type="textarea" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { deepCloneByJSON } from '@/utils/auxiliaryFunction'
|
||||
|
||||
const props = defineProps({
|
||||
newData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = reactive<any>({
|
||||
order: '',
|
||||
content: ''
|
||||
})
|
||||
const formRules = reactive<any>({
|
||||
order: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
content: [{ required: true, message: '请输入', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setFormData(props.newData)
|
||||
})
|
||||
|
||||
const getForm = () => {
|
||||
return formData
|
||||
}
|
||||
|
||||
const setFormData = (data: any) => {
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const data1 = deepCloneByJSON(data)
|
||||
|
||||
Object.assign(formData, data1)
|
||||
}
|
||||
}
|
||||
|
||||
const submit = (): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
formRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(true)
|
||||
} else {
|
||||
ElMessage.error('请完善必填信息')
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre-registration-form {
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<h3>复聊话术</h3>
|
||||
<el-card shadow="hover" class="data-table">
|
||||
<div class="data-table__toolbar">
|
||||
<div class="data-table__toolbar--actions">
|
||||
<el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>
|
||||
</div>
|
||||
<div class="data-table__toolbar--actions">
|
||||
<el-select
|
||||
v-model="queryParams.day_number"
|
||||
placeholder="请选择"
|
||||
style="width: 100px"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<el-option v-for="item in 5" :key="item" :label="`第${item}天`" :value="item">
|
||||
{{ `第${item}天` }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="roleList"
|
||||
highlight-current-row
|
||||
border
|
||||
class="data-table__content"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column label="第几天" prop="day_number">
|
||||
<template #default="scope">第{{ scope.row?.day_number }}天</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="order" />
|
||||
<el-table-column label="话术内容" prop="content" />
|
||||
<el-table-column label="创建时间" prop="created_at">
|
||||
<template #default="scope">
|
||||
{{ formatISOToDateTime(scope.row?.created_at || '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="140">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleOpenDialog(scope.row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="onUserDeleteDepartment(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="fetchData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RolePageVO } from '@/api/system/role-api'
|
||||
import { functionDialogBox } from '@/utils/functionDialogBox'
|
||||
import RevisedConversationScriptForm from './components/RevisedConversationScriptForm.vue'
|
||||
import {
|
||||
ApiFollowupScripts,
|
||||
ApiFollowupScriptsAdd,
|
||||
ApiFollowupScriptsDelete,
|
||||
ApiFollowupScriptsEditor
|
||||
} from '@/api/ReconciliationManagement'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
|
||||
const props = defineProps({
|
||||
configId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
defineOptions({
|
||||
name: 'Role',
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const ids = ref<number[]>([])
|
||||
const total = ref(0)
|
||||
|
||||
const queryParams = reactive<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
day_number: 1
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const roleList = ref<RolePageVO[]>()
|
||||
|
||||
// 弹窗
|
||||
const dialog = reactive({
|
||||
title: '',
|
||||
visible: false
|
||||
})
|
||||
|
||||
// 获取数据
|
||||
function fetchData() {
|
||||
loading.value = true
|
||||
ApiFollowupScripts({
|
||||
config_id: props.configId,
|
||||
day_number: queryParams.day_number
|
||||
})
|
||||
.then((res: any) => {
|
||||
roleList.value = res.data
|
||||
// total.value = res.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 查询(重置页码后获取数据)
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
function handleResetQuery() {
|
||||
if (queryFormRef.value) queryFormRef.value.resetFields()
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 行复选框选中
|
||||
function handleSelectionChange(selection: any) {
|
||||
ids.value = selection.map((item: any) => item.id)
|
||||
}
|
||||
|
||||
// 打开角色弹窗
|
||||
function handleOpenDialog(data: any = null) {
|
||||
dialog.visible = true
|
||||
if (data) {
|
||||
functionDialogBox(
|
||||
RevisedConversationScriptForm,
|
||||
{
|
||||
newData: data
|
||||
},
|
||||
{
|
||||
title: '编辑复聊话术',
|
||||
width: '900',
|
||||
ok(value: any) {
|
||||
handleSubmit({ id: data.id, ...value })
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
functionDialogBox(
|
||||
RevisedConversationScriptForm,
|
||||
{},
|
||||
{
|
||||
title: '创建复聊话术',
|
||||
width: '900',
|
||||
ok(value: any) {
|
||||
handleSubmit({ ...value, config_id: props.configId, day_number: queryParams.day_number })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交角色表单
|
||||
function handleSubmit(data: any) {
|
||||
loading.value = true
|
||||
const roleId = data.id
|
||||
if (roleId) {
|
||||
ApiFollowupScriptsEditor(data)
|
||||
.then(() => {
|
||||
ElMessage.success('修改成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
} else {
|
||||
ApiFollowupScriptsAdd(data)
|
||||
.then(() => {
|
||||
ElMessage.success('新增成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
}
|
||||
}
|
||||
|
||||
const onUserDeleteDepartment = (id: string) => {
|
||||
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(
|
||||
() => {
|
||||
loading.value = true
|
||||
ApiFollowupScriptsDelete(id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
},
|
||||
() => {
|
||||
ElMessage.info('已取消删除')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
11
src/views/ReconciliationManagement/index.vue
Normal file
11
src/views/ReconciliationManagement/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<ReconfigurationSettings></ReconfigurationSettings>
|
||||
<!-- <RevisedConversationScript></RevisedConversationScript>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ReconfigurationSettings from './components/ReconfigurationSettings/index.vue'
|
||||
// import RevisedConversationScript from './components/RevisedConversationScript/index.vue'
|
||||
</script>
|
||||
137
src/views/ScreeningManagement/components/FilterSettingsForm.vue
Normal file
137
src/views/ScreeningManagement/components/FilterSettingsForm.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="pre-registration-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="auto"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="配置名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入" />
|
||||
<!-- <el-select v-model="formData.task_type" placeholder="请选择">-->
|
||||
<!-- <el-option label="检查登录" value="check_login" />-->
|
||||
<!-- <el-option label="招聘" value="boss_recruit" />-->
|
||||
<!-- </el-select>-->
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位关键词" prop="position_keywords">
|
||||
<el-input v-model="formData.position_keywords" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="城市" prop="city">
|
||||
<el-input v-model="formData.city" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最低薪资(k)" prop="salary_min">
|
||||
<el-input v-model="formData.salary_min" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最高薪资(k)" prop="salary_max">
|
||||
<el-input v-model="formData.salary_max" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作经验" prop="experience">
|
||||
<el-input v-model="formData.experience" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学历要求" prop="education">
|
||||
<el-input v-model="formData.education" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="is_active">
|
||||
<el-switch v-model="formData.is_active" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { deepCloneByJSON } from '@/utils/auxiliaryFunction'
|
||||
|
||||
const props = defineProps({
|
||||
newData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = reactive<any>({
|
||||
name: '',
|
||||
position_keywords: '',
|
||||
city: '',
|
||||
salary_min: '',
|
||||
salary_max: '',
|
||||
experience: '',
|
||||
education: '',
|
||||
is_active: false
|
||||
})
|
||||
const formRules = reactive<any>({
|
||||
name: [{ required: true, message: '请输入', trigger: 'blur' }]
|
||||
// position_keywords: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
// city: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
// salary_min: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
// experience: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
// education: [{ required: true, message: '请输入', trigger: 'blur' }],
|
||||
// is_active: [{ required: true, message: '请输入', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setFormData(props.newData)
|
||||
})
|
||||
|
||||
const getForm = () => {
|
||||
return formData
|
||||
}
|
||||
|
||||
const setFormData = (data: any) => {
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const data1 = deepCloneByJSON(data)
|
||||
|
||||
Object.assign(formData, data1)
|
||||
}
|
||||
}
|
||||
|
||||
const submit = (): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
formRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(true)
|
||||
} else {
|
||||
ElMessage.error('请完善必填信息')
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre-registration-form {
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
240
src/views/ScreeningManagement/index.vue
Normal file
240
src/views/ScreeningManagement/index.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card shadow="hover" class="data-table">
|
||||
<div class="data-table__toolbar">
|
||||
<div class="data-table__toolbar--actions">
|
||||
<el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="roleList"
|
||||
highlight-current-row
|
||||
row-key="id"
|
||||
border
|
||||
class="data-table__content"
|
||||
>
|
||||
<el-table-column label="配置名称" prop="name" />
|
||||
<el-table-column label="岗位关键词" prop="position_keywords" />
|
||||
<el-table-column label="城市" prop="city" />
|
||||
<el-table-column label="薪资范围(单位:k)" prop="salary_min">
|
||||
<template #default="scope">
|
||||
{{ scope.row.salary_min || 0 }} - {{ scope.row.salary_max || 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="复聊间隔" prop="salary_max" />-->
|
||||
<el-table-column label="工作经验" prop="experience" />
|
||||
<el-table-column label="学历要求" prop="education" />
|
||||
<el-table-column label="启用状态" prop="is_active">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.is_active" @change="onStatusModification(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="created_at">
|
||||
<template #default="scope">
|
||||
{{ formatISOToDateTime(scope.row?.created_at || '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="220">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleOpenDialog(scope.row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="onUserDeleteDepartment(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="fetchData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RolePageVO } from '@/api/system/role-api'
|
||||
import { functionDialogBox } from '@/utils/functionDialogBox'
|
||||
import FilterSettingsForm from './components/FilterSettingsForm.vue'
|
||||
import { formatISOToDateTime } from '@/utils/auxiliaryFunction'
|
||||
import {
|
||||
ApiFilters,
|
||||
ApiFiltersAdd,
|
||||
ApiFiltersDelete,
|
||||
ApiFiltersEditor
|
||||
} from '@/api/ScreeningManagement'
|
||||
|
||||
defineOptions({
|
||||
name: 'Role',
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
const dataTableRef = ref()
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
|
||||
const queryParams = reactive<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
times: [],
|
||||
seal_type: '',
|
||||
CaseNumber: ''
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const roleList = ref<RolePageVO[]>()
|
||||
|
||||
// 弹窗
|
||||
const dialog = reactive({
|
||||
title: '',
|
||||
visible: false
|
||||
})
|
||||
|
||||
// 获取数据
|
||||
function fetchData() {
|
||||
loading.value = true
|
||||
ApiFilters()
|
||||
.then((res: any) => {
|
||||
roleList.value = res.data
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 查询(重置页码后获取数据)
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
function handleResetQuery() {
|
||||
if (queryFormRef.value) queryFormRef.value.resetFields()
|
||||
queryParams.pageNum = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 打开角色弹窗
|
||||
function handleOpenDialog(data: any = null) {
|
||||
dialog.visible = true
|
||||
if (data) {
|
||||
functionDialogBox(
|
||||
FilterSettingsForm,
|
||||
{
|
||||
newData: data
|
||||
},
|
||||
{
|
||||
title: '编辑筛选配置',
|
||||
width: '900',
|
||||
ok(value: any) {
|
||||
handleSubmit({ id: data.id, ...value })
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
functionDialogBox(
|
||||
FilterSettingsForm,
|
||||
{},
|
||||
{
|
||||
title: '创建筛选配置',
|
||||
width: '900',
|
||||
ok(value: any) {
|
||||
handleSubmit(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交角色表单
|
||||
function handleSubmit(data: any) {
|
||||
loading.value = true
|
||||
const roleId = data.id
|
||||
if (roleId) {
|
||||
ApiFiltersEditor(data)
|
||||
.then(() => {
|
||||
ElMessage.success('修改成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
} else {
|
||||
ApiFiltersAdd(data)
|
||||
.then(() => {
|
||||
ElMessage.success('新增成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
}
|
||||
}
|
||||
|
||||
const onUserDeleteDepartment = (id: string) => {
|
||||
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(
|
||||
() => {
|
||||
loading.value = true
|
||||
ApiFiltersDelete(id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
},
|
||||
() => {
|
||||
ElMessage.info('已取消删除')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const onStatusModification = (data: any) => {
|
||||
ElMessageBox.confirm(
|
||||
data.is_active ? '确认启用已选中的数据项?' : '确认禁用已选中的数据项?',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
loading.value = true
|
||||
ApiFiltersEditor({ is_active: data.is_active, id: data.id })
|
||||
.then(() => {
|
||||
ElMessage.success('修改成功')
|
||||
handleResetQuery()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('取消修改')
|
||||
handleResetQuery()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
@@ -8,10 +8,10 @@
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item label="任务类型" prop="task_type">
|
||||
<!-- <el-input v-model="formData.task_type" placeholder="请输入" />-->
|
||||
<el-select v-model="formData.task_type" placeholder="请选择">
|
||||
<el-option label="检查登录" value="check_login" />
|
||||
<el-option label="招聘" value="boss_recruit" />
|
||||
<el-option label="回复" value="boss_reply" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务参数" prop="params">
|
||||
@@ -33,7 +33,6 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = reactive<any>({
|
||||
task_type: '',
|
||||
worker_id: '',
|
||||
@@ -56,7 +55,6 @@ const getForm = () => {
|
||||
const setFormData = (data: any) => {
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const data1 = deepCloneByJSON(data)
|
||||
|
||||
Object.assign(formData, data1)
|
||||
}
|
||||
}
|
||||
@@ -73,6 +71,7 @@ const submit = (): Promise<boolean> => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
submit,
|
||||
getForm
|
||||
@@ -108,3 +107,4 @@ defineExpose({
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user