项目需求、任务以及模块精简

This commit is contained in:
2026-04-06 00:23:23 +08:00
parent 97564b82da
commit 477a08da9f
32 changed files with 1888 additions and 874 deletions

View File

@@ -43,7 +43,7 @@ import java.io.Serial;
@Column(name = "contract_status", attrName = "contractStatus", label = "合同状态"), @Column(name = "contract_status", attrName = "contractStatus", label = "合同状态"),
@Column(name = "payment_status", attrName = "paymentStatus", label = "付款状态"), @Column(name = "payment_status", attrName = "paymentStatus", label = "付款状态"),
@Column(name = "remark", attrName = "remark", label = "合同备注", isQuery = false), @Column(name = "remark", attrName = "remark", label = "合同备注", isQuery = false),
@Column(name = "create_user", attrName = "createUser", label = "创建人员", isUpdate = false, isQuery = false), @Column(name = "create_user", attrName = "createUser", label = "创建人员", isUpdate = false),
@Column(name = "update_time", attrName = "updateTime", label = "更新时间", isQuery = false, isUpdateForce = true), @Column(name = "update_time", attrName = "updateTime", label = "更新时间", isQuery = false, isUpdateForce = true),
}, joinTable = { }, joinTable = {
@JoinTable(type = Type.LEFT_JOIN, entity = MyProjectInfo.class, alias = "b", @JoinTable(type = Type.LEFT_JOIN, entity = MyProjectInfo.class, alias = "b",

View File

@@ -43,7 +43,7 @@ import java.io.Serial;
@Column(name = "actual_end_time", attrName = "actualEndTime", label = "实际结束", isQuery = false, isUpdateForce = true), @Column(name = "actual_end_time", attrName = "actualEndTime", label = "实际结束", isQuery = false, isUpdateForce = true),
@Column(name = "work_hours", attrName = "workHours", label = "工时", isQuery = false), @Column(name = "work_hours", attrName = "workHours", label = "工时", isQuery = false),
@Column(name = "remark", attrName = "remark", label = "备注", queryType = QueryType.LIKE), @Column(name = "remark", attrName = "remark", label = "备注", queryType = QueryType.LIKE),
@Column(name = "create_user", attrName = "createUser", label = "创建人员", isUpdate = false, isQuery = false), @Column(name = "create_user", attrName = "createUser", label = "创建人员", isUpdate = false),
@Column(name = "update_time", attrName = "updateTime", label = "更新时间", isUpdate = false, isQuery = false, isUpdateForce = true), @Column(name = "update_time", attrName = "updateTime", label = "更新时间", isUpdate = false, isQuery = false, isUpdateForce = true),
}, joinTable = { }, joinTable = {
@JoinTable(type = Type.LEFT_JOIN, entity = MyProjectInfo.class, alias = "b", @JoinTable(type = Type.LEFT_JOIN, entity = MyProjectInfo.class, alias = "b",

View File

@@ -1,6 +1,7 @@
package com.jeesite.modules.biz.web; package com.jeesite.modules.biz.web;
import java.util.List; import java.util.List;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@@ -26,6 +27,7 @@ import com.jeesite.modules.biz.service.MyProjectContractService;
/** /**
* 项目合同 Controller * 项目合同 Controller
*
* @author gaoxq * @author gaoxq
* @version 2026-04-04 * @version 2026-04-04
*/ */
@@ -33,114 +35,119 @@ import com.jeesite.modules.biz.service.MyProjectContractService;
@RequestMapping(value = "${adminPath}/biz/myProjectContract") @RequestMapping(value = "${adminPath}/biz/myProjectContract")
public class MyProjectContractController extends BaseController { public class MyProjectContractController extends BaseController {
private final MyProjectContractService myProjectContractService; private final MyProjectContractService myProjectContractService;
public MyProjectContractController(MyProjectContractService myProjectContractService) { public MyProjectContractController(MyProjectContractService myProjectContractService) {
this.myProjectContractService = myProjectContractService; this.myProjectContractService = myProjectContractService;
} }
/** /**
* 获取数据 * 获取数据
*/ */
@ModelAttribute @ModelAttribute
public MyProjectContract get(String contractId, boolean isNewRecord) { public MyProjectContract get(String contractId, boolean isNewRecord) {
return myProjectContractService.get(contractId, isNewRecord); return myProjectContractService.get(contractId, isNewRecord);
} }
/** /**
* 查询列表 * 查询列表
*/ */
@RequiresPermissions("biz:myProjectContract:view") @RequiresPermissions("biz:myProjectContract:view")
@RequestMapping(value = {"list", ""}) @RequestMapping(value = {"list", ""})
public String list(MyProjectContract myProjectContract, Model model) { public String list(MyProjectContract myProjectContract, Model model) {
model.addAttribute("myProjectContract", myProjectContract); model.addAttribute("myProjectContract", myProjectContract);
return "modules/biz/myProjectContractList"; return "modules/biz/myProjectContractList";
} }
/** /**
* 查询列表数据 * 查询列表数据
*/ */
@RequiresPermissions("biz:myProjectContract:view") @RequiresPermissions("biz:myProjectContract:view")
@RequestMapping(value = "listData") @RequestMapping(value = "listData")
@ResponseBody @ResponseBody
public Page<MyProjectContract> listData(MyProjectContract myProjectContract, HttpServletRequest request, HttpServletResponse response) { public Page<MyProjectContract> listData(MyProjectContract myProjectContract, HttpServletRequest request, HttpServletResponse response) {
myProjectContract.setPage(new Page<>(request, response)); myProjectContract.setPage(new Page<>(request, response));
Page<MyProjectContract> page = myProjectContractService.findPage(myProjectContract); Page<MyProjectContract> page = myProjectContractService.findPage(myProjectContract);
return page; return page;
} }
/** /**
* 查看编辑表单 * 查看编辑表单
*/ */
@RequiresPermissions("biz:myProjectContract:view") @RequiresPermissions("biz:myProjectContract:view")
@RequestMapping(value = "form") @RequestMapping(value = "form")
public String form(MyProjectContract myProjectContract, Model model) { public String form(MyProjectContract myProjectContract, Model model) {
model.addAttribute("myProjectContract", myProjectContract); model.addAttribute("myProjectContract", myProjectContract);
return "modules/biz/myProjectContractForm"; return "modules/biz/myProjectContractForm";
} }
/** /**
* 保存数据 * 保存数据
*/ */
@RequiresPermissions("biz:myProjectContract:edit") @RequiresPermissions("biz:myProjectContract:edit")
@PostMapping(value = "save") @PostMapping(value = "save")
@ResponseBody @ResponseBody
public String save(@Validated MyProjectContract myProjectContract) { public String save(@Validated MyProjectContract myProjectContract) {
myProjectContractService.save(myProjectContract); myProjectContractService.save(myProjectContract);
return renderResult(Global.TRUE, text("保存合同成功!")); return renderResult(Global.TRUE, text("保存合同成功!"));
} }
/** /**
* 导出数据 * 导出数据
*/ */
@RequiresPermissions("biz:myProjectContract:view") @RequiresPermissions("biz:myProjectContract:view")
@RequestMapping(value = "exportData") @RequestMapping(value = "exportData")
public void exportData(MyProjectContract myProjectContract, HttpServletResponse response) { public void exportData(MyProjectContract myProjectContract, HttpServletResponse response) {
List<MyProjectContract> list = myProjectContractService.findList(myProjectContract); List<MyProjectContract> list = myProjectContractService.findList(myProjectContract);
String fileName = "合同" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx"; String fileName = "合同" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
try(ExcelExport ee = new ExcelExport("合同", MyProjectContract.class)){ try (ExcelExport ee = new ExcelExport("合同", MyProjectContract.class)) {
ee.setDataList(list).write(response, fileName); ee.setDataList(list).write(response, fileName);
} }
} }
/** /**
* 下载模板 * 下载模板
*/ */
@RequiresPermissions("biz:myProjectContract:view") @RequiresPermissions("biz:myProjectContract:view")
@RequestMapping(value = "importTemplate") @RequestMapping(value = "importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
MyProjectContract myProjectContract = new MyProjectContract(); MyProjectContract myProjectContract = new MyProjectContract();
List<MyProjectContract> list = ListUtils.newArrayList(myProjectContract); List<MyProjectContract> list = ListUtils.newArrayList(myProjectContract);
String fileName = "合同模板.xlsx"; String fileName = "合同模板.xlsx";
try(ExcelExport ee = new ExcelExport("合同", MyProjectContract.class, Type.IMPORT)){ try (ExcelExport ee = new ExcelExport("合同", MyProjectContract.class, Type.IMPORT)) {
ee.setDataList(list).write(response, fileName); ee.setDataList(list).write(response, fileName);
} }
} }
/** /**
* 导入数据 * 导入数据
*/ */
@ResponseBody @ResponseBody
@RequiresPermissions("biz:myProjectContract:edit") @RequiresPermissions("biz:myProjectContract:edit")
@PostMapping(value = "importData") @PostMapping(value = "importData")
public String importData(MultipartFile file) { public String importData(MultipartFile file) {
try { try {
String message = myProjectContractService.importData(file); String message = myProjectContractService.importData(file);
return renderResult(Global.TRUE, "posfull:"+message); return renderResult(Global.TRUE, "posfull:" + message);
} catch (Exception ex) { } catch (Exception ex) {
return renderResult(Global.FALSE, "posfull:"+ex.getMessage()); return renderResult(Global.FALSE, "posfull:" + ex.getMessage());
} }
} }
/** /**
* 删除数据 * 删除数据
*/ */
@RequiresPermissions("biz:myProjectContract:edit") @RequiresPermissions("biz:myProjectContract:edit")
@RequestMapping(value = "delete") @RequestMapping(value = "delete")
@ResponseBody @ResponseBody
public String delete(MyProjectContract myProjectContract) { public String delete(MyProjectContract myProjectContract) {
myProjectContractService.delete(myProjectContract); myProjectContractService.delete(myProjectContract);
return renderResult(Global.TRUE, text("删除合同成功!")); return renderResult(Global.TRUE, text("删除合同成功!"));
} }
@RequestMapping(value = "listAll")
@ResponseBody
public List<MyProjectContract> listAll(MyProjectContract myProjectContract) {
return myProjectContractService.findList(myProjectContract);
}
} }

View File

@@ -1,6 +1,7 @@
package com.jeesite.modules.biz.web; package com.jeesite.modules.biz.web;
import java.util.List; import java.util.List;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@@ -26,6 +27,7 @@ import com.jeesite.modules.biz.service.MyProjectTaskService;
/** /**
* 项目任务 Controller * 项目任务 Controller
*
* @author gaoxq * @author gaoxq
* @version 2026-04-04 * @version 2026-04-04
*/ */
@@ -33,114 +35,121 @@ import com.jeesite.modules.biz.service.MyProjectTaskService;
@RequestMapping(value = "${adminPath}/biz/myProjectTask") @RequestMapping(value = "${adminPath}/biz/myProjectTask")
public class MyProjectTaskController extends BaseController { public class MyProjectTaskController extends BaseController {
private final MyProjectTaskService myProjectTaskService; private final MyProjectTaskService myProjectTaskService;
public MyProjectTaskController(MyProjectTaskService myProjectTaskService) { public MyProjectTaskController(MyProjectTaskService myProjectTaskService) {
this.myProjectTaskService = myProjectTaskService; this.myProjectTaskService = myProjectTaskService;
} }
/** /**
* 获取数据 * 获取数据
*/ */
@ModelAttribute @ModelAttribute
public MyProjectTask get(String taskId, boolean isNewRecord) { public MyProjectTask get(String taskId, boolean isNewRecord) {
return myProjectTaskService.get(taskId, isNewRecord); return myProjectTaskService.get(taskId, isNewRecord);
} }
/** /**
* 查询列表 * 查询列表
*/ */
@RequiresPermissions("biz:myProjectTask:view") @RequiresPermissions("biz:myProjectTask:view")
@RequestMapping(value = {"list", ""}) @RequestMapping(value = {"list", ""})
public String list(MyProjectTask myProjectTask, Model model) { public String list(MyProjectTask myProjectTask, Model model) {
model.addAttribute("myProjectTask", myProjectTask); model.addAttribute("myProjectTask", myProjectTask);
return "modules/biz/myProjectTaskList"; return "modules/biz/myProjectTaskList";
} }
/** /**
* 查询列表数据 * 查询列表数据
*/ */
@RequiresPermissions("biz:myProjectTask:view") @RequiresPermissions("biz:myProjectTask:view")
@RequestMapping(value = "listData") @RequestMapping(value = "listData")
@ResponseBody @ResponseBody
public Page<MyProjectTask> listData(MyProjectTask myProjectTask, HttpServletRequest request, HttpServletResponse response) { public Page<MyProjectTask> listData(MyProjectTask myProjectTask, HttpServletRequest request, HttpServletResponse response) {
myProjectTask.setPage(new Page<>(request, response)); myProjectTask.setPage(new Page<>(request, response));
Page<MyProjectTask> page = myProjectTaskService.findPage(myProjectTask); Page<MyProjectTask> page = myProjectTaskService.findPage(myProjectTask);
return page; return page;
} }
/** /**
* 查看编辑表单 * 查看编辑表单
*/ */
@RequiresPermissions("biz:myProjectTask:view") @RequiresPermissions("biz:myProjectTask:view")
@RequestMapping(value = "form") @RequestMapping(value = "form")
public String form(MyProjectTask myProjectTask, Model model) { public String form(MyProjectTask myProjectTask, Model model) {
model.addAttribute("myProjectTask", myProjectTask); model.addAttribute("myProjectTask", myProjectTask);
return "modules/biz/myProjectTaskForm"; return "modules/biz/myProjectTaskForm";
} }
/** /**
* 保存数据 * 保存数据
*/ */
@RequiresPermissions("biz:myProjectTask:edit") @RequiresPermissions("biz:myProjectTask:edit")
@PostMapping(value = "save") @PostMapping(value = "save")
@ResponseBody @ResponseBody
public String save(@Validated MyProjectTask myProjectTask) { public String save(@Validated MyProjectTask myProjectTask) {
myProjectTaskService.save(myProjectTask); myProjectTaskService.save(myProjectTask);
return renderResult(Global.TRUE, text("保存任务成功!")); return renderResult(Global.TRUE, text("保存任务成功!"));
} }
/** /**
* 导出数据 * 导出数据
*/ */
@RequiresPermissions("biz:myProjectTask:view") @RequiresPermissions("biz:myProjectTask:view")
@RequestMapping(value = "exportData") @RequestMapping(value = "exportData")
public void exportData(MyProjectTask myProjectTask, HttpServletResponse response) { public void exportData(MyProjectTask myProjectTask, HttpServletResponse response) {
List<MyProjectTask> list = myProjectTaskService.findList(myProjectTask); List<MyProjectTask> list = myProjectTaskService.findList(myProjectTask);
String fileName = "任务" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx"; String fileName = "任务" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
try(ExcelExport ee = new ExcelExport("任务", MyProjectTask.class)){ try (ExcelExport ee = new ExcelExport("任务", MyProjectTask.class)) {
ee.setDataList(list).write(response, fileName); ee.setDataList(list).write(response, fileName);
} }
} }
/** /**
* 下载模板 * 下载模板
*/ */
@RequiresPermissions("biz:myProjectTask:view") @RequiresPermissions("biz:myProjectTask:view")
@RequestMapping(value = "importTemplate") @RequestMapping(value = "importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
MyProjectTask myProjectTask = new MyProjectTask(); MyProjectTask myProjectTask = new MyProjectTask();
List<MyProjectTask> list = ListUtils.newArrayList(myProjectTask); List<MyProjectTask> list = ListUtils.newArrayList(myProjectTask);
String fileName = "任务模板.xlsx"; String fileName = "任务模板.xlsx";
try(ExcelExport ee = new ExcelExport("任务", MyProjectTask.class, Type.IMPORT)){ try (ExcelExport ee = new ExcelExport("任务", MyProjectTask.class, Type.IMPORT)) {
ee.setDataList(list).write(response, fileName); ee.setDataList(list).write(response, fileName);
} }
} }
/** /**
* 导入数据 * 导入数据
*/ */
@ResponseBody @ResponseBody
@RequiresPermissions("biz:myProjectTask:edit") @RequiresPermissions("biz:myProjectTask:edit")
@PostMapping(value = "importData") @PostMapping(value = "importData")
public String importData(MultipartFile file) { public String importData(MultipartFile file) {
try { try {
String message = myProjectTaskService.importData(file); String message = myProjectTaskService.importData(file);
return renderResult(Global.TRUE, "posfull:"+message); return renderResult(Global.TRUE, "posfull:" + message);
} catch (Exception ex) { } catch (Exception ex) {
return renderResult(Global.FALSE, "posfull:"+ex.getMessage()); return renderResult(Global.FALSE, "posfull:" + ex.getMessage());
} }
} }
/** /**
* 删除数据 * 删除数据
*/ */
@RequiresPermissions("biz:myProjectTask:edit") @RequiresPermissions("biz:myProjectTask:edit")
@RequestMapping(value = "delete") @RequestMapping(value = "delete")
@ResponseBody @ResponseBody
public String delete(MyProjectTask myProjectTask) { public String delete(MyProjectTask myProjectTask) {
myProjectTaskService.delete(myProjectTask); myProjectTaskService.delete(myProjectTask);
return renderResult(Global.TRUE, text("删除任务成功!")); return renderResult(Global.TRUE, text("删除任务成功!"));
} }
@RequestMapping(value = "listAll")
@ResponseBody
public List<MyProjectTask> listAll(MyProjectTask myProjectTask) {
return myProjectTaskService.findList(myProjectTask);
}
} }

View File

@@ -1,5 +0,0 @@
@jeesite:registry=https://maven.jeesite.net/repository/npm-package/
registry=https://registry.npmmirror.com
package-manager-strict=false
auto-install-peers = true
git-checks=false

View File

@@ -35,6 +35,9 @@ export interface MyProjectContract extends BasicModel<MyProjectContract> {
export const myProjectContractList = (params?: MyProjectContract | any) => export const myProjectContractList = (params?: MyProjectContract | any) =>
defHttp.get<MyProjectContract>({ url: adminPath + '/biz/myProjectContract/list', params }); defHttp.get<MyProjectContract>({ url: adminPath + '/biz/myProjectContract/list', params });
export const myProjectContractListAll = (params?: MyProjectContract | any) =>
defHttp.get<MyProjectContract[]>({ url: adminPath + '/biz/myProjectContract/listAll', params });
export const myProjectContractListData = (params?: MyProjectContract | any) => export const myProjectContractListData = (params?: MyProjectContract | any) =>
defHttp.post<Page<MyProjectContract>>({ url: adminPath + '/biz/myProjectContract/listData', params }); defHttp.post<Page<MyProjectContract>>({ url: adminPath + '/biz/myProjectContract/listData', params });

View File

@@ -33,6 +33,9 @@ export interface MyProjectTask extends BasicModel<MyProjectTask> {
export const myProjectTaskList = (params?: MyProjectTask | any) => export const myProjectTaskList = (params?: MyProjectTask | any) =>
defHttp.get<MyProjectTask>({ url: adminPath + '/biz/myProjectTask/list', params }); defHttp.get<MyProjectTask>({ url: adminPath + '/biz/myProjectTask/list', params });
export const myProjectTaskListAll = (params?: MyProjectTask | any) =>
defHttp.get<MyProjectTask[]>({ url: adminPath + '/biz/myProjectTask/listAll', params });
export const myProjectTaskListData = (params?: MyProjectTask | any) => export const myProjectTaskListData = (params?: MyProjectTask | any) =>
defHttp.post<Page<MyProjectTask>>({ url: adminPath + '/biz/myProjectTask/listData', params }); defHttp.post<Page<MyProjectTask>>({ url: adminPath + '/biz/myProjectTask/listData', params });

View File

@@ -27,7 +27,11 @@
import { Icon } from '@jeesite/core/components/Icon'; import { Icon } from '@jeesite/core/components/Icon';
import { BasicForm, FormSchema, useForm } from '@jeesite/core/components/Form'; import { BasicForm, FormSchema, useForm } from '@jeesite/core/components/Form';
import { BasicDrawer, useDrawerInner } from '@jeesite/core/components/Drawer'; import { BasicDrawer, useDrawerInner } from '@jeesite/core/components/Drawer';
import { MyProjectRequirement, myProjectRequirementSave, myProjectRequirementForm } from '@jeesite/biz/api/biz/myProjectRequirement'; import {
MyProjectRequirement,
myProjectRequirementSave,
myProjectRequirementForm,
} from '@jeesite/biz/api/biz/myProjectRequirement';
import { formatToDateTime } from '@jeesite/core/utils/dateUtil'; import { formatToDateTime } from '@jeesite/core/utils/dateUtil';
import { useUserStore } from '@jeesite/core/store/modules/user'; import { useUserStore } from '@jeesite/core/store/modules/user';
@@ -46,6 +50,10 @@
value: record.value.isNewRecord ? t('新增需求') : t('编辑需求'), value: record.value.isNewRecord ? t('新增需求') : t('编辑需求'),
})); }));
function getRequirementNo() {
return `WS_${Date.now()}`;
}
const inputFormSchemas: FormSchema<MyProjectRequirement>[] = [ const inputFormSchemas: FormSchema<MyProjectRequirement>[] = [
{ {
label: t('基本信息'), label: t('基本信息'),
@@ -59,6 +67,7 @@
component: 'Input', component: 'Input',
componentProps: { componentProps: {
maxlength: 64, maxlength: 64,
disabled: true,
}, },
required: true, required: true,
}, },
@@ -74,38 +83,38 @@
{ {
label: t('项目名称'), label: t('项目名称'),
field: 'projectId', field: 'projectId',
fieldLabel: 'projectName', fieldLabel: 'projectName',
component: 'ListSelect', component: 'ListSelect',
componentProps: { componentProps: {
selectType: 'bizProjectSelect', selectType: 'bizProjectSelect',
}, },
required: true,
},
{
label: t('优先等级'),
field: 'priority',
component: 'Select',
componentProps: {
dictType: 'biz_priority',
allowClear: true,
},
required: true, required: true,
}, },
{
label: t('优先等级'),
field: 'priority',
component: 'Select',
componentProps: {
dictType: 'biz_priority',
allowClear: true,
},
required: true,
},
{ {
label: t('详细内容'), label: t('详细内容'),
field: 'requirementContent', field: 'requirementContent',
component: 'InputTextArea', component: 'InputTextArea',
required: true, required: true,
colProps: { md: 24, lg: 24 }, colProps: { md: 24, lg: 24 },
}, },
{ {
label: t('需求状态'), label: t('需求状态'),
field: 'requirementStatus', field: 'requirementStatus',
component: 'Select', component: 'Select',
componentProps: { componentProps: {
dictType: 'requirement_status', dictType: 'requirement_status',
allowClear: true, allowClear: true,
}, },
required: true, required: true,
}, },
{ {
@@ -138,7 +147,7 @@
label: t('需求备注'), label: t('需求备注'),
field: 'remark', field: 'remark',
component: 'InputTextArea', component: 'InputTextArea',
colProps: { md: 24, lg: 24 }, colProps: { md: 24, lg: 24 },
}, },
{ {
label: t('附件上传'), label: t('附件上传'),
@@ -165,6 +174,9 @@
await resetFields(); await resetFields();
const res = await myProjectRequirementForm(data); const res = await myProjectRequirementForm(data);
record.value = (res.myProjectRequirement || {}) as MyProjectRequirement; record.value = (res.myProjectRequirement || {}) as MyProjectRequirement;
if (record.value.isNewRecord) {
record.value.requirementNo = getRequirementNo();
}
record.value.__t = new Date().getTime(); record.value.__t = new Date().getTime();
await setFieldsValue(record.value); await setFieldsValue(record.value);
setDrawerProps({ loading: false }); setDrawerProps({ loading: false });
@@ -179,11 +191,12 @@
requirementId: record.value.requirementId || data.requirementId, requirementId: record.value.requirementId || data.requirementId,
}; };
if(record.value.isNewRecord){ if (record.value.isNewRecord) {
data.createUser = userinfo.value.loginCode; data.createUser = userinfo.value.loginCode;
} data.requirementNo = getRequirementNo();
}
data[record.value.isNewRecord ? 'createTime' : 'updateTime'] = formatToDateTime(new Date()); data[record.value.isNewRecord ? 'createTime' : 'updateTime'] = formatToDateTime(new Date());
// console.log('submit', params, data, record); // console.log('submit', params, data, record);
const res = await myProjectRequirementSave(params, data); const res = await myProjectRequirementSave(params, data);

View File

@@ -1,186 +0,0 @@
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { BasicColumn, BasicTableProps, FormProps } from '@jeesite/core/components/Table';
import { myProjectRequirementListData } from '@jeesite/biz/api/biz/myProjectRequirement';
const { t } = useI18n('biz.myProjectRequirement');
const modalProps = {
title: t('需求选择'),
};
const searchForm: FormProps<MyProjectRequirement> = {
baseColProps: { md: 8, lg: 6 },
labelWidth: 90,
schemas: [
{
label: t('记录时间起'),
field: 'createTime_gte',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm',
showTime: { format: 'HH:mm' },
},
},
{
label: t('记录时间止'),
field: 'createTime_lte',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm',
showTime: { format: 'HH:mm' },
},
},
{
label: t('需求编号'),
field: 'requirementNo',
component: 'Input',
},
{
label: t('需求标题'),
field: 'requirementTitle',
component: 'Input',
},
{
label: t('项目编号'),
field: 'projectId',
component: 'Input',
},
{
label: t('优先等级'),
field: 'priority',
component: 'Input',
},
{
label: t('需求状态'),
field: 'requirementStatus',
component: 'Input',
},
],
};
const tableColumns: BasicColumn<MyProjectRequirement>[] = [
{
title: t('记录时间'),
dataIndex: 'createTime',
key: 'a.create_time',
sorter: true,
width: 230,
align: 'left',
slot: 'firstColumn',
},
{
title: t('需求编号'),
dataIndex: 'requirementNo',
key: 'a.requirement_no',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('需求标题'),
dataIndex: 'requirementTitle',
key: 'a.requirement_title',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('项目编号'),
dataIndex: 'projectId',
key: 'a.project_id',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('详细内容'),
dataIndex: 'requirementContent',
key: 'a.requirement_content',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('优先等级'),
dataIndex: 'priority',
key: 'a.priority',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('需求状态'),
dataIndex: 'requirementStatus',
key: 'a.requirement_status',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('负责人员'),
dataIndex: 'handler',
key: 'a.handler',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('计划完成时间'),
dataIndex: 'planFinishTime',
key: 'a.plan_finish_time',
sorter: true,
width: 130,
align: 'center',
},
{
title: t('实际完成时间'),
dataIndex: 'actualFinishTime',
key: 'a.actual_finish_time',
sorter: true,
width: 130,
align: 'center',
},
{
title: t('备注'),
dataIndex: 'remark',
key: 'a.remark',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('创建人'),
dataIndex: 'createUser',
key: 'a.create_user',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('更新时间'),
dataIndex: 'updateTime',
key: 'a.update_time',
sorter: true,
width: 130,
align: 'center',
},
];
const tableProps: BasicTableProps = {
api: myProjectRequirementListData,
beforeFetch: (params) => {
params['isAll'] = true;
return params;
},
columns: tableColumns,
formConfig: searchForm,
rowKey: 'requirementId',
};
export default {
modalProps,
tableProps,
itemCode: 'requirementId',
itemName: 'requirementId',
isShowCode: false,
};

View File

@@ -42,7 +42,7 @@
const { meta } = unref(router.currentRoute); const { meta } = unref(router.currentRoute);
const record = ref<MyProjectTask>({} as MyProjectTask); const record = ref<MyProjectTask>({} as MyProjectTask);
const requirementListParams = ref<Recordable>({ projectId: '-1' }); const requirementListParams = ref<Recordable>({ projectId: '-1',requirementStatus: '2' });
const getTitle = computed(() => ({ const getTitle = computed(() => ({
icon: meta.icon || 'i-ant-design:book-outlined', icon: meta.icon || 'i-ant-design:book-outlined',

View File

@@ -1,194 +0,0 @@
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { BasicColumn, BasicTableProps, FormProps } from '@jeesite/core/components/Table';
import { myProjectTaskListData } from '@jeesite/biz/api/biz/myProjectTask';
const { t } = useI18n('biz.myProjectTask');
const modalProps = {
title: t('任务选择'),
};
const searchForm: FormProps<MyProjectTask> = {
baseColProps: { md: 8, lg: 6 },
labelWidth: 90,
schemas: [
{
label: t('记录时间起'),
field: 'createTime_gte',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm',
showTime: { format: 'HH:mm' },
},
},
{
label: t('记录时间止'),
field: 'createTime_lte',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm',
showTime: { format: 'HH:mm' },
},
},
{
label: t('任务名称'),
field: 'taskName',
component: 'Input',
},
{
label: t('项目标识'),
field: 'projectId',
component: 'Input',
},
{
label: t('需求标识'),
field: 'requirementId',
component: 'Input',
},
{
label: t('任务状态'),
field: 'taskStatus',
component: 'Input',
},
{
label: t('备注'),
field: 'remark',
component: 'Input',
},
],
};
const tableColumns: BasicColumn<MyProjectTask>[] = [
{
title: t('记录时间'),
dataIndex: 'createTime',
key: 'a.create_time',
sorter: true,
width: 230,
align: 'left',
slot: 'firstColumn',
},
{
title: t('任务名称'),
dataIndex: 'taskName',
key: 'a.task_name',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('项目标识'),
dataIndex: 'projectId',
key: 'a.project_id',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('需求标识'),
dataIndex: 'requirementId',
key: 'a.requirement_id',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('任务状态'),
dataIndex: 'taskStatus',
key: 'a.task_status',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('负责人员'),
dataIndex: 'handler',
key: 'a.handler',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('计划开始'),
dataIndex: 'planStartTime',
key: 'a.plan_start_time',
sorter: true,
width: 130,
align: 'center',
},
{
title: t('计划结束'),
dataIndex: 'planEndTime',
key: 'a.plan_end_time',
sorter: true,
width: 130,
align: 'center',
},
{
title: t('实际开始'),
dataIndex: 'actualStartTime',
key: 'a.actual_start_time',
sorter: true,
width: 130,
align: 'center',
},
{
title: t('实际结束'),
dataIndex: 'actualEndTime',
key: 'a.actual_end_time',
sorter: true,
width: 130,
align: 'center',
},
{
title: t('工时'),
dataIndex: 'workHours',
key: 'a.work_hours',
sorter: true,
width: 130,
align: 'right',
},
{
title: t('备注'),
dataIndex: 'remark',
key: 'a.remark',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('创建人员'),
dataIndex: 'createUser',
key: 'a.create_user',
sorter: true,
width: 130,
align: 'left',
},
{
title: t('更新时间'),
dataIndex: 'updateTime',
key: 'a.update_time',
sorter: true,
width: 130,
align: 'center',
},
];
const tableProps: BasicTableProps = {
api: myProjectTaskListData,
beforeFetch: (params) => {
params['isAll'] = true;
return params;
},
columns: tableColumns,
formConfig: searchForm,
rowKey: 'taskId',
};
export default {
modalProps,
tableProps,
itemCode: 'taskId',
itemName: 'taskId',
isShowCode: false,
};

View File

@@ -1,58 +1,78 @@
<template> <template>
<div class="my-screen-page"> <PageWrapper :contentFullHeight="true" :dense="true" title="false" contentClass="my-screen-page-wrapper">
<header class="my-screen-panel my-screen-panel--header"> <div class="my-screen-page">
<ChartTop /> <header class="my-screen-panel my-screen-panel--header">
</header> <ChartTop />
<section class="my-screen-bottom"> </header>
<div class="my-screen-left"> <section class="my-screen-bottom">
<section class="my-screen-panel my-screen-left-top">左上区域</section> <div class="my-screen-left">
<section class="my-screen-left-middle"> <section class="my-screen-panel my-screen-left-top">左上区域</section>
<div class="my-screen-panel">左中左区域</div> <section class="my-screen-left-middle">
<div class="my-screen-panel">左中区域</div> <div class="my-screen-panel">左中区域</div>
</section> <div class="my-screen-panel">左中右区域</div>
<section class="my-screen-left-bottom"> </section>
<div class="my-screen-panel">左下左区域</div> <section class="my-screen-left-bottom">
<div class="my-screen-panel">左下区域</div> <div class="my-screen-panel">左下区域</div>
</section> <div class="my-screen-panel">左下右区域</div>
</div> </section>
<aside class="my-screen-right"> </div>
<section class="my-screen-panel my-screen-right-top">右上区域</section> <aside class="my-screen-right">
<section class="my-screen-panel my-screen-right-middle">区域</section> <section class="my-screen-panel my-screen-right-top">区域</section>
<section class="my-screen-panel my-screen-right-bottom">区域</section> <section class="my-screen-panel my-screen-right-middle">区域</section>
</aside> <section class="my-screen-panel my-screen-right-bottom">右下区域</section>
</section> </aside>
</div> </section>
</div>
</PageWrapper>
</template> </template>
<script lang="ts" setup name="BizMyScreen"> <script lang="ts" setup name="BizMyScreen">
import ChartTop from './components/ChartTop.vue'; import { PageWrapper } from '@jeesite/core/components/Page';
import ChartTop from './components/ChartTop.vue';
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@dark-bg: #141414; @dark-bg: #141414;
@desktop-page-gap: 12px;
@desktop-page-padding: 0;
@desktop-card-radius: 10px;
@desktop-card-border: 1px solid rgb(226 232 240);
@desktop-card-shadow: 0 1px 3px rgb(15 23 42 / 0.06);
@desktop-dark-border: rgb(51 65 85);
.my-screen-page-wrapper {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding: 0 !important;
overflow: hidden !important;
background: transparent !important;
}
.my-screen-page { .my-screen-page {
display: flex;
flex: 1;
flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
display: flex; gap: @desktop-page-gap;
flex-direction: column; padding: @desktop-page-padding;
gap: 12px;
padding: 0;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.my-screen-panel { .my-screen-panel {
min-height: 0; min-height: 0;
padding: 8px 12px 12px; padding: 8px 12px 12px;
box-sizing: border-box; box-sizing: border-box;
border-radius: 10px; border-radius: @desktop-card-radius;
border: 1px solid rgb(226 232 240); border: @desktop-card-border;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
box-shadow: 0 1px 3px rgb(15 23 42 / 0.06); box-shadow: @desktop-card-shadow;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -72,9 +92,9 @@
flex: 1 1 90%; flex: 1 1 90%;
min-height: 0; min-height: 0;
display: flex; display: flex;
gap: 12px; gap: @desktop-page-gap;
background: transparent; background: transparent;
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.my-screen-left { .my-screen-left {
@@ -83,7 +103,7 @@
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: @desktop-page-gap;
} }
.my-screen-right { .my-screen-right {
@@ -92,7 +112,7 @@
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: @desktop-page-gap;
} }
.my-screen-left-top { .my-screen-left-top {
@@ -104,7 +124,7 @@
flex: 1 1 0; flex: 1 1 0;
min-height: 0; min-height: 0;
display: flex; display: flex;
gap: 12px; gap: @desktop-page-gap;
} }
.my-screen-left-middle .my-screen-panel, .my-screen-left-middle .my-screen-panel,
@@ -131,9 +151,8 @@
background: @dark-bg !important; background: @dark-bg !important;
} }
html[data-theme='dark'] .my-screen-panel,
html[data-theme='dark'] .my-screen-panel { html[data-theme='dark'] .my-screen-panel {
border-color: rgb(51 65 85); border-color: @desktop-dark-border;
background: @dark-bg !important; background: @dark-bg !important;
color: rgb(203 213 225); color: rgb(203 213 225);
box-shadow: none; box-shadow: none;
@@ -145,8 +164,9 @@
@media (max-width: 768px) { @media (max-width: 768px) {
.my-screen-page { .my-screen-page {
height: auto; flex: 1 1 auto;
min-height: 100%; height: 100%;
min-height: 0;
} }
.my-screen-panel--header { .my-screen-panel--header {

View File

@@ -93,19 +93,22 @@
}); });
}; };
const loadComponent = (vueName: string) => { const loadComponent = (vueName: string): Component => {
return defineAsyncComponent({ return defineAsyncComponent({
loader: () => import(`./components/${vueName}.vue`), loader: () => import(`./components/${vueName}.vue`),
delay: 200, delay: 200,
timeout: 5000, timeout: 5000,
onError(error) {
console.error('加载组件失败', error);
},
}); });
}; };
async function getChartList() { async function getChartList() {
try { try {
const reqParams = { const reqParams = {
ustatus: '1', ustatus: '1',
chartCode: 'erp', chartCode: 'erp',
}; };
const res = await myChartInfoListAll(reqParams); const res = await myChartInfoListAll(reqParams);
chartData.value = res || []; chartData.value = res || [];
@@ -132,7 +135,9 @@
await getChartList(); await getChartList();
const newComponentMap: ComponentMap = {}; const newComponentMap: ComponentMap = {};
const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))]; const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))].filter(
(vueName) => vueName && vueName !== 'ChartTop',
);
for (const vueName of uniqueComponents) { for (const vueName of uniqueComponents) {
if (vueName) { if (vueName) {
newComponentMap[vueName] = loadComponent(vueName); newComponentMap[vueName] = loadComponent(vueName);

View File

@@ -29,7 +29,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted, defineAsyncComponent } from 'vue';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import ChartTop from './components/ChartTop.vue'; import ChartTop from './components/ChartTop.vue';
@@ -68,21 +68,22 @@
}); });
}; };
const loadComponent = async (vueName: string): Promise<Component | null> => { const loadComponent = (vueName: string): Component => {
try { return defineAsyncComponent({
const module = await import(`./components/${vueName}.vue`); loader: () => import(`./components/${vueName}.vue`),
return module.default; delay: 200,
} catch (error) { timeout: 5000,
console.error('加载组件失败', error); onError(error) {
return null; console.error('加载组件失败', error);
} },
});
}; };
async function getChartList() { async function getChartList() {
try { try {
const reqParams = { const reqParams = {
ustatus: '1', ustatus: '1',
chartCode: 'home', chartCode: 'home',
}; };
const res = await myChartInfoListAll(reqParams); const res = await myChartInfoListAll(reqParams);
chartData.value = res || []; chartData.value = res || [];
@@ -97,7 +98,9 @@
await getChartList(); await getChartList();
const newComponentMap: ComponentMap = {}; const newComponentMap: ComponentMap = {};
const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))]; const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))].filter(
(vueName) => vueName && vueName !== 'ChartTop',
);
for (const vueName of uniqueComponents) { for (const vueName of uniqueComponents) {
if (vueName) { if (vueName) {
newComponentMap[vueName] = loadComponent(vueName); newComponentMap[vueName] = loadComponent(vueName);

View File

@@ -29,7 +29,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted, defineAsyncComponent } from 'vue';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import ChartTop from './components/ChartTop.vue'; import ChartTop from './components/ChartTop.vue';
@@ -68,21 +68,22 @@
}); });
}; };
const loadComponent = async (vueName: string): Promise<Component | null> => { const loadComponent = (vueName: string): Component => {
try { return defineAsyncComponent({
const module = await import(`./components/${vueName}.vue`); loader: () => import(`./components/${vueName}.vue`),
return module.default; delay: 200,
} catch (error) { timeout: 5000,
console.error('加载组件失败', error); onError(error) {
return null; console.error('加载组件失败', error);
} },
});
}; };
async function getChartList() { async function getChartList() {
try { try {
const reqParams = { const reqParams = {
ustatus: '1', ustatus: '1',
chartCode: 'sys', chartCode: 'sys',
}; };
const res = await myChartInfoListAll(reqParams); const res = await myChartInfoListAll(reqParams);
chartData.value = res || []; chartData.value = res || [];
@@ -97,7 +98,9 @@
await getChartList(); await getChartList();
const newComponentMap: ComponentMap = {}; const newComponentMap: ComponentMap = {};
const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))]; const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))].filter(
(vueName) => vueName && vueName !== 'ChartTop',
);
for (const vueName of uniqueComponents) { for (const vueName of uniqueComponents) {
if (vueName) { if (vueName) {
newComponentMap[vueName] = loadComponent(vueName); newComponentMap[vueName] = loadComponent(vueName);

View File

@@ -37,7 +37,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted, defineAsyncComponent } from 'vue';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import ChartTop from './components/ChartTop.vue'; import ChartTop from './components/ChartTop.vue';
@@ -80,21 +80,22 @@
}); });
}; };
const loadComponent = async (vueName: string): Promise<Component | null> => { const loadComponent = (vueName: string): Component => {
try { return defineAsyncComponent({
const module = await import(`./components/${vueName}.vue`); loader: () => import(`./components/${vueName}.vue`),
return module.default; delay: 200,
} catch (error) { timeout: 5000,
console.error('加载组件失败', error); onError(error) {
return null; console.error('加载组件失败', error);
} },
});
}; };
async function getChartList() { async function getChartList() {
try { try {
const reqParams = { const reqParams = {
ustatus: '1', ustatus: '1',
chartCode: 'work', chartCode: 'work',
}; };
const res = await myChartInfoListAll(reqParams); const res = await myChartInfoListAll(reqParams);
chartData.value = res || []; chartData.value = res || [];
@@ -109,7 +110,9 @@
await getChartList(); await getChartList();
const newComponentMap: ComponentMap = {}; const newComponentMap: ComponentMap = {};
const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))]; const uniqueComponents = [...new Set(chartData.value.map((item) => item.vueName))].filter(
(vueName) => vueName && vueName !== 'ChartTop',
);
for (const vueName of uniqueComponents) { for (const vueName of uniqueComponents) {
if (vueName) { if (vueName) {
newComponentMap[vueName] = loadComponent(vueName); newComponentMap[vueName] = loadComponent(vueName);

View File

@@ -1,8 +1,6 @@
<template> <template>
<PageWrapper> <PageWrapper :contentFullHeight="true" :dense="true" title="false" contentClass="about-page-wrapper">
<template #headerContent> <template #headerContent> </template>
</template>
<div class="jeesite-workbench"> <div class="jeesite-workbench">
<div class="workbench-layout"> <div class="workbench-layout">
<div class="workbench-top">10% 区域</div> <div class="workbench-top">10% 区域</div>
@@ -14,10 +12,10 @@
<div class="workbench-col">30% 区域左侧</div> <div class="workbench-col">30% 区域左侧</div>
<div class="workbench-col">30% 区域右侧</div> <div class="workbench-col">30% 区域右侧</div>
</div> </div>
<div class="workbench-row"> <div class="workbench-row">
<div class="workbench-col">30% 区域左侧</div> <div class="workbench-col">30% 区域左侧</div>
<div class="workbench-col">30% 区域右侧</div> <div class="workbench-col">30% 区域右侧</div>
</div> </div>
</div> </div>
</div> </div>
</PageWrapper> </PageWrapper>
@@ -34,28 +32,48 @@
<style lang="less"> <style lang="less">
@dark-bg: #141414; @dark-bg: #141414;
@desktop-page-gap: 12px;
@desktop-page-padding: 0;
@desktop-card-radius: 10px;
@desktop-card-border: 1px solid rgb(226 232 240);
@desktop-card-shadow: 0 1px 3px rgb(15 23 42 / 0.06);
@desktop-dark-border: rgb(51 65 85);
.about-page-wrapper {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding: 0 !important;
overflow: hidden !important;
background: transparent !important;
}
.jeesite-workbench { .jeesite-workbench {
display: flex;
flex-direction: column;
flex: 1;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
margin: 0; margin: 0;
background: #FFFFFF; background: #ffffff;
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.jeesite-workbench .workbench-layout { .jeesite-workbench .workbench-layout {
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
gap: 12px; gap: @desktop-page-gap;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
padding: 4px; padding: @desktop-page-padding;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.jeesite-workbench .workbench-top { .jeesite-workbench .workbench-top {
@@ -66,7 +84,7 @@
.jeesite-workbench .workbench-row { .jeesite-workbench .workbench-row {
display: flex; display: flex;
flex: 0 0 30%; flex: 0 0 30%;
gap: 12px; gap: @desktop-page-gap;
min-height: 0; min-height: 0;
} }
@@ -75,9 +93,10 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 10px; border-radius: @desktop-card-radius;
border: 1px solid rgb(226 232 240); border: @desktop-card-border;
background: #FFFFFF; background: #ffffff;
box-shadow: @desktop-card-shadow;
color: rgb(71 85 105); color: rgb(71 85 105);
} }
@@ -94,8 +113,9 @@
html[data-theme='dark'] .jeesite-workbench .workbench-top, html[data-theme='dark'] .jeesite-workbench .workbench-top,
html[data-theme='dark'] .jeesite-workbench .workbench-col { html[data-theme='dark'] .jeesite-workbench .workbench-col {
border-color: rgb(51 65 85); border-color: @desktop-dark-border;
background: @dark-bg !important; background: @dark-bg !important;
color: rgb(226 232 240); color: rgb(226 232 240);
box-shadow: none;
} }
</style> </style>

View File

@@ -43,6 +43,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-hover-bg: rgb(30 41 59);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.biz-apps-card { .biz-apps-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -122,7 +126,7 @@
} }
html[data-theme='dark'] .biz-apps-card { html[data-theme='dark'] .biz-apps-card {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -131,12 +135,13 @@
.biz-apps-item { .biz-apps-item {
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
&:hover { &:hover {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
box-shadow: 0 14px 32px rgb(37 99 235 / 22%); background: @analysis-dark-hover-bg;
box-shadow: @analysis-dark-shadow;
} }
&__name { &__name {

View File

@@ -276,6 +276,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-hover-bg: rgb(30 41 59);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.host-card { .host-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -413,7 +417,7 @@
} }
html[data-theme='dark'] .host-card { html[data-theme='dark'] .host-card {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -438,20 +442,21 @@
} }
.host-gauge-panel { .host-gauge-panel {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
} }
.metric-item { .metric-item {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
&__label { &__label {
color: rgb(148 163 184); color: rgb(148 163 184);
} }
&:hover { &:hover {
box-shadow: 0 12px 28px rgb(96 165 250 / 20%); background: @analysis-dark-hover-bg;
box-shadow: @analysis-dark-shadow;
} }
} }
} }

View File

@@ -206,7 +206,9 @@
</script> </script>
<style lang="less"> <style lang="less">
@dark-bg: #141414; @analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-hover-bg: rgb(30 41 59);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.notice-card { .notice-card {
width: 100%; width: 100%;
@@ -591,7 +593,7 @@
html[data-theme='dark'] .notice-card { html[data-theme='dark'] .notice-card {
box-shadow: none; box-shadow: none;
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -616,15 +618,27 @@
.table-container { .table-container {
.el-table { .el-table {
--el-table-border-color: transparent; --el-table-border-color: transparent;
--el-table-header-bg-color: rgb(20, 20, 20); --el-table-header-bg-color: @analysis-dark-bg;
--el-table-tr-bg-color: transparent; --el-table-tr-bg-color: transparent;
--el-table-row-hover-bg-color: rgb(30 41 59); --el-table-row-hover-bg-color: @analysis-dark-hover-bg;
--el-table-text-color: rgb(148 163 184); --el-table-text-color: rgb(148 163 184);
--el-table-header-text-color: rgb(226 232 240); --el-table-header-text-color: rgb(226 232 240);
background: transparent; background: transparent;
th.el-table__cell { th.el-table__cell {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
}
.el-loading-mask {
background-color: rgb(20 20 20 / 72%);
}
.el-loading-spinner .path {
stroke: rgb(147 197 253);
}
.el-loading-spinner .el-loading-text {
color: rgb(203 213 225);
} }
th.el-table__cell, th.el-table__cell,
@@ -650,25 +664,25 @@
&__content-panel, &__content-panel,
&__attachments-panel { &__attachments-panel {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
} }
&__attachments-panel { &__attachments-panel {
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
} }
&__content { &__content {
color: rgb(226 232 240); color: rgb(226 232 240);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
} }
&__attachment-item, &__attachment-item,
&__attachments-empty { &__attachments-empty {
border-color: rgb(71 85 105); border-color: rgb(71 85 105);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
color: rgb(226 232 240); color: rgb(226 232 240);
} }
@@ -686,8 +700,8 @@
&__attachment-item:hover { &__attachment-item:hover {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
background: rgb(37 99 235 / 22%); background: @analysis-dark-hover-bg;
box-shadow: 0 14px 32px rgb(37 99 235 / 22%); box-shadow: @analysis-dark-shadow;
color: rgb(241 245 249); color: rgb(241 245 249);
} }
@@ -698,27 +712,27 @@
} }
html[data-theme='dark'] .notice-info-dialog { html[data-theme='dark'] .notice-info-dialog {
--el-bg-color: rgb(20, 20, 20); --el-bg-color: @analysis-dark-bg;
--el-dialog-bg-color: rgb(20, 20, 20); --el-dialog-bg-color: @analysis-dark-bg;
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
.el-dialog { .el-dialog {
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
--el-dialog-bg-color: rgb(20, 20, 20); --el-dialog-bg-color: @analysis-dark-bg;
--el-bg-color: rgb(20, 20, 20); --el-bg-color: @analysis-dark-bg;
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
box-shadow: 0 14px 36px rgb(0 0 0 / 42%); box-shadow: @analysis-dark-shadow;
} }
.el-dialog__wrapper, .el-dialog__wrapper,
.el-overlay-dialog, .el-overlay-dialog,
.el-dialog__content { .el-dialog__content {
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
} }
.el-dialog__header { .el-dialog__header {
border-bottom: 1px solid rgb(51 65 85) !important; border-bottom: 1px solid rgb(51 65 85) !important;
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
} }
.el-dialog__title { .el-dialog__title {
@@ -730,14 +744,14 @@
} }
.el-dialog__body { .el-dialog__body {
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
} }
.el-dialog__header, .el-dialog__header,
.el-dialog__body, .el-dialog__body,
.el-dialog__footer { .el-dialog__footer {
--el-bg-color: rgb(20, 20, 20); --el-bg-color: @analysis-dark-bg;
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
} }
.el-divider { .el-divider {
@@ -746,7 +760,7 @@
.notice-dialog { .notice-dialog {
&__attachments-title { &__attachments-title {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
color: rgb(226 232 240); color: rgb(226 232 240);
} }

View File

@@ -196,6 +196,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-hover-bg: rgb(30 41 59);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.oper-log-card { .oper-log-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -369,7 +373,7 @@
} }
html[data-theme='dark'] .oper-log-card { html[data-theme='dark'] .oper-log-card {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -378,8 +382,8 @@
.query-panel, .query-panel,
.table-panel { .table-panel {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
} }
.query-form { .query-form {
@@ -391,7 +395,7 @@
:deep(.el-select__wrapper) { :deep(.el-select__wrapper) {
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: rgb(20, 20, 20);
--el-bg-color: rgb(20, 20, 20); --el-bg-color: rgb(20, 20, 20);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
box-shadow: 0 0 0 1px rgb(71 85 105) inset; box-shadow: 0 0 0 1px rgb(71 85 105) inset;
} }
@@ -404,7 +408,7 @@
:deep(.el-select-dropdown), :deep(.el-select-dropdown),
:deep(.el-popper), :deep(.el-popper),
:deep(.el-select__popper.el-popper) { :deep(.el-select__popper.el-popper) {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
} }
@@ -415,7 +419,7 @@
:deep(.el-select-dropdown__item.hover), :deep(.el-select-dropdown__item.hover),
:deep(.el-select-dropdown__item:hover), :deep(.el-select-dropdown__item:hover),
:deep(.el-select-dropdown__item.is-hovering) { :deep(.el-select-dropdown__item.is-hovering) {
background: rgb(30 41 59); background: @analysis-dark-hover-bg;
color: rgb(241 245 249); color: rgb(241 245 249);
} }
@@ -453,28 +457,28 @@
} }
:deep(.el-button) { :deep(.el-button) {
--el-fill-color-blank: rgb(30 41 59); --el-fill-color-blank: @analysis-dark-bg;
--el-bg-color: rgb(30 41 59); --el-bg-color: @analysis-dark-bg;
border-color: rgb(71 85 105); border-color: rgb(71 85 105);
background: rgb(30 41 59); background: @analysis-dark-bg;
color: rgb(226 232 240); color: rgb(226 232 240);
} }
:deep(.el-button:hover) { :deep(.el-button:hover) {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
background: rgb(37 99 235 / 22%); background: @analysis-dark-hover-bg;
color: rgb(241 245 249); color: rgb(241 245 249);
} }
:deep(.el-button--primary) { :deep(.el-button--primary) {
border-color: rgb(59 130 246); border-color: rgb(96 165 250);
background: rgb(37 99 235); background: @analysis-dark-bg;
color: rgb(248 250 252); color: rgb(248 250 252);
} }
:deep(.el-button--primary:hover) { :deep(.el-button--primary:hover) {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
background: rgb(59 130 246); background: @analysis-dark-hover-bg;
color: rgb(248 250 252); color: rgb(248 250 252);
} }
} }
@@ -482,15 +486,15 @@
.table-panel { .table-panel {
.el-table { .el-table {
--el-table-border-color: transparent; --el-table-border-color: transparent;
--el-table-header-bg-color: rgb(20, 20, 20); --el-table-header-bg-color: @analysis-dark-bg;
--el-table-tr-bg-color: transparent; --el-table-tr-bg-color: transparent;
--el-table-row-hover-bg-color: rgb(30 41 59); --el-table-row-hover-bg-color: @analysis-dark-hover-bg;
--el-table-text-color: rgb(148 163 184); --el-table-text-color: rgb(148 163 184);
--el-table-header-text-color: rgb(226 232 240); --el-table-header-text-color: rgb(226 232 240);
background: transparent; background: transparent;
th.el-table__cell { th.el-table__cell {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
} }
th.el-table__cell, th.el-table__cell,
@@ -499,11 +503,23 @@
} }
.el-table__footer-wrapper td.el-table__cell { .el-table__footer-wrapper td.el-table__cell {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
border-top: 1px solid rgb(51 65 85); border-top: 1px solid rgb(51 65 85);
border-bottom: none; border-bottom: none;
} }
.el-loading-mask {
background-color: rgb(20 20 20 / 72%);
}
.el-loading-spinner .path {
stroke: rgb(147 197 253);
}
.el-loading-spinner .el-loading-text {
color: rgb(203 213 225);
}
.el-table__footer-wrapper .cell { .el-table__footer-wrapper .cell {
color: rgb(226 232 240); color: rgb(226 232 240);
} }
@@ -517,7 +533,7 @@
:deep(.el-pagination) { :deep(.el-pagination) {
--el-pagination-text-color: rgb(226 232 240); --el-pagination-text-color: rgb(226 232 240);
--el-pagination-button-color: rgb(226 232 240); --el-pagination-button-color: rgb(226 232 240);
--el-pagination-button-bg-color: rgb(30 41 59); --el-pagination-button-bg-color: @analysis-dark-bg;
--el-pagination-hover-color: rgb(147 197 253); --el-pagination-hover-color: rgb(147 197 253);
} }
@@ -525,7 +541,7 @@
:deep(.btn-next), :deep(.btn-next),
:deep(.el-pager li), :deep(.el-pager li),
:deep(.el-pagination button) { :deep(.el-pagination button) {
background: rgb(30 41 59) !important; background: @analysis-dark-bg !important;
color: rgb(226 232 240) !important; color: rgb(226 232 240) !important;
} }
@@ -578,7 +594,7 @@
html[data-theme='dark'] .el-select-dropdown__item.hover, html[data-theme='dark'] .el-select-dropdown__item.hover,
html[data-theme='dark'] .el-select-dropdown__item:hover, html[data-theme='dark'] .el-select-dropdown__item:hover,
html[data-theme='dark'] .el-select-dropdown__item.is-hovering { html[data-theme='dark'] .el-select-dropdown__item.is-hovering {
background: rgb(30 41 59) !important; background: @analysis-dark-hover-bg !important;
color: rgb(241 245 249) !important; color: rgb(241 245 249) !important;
} }
@@ -590,7 +606,7 @@
html[data-theme='dark'] .el-pagination .btn-next, html[data-theme='dark'] .el-pagination .btn-next,
html[data-theme='dark'] .el-pagination .el-pager li, html[data-theme='dark'] .el-pagination .el-pager li,
html[data-theme='dark'] .el-pagination button { html[data-theme='dark'] .el-pagination button {
background: rgb(30 41 59) !important; background: @analysis-dark-bg !important;
color: rgb(226 232 240) !important; color: rgb(226 232 240) !important;
} }
@@ -632,7 +648,7 @@
html[data-theme='dark'] .oper-log-card .el-input.is-disabled .el-input__wrapper, html[data-theme='dark'] .oper-log-card .el-input.is-disabled .el-input__wrapper,
html[data-theme='dark'] .oper-log-card .el-select.is-disabled .el-select__wrapper { html[data-theme='dark'] .oper-log-card .el-select.is-disabled .el-select__wrapper {
background: rgb(30 41 59) !important; background: @analysis-dark-bg !important;
} }
html[data-theme='dark'] .oper-log-card .el-input__suffix, html[data-theme='dark'] .oper-log-card .el-input__suffix,
@@ -644,16 +660,16 @@
} }
html[data-theme='dark'] .oper-log-card .query-form__actions .el-button { html[data-theme='dark'] .oper-log-card .query-form__actions .el-button {
--el-button-bg-color: rgb(30 41 59) !important; --el-button-bg-color: @analysis-dark-bg !important;
--el-button-border-color: rgb(71 85 105) !important; --el-button-border-color: rgb(71 85 105) !important;
--el-button-text-color: rgb(226 232 240) !important; --el-button-text-color: rgb(226 232 240) !important;
--el-button-hover-bg-color: rgb(37 99 235 / 22%) !important; --el-button-hover-bg-color: @analysis-dark-hover-bg !important;
--el-button-hover-border-color: rgb(96 165 250) !important; --el-button-hover-border-color: rgb(96 165 250) !important;
--el-button-hover-text-color: rgb(241 245 249) !important; --el-button-hover-text-color: rgb(241 245 249) !important;
--el-button-active-bg-color: rgb(37 99 235 / 28%) !important; --el-button-active-bg-color: @analysis-dark-hover-bg !important;
--el-button-active-border-color: rgb(96 165 250) !important; --el-button-active-border-color: rgb(96 165 250) !important;
--el-button-active-text-color: rgb(241 245 249) !important; --el-button-active-text-color: rgb(241 245 249) !important;
background: rgb(30 41 59) !important; background: @analysis-dark-bg !important;
border-color: rgb(71 85 105) !important; border-color: rgb(71 85 105) !important;
color: rgb(226 232 240) !important; color: rgb(226 232 240) !important;
box-shadow: none !important; box-shadow: none !important;
@@ -661,26 +677,26 @@
html[data-theme='dark'] .oper-log-card .query-form__actions .el-button:hover, html[data-theme='dark'] .oper-log-card .query-form__actions .el-button:hover,
html[data-theme='dark'] .oper-log-card .query-form__actions .el-button:focus { html[data-theme='dark'] .oper-log-card .query-form__actions .el-button:focus {
background: rgb(37 99 235 / 22%) !important; background: @analysis-dark-hover-bg !important;
border-color: rgb(96 165 250) !important; border-color: rgb(96 165 250) !important;
color: rgb(241 245 249) !important; color: rgb(241 245 249) !important;
} }
html[data-theme='dark'] .oper-log-card .query-form__actions .el-button--primary { html[data-theme='dark'] .oper-log-card .query-form__actions .el-button--primary {
--el-button-bg-color: rgb(37 99 235) !important; --el-button-bg-color: @analysis-dark-bg !important;
--el-button-border-color: rgb(59 130 246) !important; --el-button-border-color: rgb(96 165 250) !important;
--el-button-text-color: rgb(248 250 252) !important; --el-button-text-color: rgb(248 250 252) !important;
--el-button-hover-bg-color: rgb(59 130 246) !important; --el-button-hover-bg-color: @analysis-dark-hover-bg !important;
--el-button-hover-border-color: rgb(96 165 250) !important; --el-button-hover-border-color: rgb(96 165 250) !important;
--el-button-hover-text-color: rgb(248 250 252) !important; --el-button-hover-text-color: rgb(248 250 252) !important;
background: rgb(37 99 235) !important; background: @analysis-dark-bg !important;
border-color: rgb(59 130 246) !important; border-color: rgb(96 165 250) !important;
color: rgb(248 250 252) !important; color: rgb(248 250 252) !important;
} }
html[data-theme='dark'] .oper-log-card .query-form__actions .el-button--primary:hover, html[data-theme='dark'] .oper-log-card .query-form__actions .el-button--primary:hover,
html[data-theme='dark'] .oper-log-card .query-form__actions .el-button--primary:focus { html[data-theme='dark'] .oper-log-card .query-form__actions .el-button--primary:focus {
background: rgb(59 130 246) !important; background: @analysis-dark-hover-bg !important;
border-color: rgb(96 165 250) !important; border-color: rgb(96 165 250) !important;
color: rgb(248 250 252) !important; color: rgb(248 250 252) !important;
} }

View File

@@ -184,6 +184,9 @@
</script> </script>
<style lang="less"> <style lang="less">
@analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.province-card { .province-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -241,7 +244,7 @@
} }
html[data-theme='dark'] .province-card { html[data-theme='dark'] .province-card {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -253,8 +256,8 @@
} }
.province-chart-panel { .province-chart-panel {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
} }
} }
</style> </style>

View File

@@ -157,6 +157,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-hover-bg: rgb(30 41 59);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.quick-login-card { .quick-login-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -292,7 +296,7 @@
} }
html[data-theme='dark'] .quick-login-card { html[data-theme='dark'] .quick-login-card {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -301,12 +305,13 @@
.quick-login-item { .quick-login-item {
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @analysis-dark-shadow;
&:hover { &:hover {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
box-shadow: 0 14px 32px rgb(37 99 235 / 22%); background: @analysis-dark-hover-bg;
box-shadow: @analysis-dark-shadow;
} }
&__name { &__name {
@@ -314,7 +319,7 @@
} }
&__image-wrap { &__image-wrap {
background: rgb(30 41 59); background: @analysis-dark-bg;
border-color: rgb(59 130 246 / 35%); border-color: rgb(59 130 246 / 35%);
box-shadow: 0 6px 14px rgb(37 99 235 / 16%); box-shadow: 0 6px 14px rgb(37 99 235 / 16%);
} }

View File

@@ -255,6 +255,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@analysis-dark-bg: rgb(20, 20, 20);
@analysis-dark-hover-bg: rgb(30 41 59);
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.notice-card { .notice-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -568,7 +572,7 @@
} }
html[data-theme='dark'] .notice-card { html[data-theme='dark'] .notice-card {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
.card-title { .card-title {
color: rgb(203 213 225); color: rgb(203 213 225);
@@ -593,15 +597,27 @@
.table-container { .table-container {
.el-table { .el-table {
--el-table-border-color: transparent; --el-table-border-color: transparent;
--el-table-header-bg-color: rgb(20, 20, 20); --el-table-header-bg-color: @analysis-dark-bg;
--el-table-tr-bg-color: transparent; --el-table-tr-bg-color: transparent;
--el-table-row-hover-bg-color: rgb(30 41 59); --el-table-row-hover-bg-color: @analysis-dark-hover-bg;
--el-table-text-color: rgb(148 163 184); --el-table-text-color: rgb(148 163 184);
--el-table-header-text-color: rgb(226 232 240); --el-table-header-text-color: rgb(226 232 240);
background: transparent; background: transparent;
th.el-table__cell { th.el-table__cell {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
}
.el-loading-mask {
background-color: rgb(20 20 20 / 72%);
}
.el-loading-spinner .path {
stroke: rgb(147 197 253);
}
.el-loading-spinner .el-loading-text {
color: rgb(203 213 225);
} }
th.el-table__cell, th.el-table__cell,
@@ -629,7 +645,7 @@
&__form-panel, &__form-panel,
&__content, &__content,
&__section-title { &__section-title {
background: rgb(20, 20, 20); background: @analysis-dark-bg;
} }
&__content { &__content {
@@ -649,7 +665,7 @@
&__textarea :deep(.el-textarea__inner) { &__textarea :deep(.el-textarea__inner) {
border: 1px solid rgb(71 85 105) !important; border: 1px solid rgb(71 85 105) !important;
border-color: rgb(71 85 105); border-color: rgb(71 85 105);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
color: rgb(226 232 240); color: rgb(226 232 240);
box-shadow: none !important; box-shadow: none !important;
} }
@@ -668,38 +684,38 @@
&__textarea :deep(.el-input__count) { &__textarea :deep(.el-input__count) {
color: rgb(148 163 184); color: rgb(148 163 184);
background: rgb(20, 20, 20); background: @analysis-dark-bg;
} }
} }
} }
html[data-theme='dark'] .todo-info-dialog { html[data-theme='dark'] .todo-info-dialog {
--el-bg-color: rgb(20, 20, 20); --el-bg-color: @analysis-dark-bg;
--el-dialog-bg-color: rgb(20, 20, 20); --el-dialog-bg-color: @analysis-dark-bg;
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
.el-dialog { .el-dialog {
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
--el-dialog-bg-color: rgb(20, 20, 20); --el-dialog-bg-color: @analysis-dark-bg;
--el-bg-color: rgb(20, 20, 20); --el-bg-color: @analysis-dark-bg;
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
box-shadow: 0 14px 36px rgb(0 0 0 / 42%); box-shadow: @analysis-dark-shadow;
} }
.el-dialog__wrapper, .el-dialog__wrapper,
.el-overlay-dialog, .el-overlay-dialog,
.el-dialog__content { .el-dialog__content {
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
} }
.el-dialog__header { .el-dialog__header {
border-bottom: 1px solid rgb(51 65 85) !important; border-bottom: 1px solid rgb(51 65 85) !important;
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
} }
.el-dialog__body, .el-dialog__body,
.el-dialog__footer { .el-dialog__footer {
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
} }
.el-textarea, .el-textarea,
@@ -708,8 +724,8 @@
--el-input-border-color: rgb(71 85 105); --el-input-border-color: rgb(71 85 105);
--el-input-hover-border-color: rgb(96 165 250); --el-input-hover-border-color: rgb(96 165 250);
--el-input-focus-border-color: rgb(96 165 250); --el-input-focus-border-color: rgb(96 165 250);
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
background: rgb(20, 20, 20) !important; background: @analysis-dark-bg !important;
box-shadow: none !important; box-shadow: none !important;
} }
@@ -728,8 +744,8 @@
.el-dialog__header, .el-dialog__header,
.el-dialog__body, .el-dialog__body,
.el-dialog__footer { .el-dialog__footer {
--el-bg-color: rgb(20, 20, 20); --el-bg-color: @analysis-dark-bg;
--el-fill-color-blank: rgb(20, 20, 20); --el-fill-color-blank: @analysis-dark-bg;
} }
.el-divider { .el-divider {
@@ -742,26 +758,26 @@
.todo-dialog__footer { .todo-dialog__footer {
.el-button:not(.el-button--primary) { .el-button:not(.el-button--primary) {
border-color: rgb(71 85 105); border-color: rgb(96 165 250);
background: rgb(30 41 59); background: @analysis-dark-bg;
color: rgb(226 232 240); color: rgb(226 232 240);
} }
.el-button:not(.el-button--primary):hover { .el-button:not(.el-button--primary):hover {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
background: rgb(37 99 235 / 22%); background: @analysis-dark-hover-bg;
color: rgb(241 245 249); color: rgb(241 245 249);
} }
.el-button--primary { .el-button--primary {
border-color: rgb(59 130 246); border-color: rgb(96 165 250);
background: rgb(37 99 235); background: @analysis-dark-bg;
color: rgb(248 250 252); color: rgb(248 250 252);
} }
.el-button--primary:hover { .el-button--primary:hover {
border-color: rgb(96 165 250); border-color: rgb(96 165 250);
background: rgb(59 130 246); background: @analysis-dark-hover-bg;
} }
} }
} }

View File

@@ -43,6 +43,12 @@
</script> </script>
<style lang="less"> <style lang="less">
@dark-bg: #141414; @dark-bg: #141414;
@desktop-page-gap: 12px;
@desktop-page-padding: 0;
@desktop-card-radius: 10px;
@desktop-card-border: 1px solid rgb(226 232 240);
@desktop-card-shadow: 0 1px 3px rgb(15 23 42 / 0.06);
@desktop-dark-border: rgb(51 65 85);
.analysis-page-wrapper { .analysis-page-wrapper {
display: flex; display: flex;
@@ -97,18 +103,19 @@
--analysis-card-title-padding: 8px 16px; --analysis-card-title-padding: 8px 16px;
--analysis-card-content-padding: 8px 12px 12px; --analysis-card-content-padding: 8px 12px 12px;
--analysis-card-item-gap: 8px; --analysis-card-item-gap: 8px;
--analysis-card-radius: 10px; --analysis-gap: @desktop-page-gap;
--analysis-card-radius: @desktop-card-radius;
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
min-height: 0; min-height: 0;
gap: var(--analysis-gap); gap: var(--analysis-gap);
padding: 0; padding: @desktop-page-padding;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.mySpring-analysis .analysis-left, .mySpring-analysis .analysis-left,
@@ -148,16 +155,16 @@
min-width: 0; min-width: 0;
padding: 0; padding: 0;
border-radius: var(--analysis-card-radius); border-radius: var(--analysis-card-radius);
border: 1px solid rgb(226 232 240); border: @desktop-card-border;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
box-shadow: 0 1px 3px rgb(15 23 42 / 0.06); box-shadow: @desktop-card-shadow;
overflow: hidden; overflow: hidden;
color: rgb(71 85 105); color: rgb(71 85 105);
font-size: 16px; font-size: 16px;
} }
html[data-theme='dark'] .mySpring-analysis .analysis-panel { html[data-theme='dark'] .mySpring-analysis .analysis-panel {
border-color: rgb(51 65 85); border-color: @desktop-dark-border;
background: @dark-bg !important; background: @dark-bg !important;
color: rgb(203 213 225); color: rgb(203 213 225);
box-shadow: none; box-shadow: none;
@@ -171,7 +178,7 @@
} }
html[data-theme='dark'] .mySpring-analysis { html[data-theme='dark'] .mySpring-analysis {
border-radius: 10px; border-radius: @desktop-card-radius;
} }
html[data-theme='dark'] .mySpring-analysis, html[data-theme='dark'] .mySpring-analysis,

View File

@@ -0,0 +1,585 @@
<template>
<div ref="contractCardRef" class="contract-info-card">
<div class="card-title">
<span>项目合同</span>
<el-button class="card-title__more" link type="primary" @click="goMore">更多</el-button>
</div>
<div class="card-content">
<div class="contract-overview">
<div class="task-chart-panel task-chart-panel--pie">
<div ref="pieChartRef" class="task-chart"></div>
</div>
<div class="task-chart-panel task-chart-panel--bar">
<div ref="barChartRef" class="task-chart"></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import * as echarts from 'echarts';
import { MyProjectContract, myProjectContractListAll } from '@jeesite/biz/api/biz/myProjectContract';
import { DictData, dictDataListData } from '@jeesite/core/api/sys/dictData';
import { useUserStore } from '@jeesite/core/store/modules/user';
import { firstCurrentYear, formatToDate } from '@jeesite/core/utils/dateUtil';
const router = useRouter();
const userStore = useUserStore();
const userinfo = computed(() => userStore.getUserInfo);
const contractCardRef = ref<HTMLElement>();
const pieChartRef = ref<HTMLElement>();
const barChartRef = ref<HTMLElement>();
const sourceData = ref<MyProjectContract[]>([]);
const statusDict = ref<DictData[]>([]);
const STATUS_COLORS = ['#3B82F6', '#10B981', '#F97316', '#EC4899', '#8B5CF6', '#06B6D4'];
let pieChartInstance: echarts.ECharts | null = null;
let barChartInstance: echarts.ECharts | null = null;
let resizeObserver: ResizeObserver | null = null;
let themeObserver: MutationObserver | null = null;
function goMore() {
router.push('/biz/myProjectContract/list');
}
const pieChartData = computed(() => {
if (!statusDict.value.length) {
const fallbackMap = new Map<string, number>();
sourceData.value.forEach((item) => {
const label = item.contractStatus || '未设置';
fallbackMap.set(label, (fallbackMap.get(label) || 0) + 1);
});
return Array.from(fallbackMap.entries()).map(([name, value], index) => ({
name,
value,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}));
}
const countMap = new Map<string, number>();
sourceData.value.forEach((item) => {
const key = item.contractStatus || 'unknown';
countMap.set(key, (countMap.get(key) || 0) + 1);
});
return statusDict.value
.map((item, index) => ({
name: item.dictLabelRaw,
value: countMap.get(item.dictValue || '') || 0,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}))
.filter((item) => item.value > 0);
});
const barChartData = computed(() => {
const statusItems = statusDict.value.length
? statusDict.value.map((item, index) => ({
key: item.dictValue,
label: item.dictLabelRaw,
color: STATUS_COLORS[index % STATUS_COLORS.length],
}))
: [];
const monthStatusMap = new Map<string, Map<string, number>>();
const monthOrderMap = new Map<string, number>();
sourceData.value.forEach((item) => {
if (!item.signDate) return;
const date = new Date(item.signDate);
if (Number.isNaN(date.getTime())) return;
const monthLabel = `${date.getMonth() + 1}`;
monthOrderMap.set(monthLabel, date.getMonth() + 1);
const statusKey = item.contractStatus || 'unknown';
if (!monthStatusMap.has(monthLabel)) {
monthStatusMap.set(monthLabel, new Map<string, number>());
}
const monthMap = monthStatusMap.get(monthLabel)!;
monthMap.set(statusKey, (monthMap.get(statusKey) || 0) + 1);
});
const months = Array.from(monthStatusMap.keys()).sort((a, b) => {
return (monthOrderMap.get(a) || 0) - (monthOrderMap.get(b) || 0);
});
const totalByMonth = months.map((month) => {
const monthMap = monthStatusMap.get(month);
return Array.from(monthMap?.values() || []).reduce((sum, count) => sum + count, 0);
});
const fallbackStatusMap = new Map<string, number>();
sourceData.value.forEach((item) => {
if (!statusDict.value.length) {
const key = item.contractStatus || '未设置';
fallbackStatusMap.set(key, (fallbackStatusMap.get(key) || 0) + 1);
}
});
const fallbackSeries = Array.from(fallbackStatusMap.keys()).map((key, index) => ({
key,
label: key,
color: STATUS_COLORS[index % STATUS_COLORS.length],
}));
return {
months,
totals: totalByMonth,
statusItems: statusItems.length ? statusItems : fallbackSeries,
monthStatusMap,
};
});
async function getDict() {
try {
statusDict.value = await dictDataListData({ dictType: 'contract_status' });
} catch (error) {
statusDict.value = [];
}
}
async function getList() {
try {
const reqParams = {
createTime_gte: firstCurrentYear(),
createUser: userinfo.value.loginCode,
};
const res = await myProjectContractListAll(reqParams);
sourceData.value = res || [];
} catch (error) {
sourceData.value = [];
}
}
function renderPieChart() {
if (!pieChartRef.value) return;
if (!pieChartInstance) {
pieChartInstance = echarts.init(pieChartRef.value);
}
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const total = pieChartData.value.reduce((sum, item) => sum + Number(item.value || 0), 0);
const data = total
? pieChartData.value
: statusDict.value.map((item, index) => ({
name: item.dictLabelRaw,
value: 0,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}));
const legendData = statusDict.value.length
? statusDict.value.map((item) => item.dictLabelRaw)
: pieChartData.value.map((item) => item.name);
const series: echarts.SeriesOption[] = [];
if (!total) {
series.push({
name: '空态环',
type: 'pie',
radius: ['46%', '72%'],
center: ['50%', '44%'],
silent: true,
legendHoverLink: false,
tooltip: {
show: false,
},
label: {
show: false,
},
labelLine: {
show: false,
},
itemStyle: {
color: isDark ? 'rgba(148, 163, 184, 0.18)' : 'rgba(203, 213, 225, 0.55)',
borderRadius: 10,
borderColor: isDark ? '#141414' : '#ffffff',
borderWidth: 3,
},
data: [{ name: 'empty-ring', value: 1 }],
});
}
series.push({
name: '合同状态',
type: 'pie',
radius: total ? ['46%', '72%'] : [0, 0],
center: ['50%', '44%'],
avoidLabelOverlap: true,
minAngle: total ? 4 : 0,
itemStyle: {
borderRadius: 10,
borderColor: isDark ? '#141414' : '#ffffff',
borderWidth: 3,
},
label: {
show: total,
formatter: ({ name, value, percent }) => (total ? `${name}\n${value}项 / ${percent}%` : ''),
color: isDark ? '#e2e8f0' : '#334155',
fontSize: 12,
},
emphasis: {
scale: true,
scaleSize: 6,
},
data,
silent: !total,
labelLine: {
show: total,
},
});
pieChartInstance.setOption({
tooltip: {
trigger: 'item',
backgroundColor: isDark ? 'rgba(20, 20, 20, 0.96)' : 'rgba(255, 255, 255, 0.96)',
borderColor: isDark ? 'rgb(51 65 85)' : 'rgb(226 232 240)',
borderWidth: 1,
textStyle: {
color: isDark ? '#e2e8f0' : '#334155',
},
formatter: ({ name, value, percent }) => {
if (!total) return `${name}<br/>数量0`;
return `${name}<br/>数量:${value}<br/>占比:${percent}%`;
},
},
legend: {
show: legendData.length > 0,
bottom: 4,
left: 'center',
itemWidth: 10,
itemHeight: 10,
data: legendData,
textStyle: {
color: isDark ? '#cbd5e1' : '#475569',
fontSize: 12,
},
},
series,
graphic: total
? [
{
type: 'text',
left: 'center',
top: '34%',
style: {
text: '合同总数',
fill: isDark ? '#94a3b8' : '#64748b',
fontSize: 12,
},
},
{
type: 'text',
left: 'center',
top: '41%',
style: {
text: `${total}`,
fill: isDark ? '#f8fafc' : '#0f172a',
fontSize: 24,
fontWeight: 700,
},
},
]
: [],
});
}
function renderBarChart() {
if (!barChartRef.value) return;
if (!barChartInstance) {
barChartInstance = echarts.init(barChartRef.value);
}
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const { months, totals, statusItems, monthStatusMap } = barChartData.value;
const hasData = totals.some((item) => item > 0);
const categories = months.length ? months : [];
const series = statusItems.length
? statusItems.map((status) => ({
name: status.label,
type: 'bar',
stack: 'month-total',
barWidth: '12%',
itemStyle: {
color: status.color,
borderRadius: 0,
},
emphasis: {
focus: 'series',
},
label: {
show: true,
position: 'inside',
formatter: ({ value }) => (Number(value) > 0 ? `${value}` : ''),
color: '#ffffff',
fontSize: 11,
},
data: categories.map((month) => monthStatusMap.get(month)?.get(status.key) || 0),
}))
: [
{
type: 'bar',
barWidth: '12%',
itemStyle: {
color: isDark ? '#334155' : '#E2E8F0',
borderRadius: [10, 10, 0, 0],
},
data: categories.map(() => 0),
},
];
if (hasData) {
series.push({
name: '总数',
type: 'bar',
stack: 'month-total',
silent: true,
legendHoverLink: false,
itemStyle: {
color: 'rgba(0,0,0,0)',
},
tooltip: {
show: false,
},
label: {
show: true,
position: 'top',
color: isDark ? '#cbd5e1' : '#475569',
fontSize: 11,
formatter: ({ dataIndex }) => (totals[dataIndex] > 0 ? `${totals[dataIndex]}` : ''),
},
data: categories.map(() => 0),
z: 10,
});
}
barChartInstance.setOption({
grid: {
left: 12,
right: 12,
top: 52,
bottom: 10,
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
backgroundColor: isDark ? 'rgba(20, 20, 20, 0.96)' : 'rgba(255, 255, 255, 0.96)',
borderColor: isDark ? 'rgb(51 65 85)' : 'rgb(226 232 240)',
borderWidth: 1,
textStyle: {
color: isDark ? '#e2e8f0' : '#334155',
},
formatter: (params) => {
const dataIndex = params[0]?.dataIndex || 0;
if (!hasData) return '';
const lines = params
.filter((item) => item.seriesName !== '总数' && Number(item.value) > 0)
.map((item) => `${item.marker}${item.seriesName}${item.value}`);
lines.push(`总数:${totals[dataIndex]}`);
return `${categories[dataIndex]}<br/>${lines.join('<br/>')}`;
},
},
legend: {
top: 6,
left: 'center',
itemGap: 16,
data: statusItems.map((item) => item.label),
textStyle: {
color: isDark ? '#e2e8f0' : '#475569',
},
},
xAxis: {
type: 'category',
data: categories,
boundaryGap: ['2%', '2%'],
axisTick: {
alignWithLabel: true,
},
axisLine: {
lineStyle: {
color: isDark ? '#475569' : '#cbd5e1',
},
},
axisLabel: {
color: isDark ? '#cbd5e1' : '#64748b',
interval: 0,
margin: 8,
rotate: 30,
},
},
yAxis: {
type: 'value',
minInterval: 1,
name: '数量',
nameTextStyle: {
color: isDark ? '#94a3b8' : '#64748b',
padding: [0, 0, 2, 0],
},
splitLine: {
lineStyle: {
color: isDark ? 'rgba(71, 85, 105, 0.35)' : 'rgba(203, 213, 225, 0.55)',
},
},
axisLabel: {
color: isDark ? '#94a3b8' : '#64748b',
},
},
series,
});
}
function renderCharts() {
renderPieChart();
renderBarChart();
}
function resizeCharts() {
pieChartInstance?.resize();
barChartInstance?.resize();
}
onMounted(async () => {
await Promise.all([getDict(), getList()]);
await nextTick();
renderCharts();
if (contractCardRef.value) {
resizeObserver = new ResizeObserver(() => {
resizeCharts();
});
resizeObserver.observe(contractCardRef.value);
}
window.addEventListener('resize', resizeCharts);
themeObserver = new MutationObserver(() => {
nextTick(() => {
renderCharts();
});
});
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme'],
});
});
onUnmounted(() => {
resizeObserver?.disconnect();
themeObserver?.disconnect();
window.removeEventListener('resize', resizeCharts);
pieChartInstance?.dispose();
barChartInstance?.dispose();
pieChartInstance = null;
barChartInstance = null;
});
</script>
<style lang="less">
@workbench-dark-panel-bg: rgb(20, 20, 20);
@workbench-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.contract-info-card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
color: rgb(51 65 85);
border-bottom: 1px solid rgb(226 232 240);
background: transparent;
&__more {
padding: 0;
font-size: 13px;
}
}
.card-content {
flex: 1;
min-height: 0;
padding: 16px;
overflow: hidden;
background: transparent;
}
.contract-overview {
display: grid;
grid-template-columns: minmax(220px, 0.9fr) minmax(0, 1.6fr);
gap: 12px;
height: 100%;
min-height: 0;
}
.task-chart-panel {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
padding: 12px;
border-radius: 12px;
background: rgb(255, 255, 255);
box-shadow: 0 8px 24px rgb(148 163 184 / 14%);
}
.task-chart {
flex: 1;
min-height: 0;
width: 100%;
}
}
html[data-theme='dark'] .contract-info-card {
.card-title {
color: rgb(203 213 225);
border-bottom-color: rgb(51 65 85);
&__more:deep(.el-button) {
color: rgb(147 197 253);
}
}
.task-chart-panel {
background: @workbench-dark-panel-bg;
box-shadow: @workbench-dark-shadow;
}
}
@media (max-width: 1100px) {
.contract-info-card {
.contract-overview {
grid-template-columns: 1fr;
}
}
}
@media (max-width: 768px) {
.contract-info-card {
.card-content {
padding: 12px;
}
.task-chart-panel {
padding: 10px;
}
.task-chart {
min-height: 220px;
}
}
}
</style>

View File

@@ -331,6 +331,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@workbench-dark-panel-bg: rgb(20, 20, 20);
@workbench-dark-solid-bg: rgb(20, 20, 20);
@workbench-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.note-card { .note-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -508,14 +512,14 @@
.metric-item, .metric-item,
.note-chart-panel { .note-chart-panel {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @workbench-dark-panel-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @workbench-dark-shadow;
} }
.metric-item { .metric-item {
&__pane { &__pane {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @workbench-dark-panel-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @workbench-dark-shadow;
} }
&__extra, &__extra,
@@ -525,12 +529,12 @@
&__label { &__label {
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
background: rgb(20, 20, 20); background: @workbench-dark-solid-bg;
} }
&__label--active { &__label--active {
border-color: rgb(59 130 246 / 40%); border-color: rgb(59 130 246 / 40%);
background: rgb(37 99 235 / 14%) !important; background: @workbench-dark-solid-bg !important;
color: rgb(191 219 254) !important; color: rgb(191 219 254) !important;
} }
} }

View File

@@ -0,0 +1,612 @@
<template>
<div ref="projectTaskCardRef" class="project-task-card">
<div class="card-title">
<span>项目任务</span>
<el-button class="card-title__more" link type="primary" @click="goMore">更多</el-button>
</div>
<div class="card-content">
<div class="project-task-overview">
<div class="task-chart-panel task-chart-panel--pie">
<div ref="pieChartRef" class="task-chart"></div>
</div>
<div class="task-chart-panel task-chart-panel--bar">
<div ref="barChartRef" class="task-chart"></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import * as echarts from 'echarts';
import { MyProjectTask, myProjectTaskListAll } from '@jeesite/biz/api/biz/myProjectTask';
import { DictData, dictDataListData } from '@jeesite/core/api/sys/dictData';
import { useUserStore } from '@jeesite/core/store/modules/user';
import { firstCurrentYear, formatToDate } from '@jeesite/core/utils/dateUtil';
const router = useRouter();
const userStore = useUserStore();
const userinfo = computed(() => userStore.getUserInfo);
const projectTaskCardRef = ref<HTMLElement>();
const pieChartRef = ref<HTMLElement>();
const barChartRef = ref<HTMLElement>();
const sourceData = ref<MyProjectTask[]>([]);
const statusDict = ref<DictData[]>([]);
const STATUS_COLORS = ['#3B82F6', '#10B981', '#F97316', '#EC4899', '#8B5CF6', '#06B6D4'];
let pieChartInstance: echarts.ECharts | null = null;
let barChartInstance: echarts.ECharts | null = null;
let resizeObserver: ResizeObserver | null = null;
let themeObserver: MutationObserver | null = null;
function goMore() {
router.push('/biz/myProjectTask/list');
}
const pieChartData = computed(() => {
if (!statusDict.value.length) {
const fallbackMap = new Map<string, number>();
sourceData.value.forEach((item) => {
const label = item.taskStatus || '未设置';
fallbackMap.set(label, (fallbackMap.get(label) || 0) + 1);
});
return Array.from(fallbackMap.entries()).map(([name, value], index) => ({
name,
value,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}));
}
const countMap = new Map<string, number>();
sourceData.value.forEach((item) => {
const key = item.taskStatus || 'unknown';
countMap.set(key, (countMap.get(key) || 0) + 1);
});
return statusDict.value
.map((item, index) => ({
name: item.dictLabelRaw,
value: countMap.get(item.dictValue || '') || 0,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}))
.filter((item) => item.value > 0);
});
const barChartData = computed(() => {
const statusItems = statusDict.value.length
? statusDict.value.map((item, index) => ({
key: item.dictValue,
label: item.dictLabelRaw,
color: STATUS_COLORS[index % STATUS_COLORS.length],
}))
: [];
const monthStatusMap = new Map<string, Map<string, number>>();
const monthOrderMap = new Map<string, number>();
sourceData.value.forEach((item) => {
if (!item.createTime) return;
const date = new Date(item.createTime);
if (Number.isNaN(date.getTime())) return;
const monthLabel = `${date.getMonth() + 1}`;
monthOrderMap.set(monthLabel, date.getMonth() + 1);
const statusKey = item.taskStatus || 'unknown';
if (!monthStatusMap.has(monthLabel)) {
monthStatusMap.set(monthLabel, new Map<string, number>());
}
const monthMap = monthStatusMap.get(monthLabel)!;
monthMap.set(statusKey, (monthMap.get(statusKey) || 0) + 1);
});
const months = Array.from(monthStatusMap.keys()).sort((a, b) => {
return (monthOrderMap.get(a) || 0) - (monthOrderMap.get(b) || 0);
});
const totalByMonth = months.map((month) => {
const monthMap = monthStatusMap.get(month);
return Array.from(monthMap?.values() || []).reduce((sum, count) => sum + count, 0);
});
const fallbackStatusMap = new Map<string, number>();
sourceData.value.forEach((item) => {
if (!statusDict.value.length) {
const key = item.taskStatus || '未设置';
fallbackStatusMap.set(key, (fallbackStatusMap.get(key) || 0) + 1);
}
});
const fallbackSeries = Array.from(fallbackStatusMap.keys()).map((key, index) => ({
key,
label: key,
color: STATUS_COLORS[index % STATUS_COLORS.length],
}));
return {
months,
totals: totalByMonth,
statusItems: statusItems.length ? statusItems : fallbackSeries,
monthStatusMap,
};
});
async function getDict() {
try {
statusDict.value = await dictDataListData({ dictType: 'task_status' });
} catch (error) {
statusDict.value = [];
}
}
async function getList() {
try {
const reqParams = {
createTime_gte: firstCurrentYear(),
createUser: userinfo.value.loginCode,
};
const res = await myProjectTaskListAll(reqParams);
sourceData.value = res || [];
} catch (error) {
sourceData.value = [];
}
}
function renderPieChart() {
if (!pieChartRef.value) return;
if (!pieChartInstance) {
pieChartInstance = echarts.init(pieChartRef.value);
}
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const total = pieChartData.value.reduce((sum, item) => sum + Number(item.value || 0), 0);
const data = total
? pieChartData.value
: statusDict.value.map((item, index) => ({
name: item.dictLabelRaw,
value: 0,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}));
const legendData = statusDict.value.length
? statusDict.value.map((item) => item.dictLabelRaw)
: pieChartData.value.map((item) => item.name);
const series: echarts.SeriesOption[] = [];
if (!total) {
series.push({
name: '空态环',
type: 'pie',
radius: ['46%', '72%'],
center: ['50%', '44%'],
silent: true,
legendHoverLink: false,
tooltip: {
show: false,
},
label: {
show: false,
},
labelLine: {
show: false,
},
itemStyle: {
color: isDark ? 'rgba(148, 163, 184, 0.18)' : 'rgba(203, 213, 225, 0.55)',
borderRadius: 10,
borderColor: isDark ? '#141414' : '#ffffff',
borderWidth: 3,
},
data: [{ name: 'empty-ring', value: 1 }],
});
}
series.push({
name: '任务状态',
type: 'pie',
radius: total ? ['46%', '72%'] : [0, 0],
center: ['50%', '44%'],
avoidLabelOverlap: true,
minAngle: total ? 4 : 0,
itemStyle: {
borderRadius: 10,
borderColor: isDark ? '#141414' : '#ffffff',
borderWidth: 3,
},
label: {
show: total,
formatter: ({ name, value, percent }) => (total ? `${name}\n${value}项 / ${percent}%` : ''),
color: isDark ? '#e2e8f0' : '#334155',
fontSize: 12,
},
emphasis: {
scale: true,
scaleSize: 6,
},
data,
silent: !total,
labelLine: {
show: total,
},
});
pieChartInstance.setOption({
tooltip: {
trigger: 'item',
backgroundColor: isDark ? 'rgba(20, 20, 20, 0.96)' : 'rgba(255, 255, 255, 0.96)',
borderColor: isDark ? 'rgb(51 65 85)' : 'rgb(226 232 240)',
borderWidth: 1,
textStyle: {
color: isDark ? '#e2e8f0' : '#334155',
},
formatter: ({ name, value, percent }) => {
if (!total) return `${name}<br/>数量0`;
return `${name}<br/>数量:${value}<br/>占比:${percent}%`;
},
},
legend: {
show: legendData.length > 0,
bottom: 4,
left: 'center',
itemWidth: 10,
itemHeight: 10,
data: legendData,
textStyle: {
color: isDark ? '#cbd5e1' : '#475569',
fontSize: 12,
},
},
series,
graphic: total
? [
{
type: 'text',
left: 'center',
top: '34%',
style: {
text: '任务总数',
fill: isDark ? '#94a3b8' : '#64748b',
fontSize: 12,
},
},
{
type: 'text',
left: 'center',
top: '41%',
style: {
text: `${total}`,
fill: isDark ? '#f8fafc' : '#0f172a',
fontSize: 24,
fontWeight: 700,
},
},
]
: [],
});
}
function renderBarChart() {
if (!barChartRef.value) return;
if (!barChartInstance) {
barChartInstance = echarts.init(barChartRef.value);
}
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const { months, totals, statusItems, monthStatusMap } = barChartData.value;
const hasData = totals.some((item) => item > 0);
const categories = months.length ? months : [];
const series = statusItems.length
? statusItems.map((status) => ({
name: status.label,
type: 'bar',
stack: 'month-total',
barWidth: '12%',
itemStyle: {
color: status.color,
borderRadius: 0,
},
emphasis: {
focus: 'series',
},
label: {
show: true,
position: 'inside',
formatter: ({ value }) => (Number(value) > 0 ? `${value}` : ''),
color: '#ffffff',
fontSize: 11,
},
data: categories.map((month) => monthStatusMap.get(month)?.get(status.key) || 0),
}))
: [
{
type: 'bar',
barWidth: '12%',
itemStyle: {
color: isDark ? '#334155' : '#E2E8F0',
borderRadius: [10, 10, 0, 0],
},
data: categories.map(() => 0),
},
];
if (hasData) {
series.push({
name: '总数',
type: 'bar',
stack: 'month-total',
silent: true,
legendHoverLink: false,
itemStyle: {
color: 'rgba(0,0,0,0)',
},
tooltip: {
show: false,
},
label: {
show: true,
position: 'top',
color: isDark ? '#cbd5e1' : '#475569',
fontSize: 11,
formatter: ({ dataIndex }) => (totals[dataIndex] > 0 ? `${totals[dataIndex]}` : ''),
},
data: categories.map(() => 0),
z: 10,
});
}
barChartInstance.setOption({
grid: {
left: 12,
right: 12,
top: 52,
bottom: 10,
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
backgroundColor: isDark ? 'rgba(20, 20, 20, 0.96)' : 'rgba(255, 255, 255, 0.96)',
borderColor: isDark ? 'rgb(51 65 85)' : 'rgb(226 232 240)',
borderWidth: 1,
textStyle: {
color: isDark ? '#e2e8f0' : '#334155',
},
formatter: (params) => {
const dataIndex = params[0]?.dataIndex || 0;
if (!hasData) return '';
const lines = params
.filter((item) => item.seriesName !== '总数' && Number(item.value) > 0)
.map((item) => `${item.marker}${item.seriesName}${item.value}`);
lines.push(`总数:${totals[dataIndex]}`);
return `${categories[dataIndex]}<br/>${lines.join('<br/>')}`;
},
},
legend: {
top: 6,
left: 'center',
itemGap: 16,
data: statusItems.map((item) => item.label),
textStyle: {
color: isDark ? '#e2e8f0' : '#475569',
},
},
xAxis: {
type: 'category',
data: categories,
boundaryGap: ['2%', '2%'],
axisTick: {
alignWithLabel: true,
},
axisLine: {
lineStyle: {
color: isDark ? '#475569' : '#cbd5e1',
},
},
axisLabel: {
color: isDark ? '#cbd5e1' : '#64748b',
interval: 0,
margin: 8,
rotate: 30,
},
},
yAxis: {
type: 'value',
name: '数量',
nameTextStyle: {
color: isDark ? '#94a3b8' : '#64748b',
padding: [0, 0, 2, 0],
},
splitLine: {
lineStyle: {
color: isDark ? 'rgba(71, 85, 105, 0.35)' : 'rgba(203, 213, 225, 0.55)',
},
},
axisLabel: {
color: isDark ? '#94a3b8' : '#64748b',
},
},
series,
});
}
function renderCharts() {
renderPieChart();
renderBarChart();
}
function resizeCharts() {
pieChartInstance?.resize();
barChartInstance?.resize();
}
onMounted(async () => {
await Promise.all([getDict(), getList()]);
await nextTick();
renderCharts();
if (projectTaskCardRef.value) {
resizeObserver = new ResizeObserver(() => {
resizeCharts();
});
resizeObserver.observe(projectTaskCardRef.value);
}
window.addEventListener('resize', resizeCharts);
themeObserver = new MutationObserver(() => {
nextTick(() => {
renderCharts();
});
});
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme'],
});
});
onUnmounted(() => {
resizeObserver?.disconnect();
themeObserver?.disconnect();
window.removeEventListener('resize', resizeCharts);
pieChartInstance?.dispose();
barChartInstance?.dispose();
pieChartInstance = null;
barChartInstance = null;
});
</script>
<style lang="less">
@workbench-dark-panel-bg: rgb(20, 20, 20);
@workbench-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.project-task-card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
color: rgb(51 65 85);
border-bottom: 1px solid rgb(226 232 240);
background: transparent;
&__more {
padding: 0;
font-size: 13px;
}
}
.card-content {
flex: 1;
min-height: 0;
padding: 16px;
overflow: hidden;
background: transparent;
}
.project-task-overview {
display: grid;
grid-template-columns: minmax(220px, 0.9fr) minmax(0, 1.6fr);
gap: 12px;
height: 100%;
min-height: 0;
}
.task-chart-panel {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
padding: 12px;
border-radius: 12px;
background: rgb(255, 255, 255);
box-shadow: 0 8px 24px rgb(148 163 184 / 14%);
}
.panel-header {
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: 8px;
}
.panel-title {
color: rgb(51 65 85);
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
.panel-subtitle {
color: rgb(100 116 139);
font-size: 12px;
line-height: 16px;
}
.task-chart {
flex: 1;
min-height: 0;
width: 100%;
}
}
html[data-theme='dark'] .project-task-card {
.card-title {
color: rgb(203 213 225);
border-bottom-color: rgb(51 65 85);
&__more:deep(.el-button) {
color: rgb(147 197 253);
}
}
.task-chart-panel {
background: @workbench-dark-panel-bg;
box-shadow: @workbench-dark-shadow;
}
.panel-title {
color: rgb(226 232 240);
}
.panel-subtitle {
color: rgb(148 163 184);
}
}
@media (max-width: 1100px) {
.project-task-card {
.project-task-overview {
grid-template-columns: 1fr;
}
}
}
@media (max-width: 768px) {
.project-task-card {
.card-content {
padding: 12px;
}
.task-chart-panel {
padding: 10px;
}
.task-chart {
min-height: 220px;
}
}
}
</style>

View File

@@ -314,6 +314,10 @@
</script> </script>
<style lang="less"> <style lang="less">
@workbench-dark-panel-bg: rgb(20, 20, 20);
@workbench-dark-solid-bg: rgb(20, 20, 20);
@workbench-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
.schedule-card { .schedule-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -700,14 +704,14 @@
.summary-item, .summary-item,
.schedule-panel { .schedule-panel {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @workbench-dark-panel-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @workbench-dark-shadow;
} }
.summary-item { .summary-item {
&__pane { &__pane {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @workbench-dark-panel-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @workbench-dark-shadow;
} }
&__extra, &__extra,
@@ -717,7 +721,7 @@
&__label { &__label {
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
background: rgb(20, 20, 20); background: @workbench-dark-solid-bg;
} }
} }
@@ -734,12 +738,12 @@
.panel-tag, .panel-tag,
.timeline-scroll { .timeline-scroll {
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%); background: @workbench-dark-panel-bg;
box-shadow: 0 10px 24px rgb(0 0 0 / 24%); box-shadow: @workbench-dark-shadow;
} }
.timeline-row__hint { .timeline-row__hint {
background: rgb(30 41 59 / 75%); background: @workbench-dark-solid-bg;
} }
.timeline-row__hint-title { .timeline-row__hint-title {
@@ -756,7 +760,7 @@
.timeline-event { .timeline-event {
border-color: rgb(51 65 85); border-color: rgb(51 65 85);
background: rgb(30 41 59 / 55%); background: @workbench-dark-solid-bg;
color: rgb(226 232 240); color: rgb(226 232 240);
&:hover { &:hover {
@@ -778,7 +782,7 @@
} }
&__label { &__label {
background: rgb(69 10 10); background: @workbench-dark-solid-bg;
color: rgb(254 202 202); color: rgb(254 202 202);
} }
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<PageWrapper :contentFullHeight="true"> <PageWrapper :contentFullHeight="true" :dense="true" title="false" contentClass="workbench-page-wrapper">
<template #headerContent> <template #headerContent>
<WorkbenchHeader /> <WorkbenchHeader />
</template> </template>
@@ -10,12 +10,16 @@
<NoteInfo /> <NoteInfo />
</div> </div>
<div class="workbench-col"> <div class="workbench-col">
<ScheduleInfo /> <ScheduleInfo />
</div> </div>
</div> </div>
<div class="workbench-row"> <div class="workbench-row">
<div class="workbench-col">下左</div> <div class="workbench-col">
<div class="workbench-col">下右</div> <ProjectTask />
</div>
<div class="workbench-col">
<ContractInfo />
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -26,6 +30,8 @@
import { PageWrapper } from '@jeesite/core/components/Page'; import { PageWrapper } from '@jeesite/core/components/Page';
import WorkbenchHeader from './components/WorkbenchHeader.vue'; import WorkbenchHeader from './components/WorkbenchHeader.vue';
import NoteInfo from './components/NoteInfo.vue'; import NoteInfo from './components/NoteInfo.vue';
import ProjectTask from './components/ProjectTask.vue';
import ContractInfo from './components/ContractInfo.vue';
import ScheduleInfo from './components/ScheduleInfo.vue'; import ScheduleInfo from './components/ScheduleInfo.vue';
const loading = ref(true); const loading = ref(true);
@@ -36,44 +42,64 @@
<style lang="less"> <style lang="less">
@dark-bg: #141414; @dark-bg: #141414;
@desktop-page-gap: 12px;
@desktop-page-padding: 0;
@desktop-card-radius: 10px;
@desktop-card-border: 1px solid rgb(226 232 240);
@desktop-card-shadow: 0 1px 3px rgb(15 23 42 / 0.06);
@desktop-dark-border: rgb(51 65 85);
.workbench-page-wrapper {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding: 0 !important;
overflow: hidden !important;
background: transparent !important;
}
.jeesite-workbench { .jeesite-workbench {
display: flex;
flex-direction: column;
flex: 1;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
margin: 0; margin: 0;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.jeesite-workbench .workbench-layout { .jeesite-workbench .workbench-layout {
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
gap: 8px; gap: @desktop-page-gap;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
padding: 0; padding: @desktop-page-padding;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border-radius: 10px; border-radius: @desktop-card-radius;
} }
.jeesite-workbench .workbench-row { .jeesite-workbench .workbench-row {
display: flex; display: flex;
flex: 1 1 0; flex: 1 1 0;
gap: 8px; gap: @desktop-page-gap;
min-height: 0; min-height: 0;
} }
.jeesite-workbench .workbench-col { .jeesite-workbench .workbench-col {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 10px; border-radius: @desktop-card-radius;
border: 1px solid rgb(226 232 240); border: @desktop-card-border;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
box-shadow: 0 1px 3px rgb(15 23 42 / 0.06); box-shadow: @desktop-card-shadow;
color: rgb(71 85 105); color: rgb(71 85 105);
font-size: 16px; font-size: 16px;
overflow: hidden; overflow: hidden;
@@ -91,7 +117,7 @@
} }
html[data-theme='dark'] .jeesite-workbench .workbench-col { html[data-theme='dark'] .jeesite-workbench .workbench-col {
border-color: rgb(51 65 85); border-color: @desktop-dark-border;
background: @dark-bg !important; background: @dark-bg !important;
color: rgb(226 232 240); color: rgb(226 232 240);
box-shadow: none; box-shadow: none;

View File

@@ -19,4 +19,12 @@ export function formatToDate(date: dayjs.ConfigType | undefined = undefined, for
return dayjs(date).format(format); return dayjs(date).format(format);
} }
export function firstCurrentYear(format = DATE_FORMAT): string {
return dayjs().startOf('year').format(format);
}
export function firstCurrentMonth(format = DATE_FORMAT): string {
return dayjs().startOf('month').format(format);
}
export const dateUtil = dayjs; export const dateUtil = dayjs;