批量推送
This commit is contained in:
+3
-2
@@ -27,6 +27,7 @@ import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.system.domain.vo.SysUserExtendVo;
|
||||
import com.ruoyi.system.service.ISysDeptService;
|
||||
import com.ruoyi.system.service.ISysPostService;
|
||||
import com.ruoyi.system.service.ISysRoleService;
|
||||
@@ -58,10 +59,10 @@ public class SysUserController extends BaseController
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:user:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(SysUser user)
|
||||
public TableDataInfo list(SysUserExtendVo vo)
|
||||
{
|
||||
startPage();
|
||||
List<SysUser> list = userService.selectUserList(user);
|
||||
List<SysUserExtendVo> list = userService.selectUserExtendList(vo);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
|
||||
+75
-10
@@ -18,13 +18,15 @@ import com.ruoyi.system.service.ISysUserService;
|
||||
|
||||
/**
|
||||
* 业务登录 - 角色选择
|
||||
* registerExecutor 完整实现 (2026-08-15 重构, 原 registerSupplier):
|
||||
* registerExecutor / registerSponsor 完整实现 (2026-08-15 重构, 原 registerSupplier):
|
||||
* 1. 校验短信验证码
|
||||
* 2. 检查手机号/用户名是否已注册
|
||||
* 3. 加密密码
|
||||
* 4. 插入 sys_user (用户名=前端传入, role_type=executor, 主账号)
|
||||
* 5. 插入 biz_org (executor 类型, business_nature/contact_phone 从表单)
|
||||
* 注: 主账号不建 biz_person, 跟 sponsor 主账号保持一致
|
||||
* 4. 插入 sys_user (用户名=前端传入, sys_user.role_type 写入 executor/sponsor, 主账号)
|
||||
* 5. 插入 biz_org (对应类型, business_nature/contact_phone 从表单)
|
||||
* 注: 主账号不建 biz_person
|
||||
*
|
||||
* 2026-08-16 重构: 删 biz_user_role_bind, 统一用 sys_user.role_type (见迁移脚本)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/business/auth")
|
||||
@@ -101,7 +103,7 @@ public class BizAuthController extends BaseController {
|
||||
user.setStatus("0");
|
||||
userService.insertUser(user);
|
||||
Long userId = user.getUserId();
|
||||
// sys_user.role_type 字段需 = executor
|
||||
// 业务角色统一写到 sys_user.role_type (2026-08-16 重构, 删 biz_user_role_bind)
|
||||
userService.updateRoleType(userId, "executor");
|
||||
|
||||
// 5. 插入 biz_org (executor 类型, 主账号自己当 contact)
|
||||
@@ -116,15 +118,78 @@ public class BizAuthController extends BaseController {
|
||||
bizOrgService.insert(org);
|
||||
Long orgId = org.getOrgId();
|
||||
|
||||
// 注: 主账号不建 biz_person (与 sponsor 主账号保持一致, 见 sponsor/Account.vue)
|
||||
// 后续主账号在 [账号信息] 看到 [所属公司] 时, 后端可通过 sys_user.role_type 关联 biz_org 取 org_name
|
||||
// 子账号 (biz_person.unit_type='sponsor' + user_id 关联 sys_user.parent_user_id) 由 [人员管理] 创建
|
||||
|
||||
return success("注册成功, 请等待审核").put("userId", userId).put("orgId", orgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册赞助方 (主账号), 流程与 executor 一致:
|
||||
* 校验 → 查重 → 写 sys_user + role_type=sponsor → 写 biz_org (sponsor 类型)
|
||||
*/
|
||||
@PostMapping("/registerSponsor")
|
||||
public AjaxResult registerSponsor(@RequestBody Map<String, Object> body) {
|
||||
return success();
|
||||
String username = (String) body.get("username");
|
||||
String unitName = (String) body.get("unitName");
|
||||
String businessNature = (String) body.get("businessNature");
|
||||
String phone = (String) body.get("phone");
|
||||
String code = (String) body.get("smsCode");
|
||||
String password = (String) body.get("password");
|
||||
String confirmPassword = (String) body.get("confirmPassword");
|
||||
String uuid = (String) body.get("uuid");
|
||||
|
||||
// 1. 基础校验 (与 executor 同)
|
||||
if (username == null || username.length() < 4 || username.length() > 20) return error("用户名长度 4-20 位");
|
||||
if (!username.matches("^[A-Za-z0-9_]+$")) return error("用户名只能包含字母/数字/下划线");
|
||||
if (unitName == null || unitName.isEmpty()) return error("企业名称不能为空");
|
||||
if (businessNature == null || businessNature.isEmpty()) return error("企业性质不能为空");
|
||||
if (phone == null || !phone.matches("^1\\d{10}$")) return error("手机号格式错误");
|
||||
if (code == null || code.isEmpty()) return error("请输入短信验证码");
|
||||
if (password == null || password.length() < 6 || password.length() > 20) return error("密码长度 6-20 位");
|
||||
if (!password.equals(confirmPassword)) return error("两次密码输入不一致");
|
||||
|
||||
// 2. 校验短信验证码
|
||||
SmsValidForm smsForm = new SmsValidForm();
|
||||
smsForm.setPhone(phone);
|
||||
smsForm.setSmsCode(code);
|
||||
smsForm.setUuid(uuid);
|
||||
try {
|
||||
smsService.verifyCode(smsForm);
|
||||
} catch (RuntimeException e) {
|
||||
return error("验证码错误或已过期: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 3. 查重
|
||||
if (userService.isPhoneRegistered(phone)) {
|
||||
return error("该手机号已注册, 请直接登录");
|
||||
}
|
||||
SysUser nameCheck = new SysUser();
|
||||
nameCheck.setUserName(username);
|
||||
if (!userService.checkUserNameUnique(nameCheck)) {
|
||||
return error("用户名已被占用: " + username);
|
||||
}
|
||||
|
||||
// 4. 写 sys_user (role_type=sponsor)
|
||||
SysUser user = new SysUser();
|
||||
user.setUserName(username);
|
||||
user.setNickName(unitName);
|
||||
user.setPhonenumber(phone);
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
user.setStatus("0");
|
||||
userService.insertUser(user);
|
||||
Long userId = user.getUserId();
|
||||
userService.updateRoleType(userId, "sponsor");
|
||||
|
||||
// 5. 写 biz_org (sponsor 类型)
|
||||
BizOrg org = new BizOrg();
|
||||
org.setOrgId(null);
|
||||
org.setOrgName(unitName);
|
||||
org.setOrgType("sponsor");
|
||||
org.setBusinessNature(businessNature);
|
||||
org.setContactPhone(phone);
|
||||
org.setContactName(unitName);
|
||||
org.setStatus("0");
|
||||
bizOrgService.insert(org);
|
||||
Long orgId = org.getOrgId();
|
||||
|
||||
return success("注册成功, 请等待审核").put("userId", userId).put("orgId", orgId);
|
||||
}
|
||||
}
|
||||
|
||||
+86
-1
@@ -1,15 +1,22 @@
|
||||
package com.ruoyi.business.controller;
|
||||
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.vo.BizExpertExportVo;
|
||||
import com.ruoyi.business.domain.vo.BizExpertImportVo;
|
||||
import com.ruoyi.business.service.IBizExpertService;
|
||||
|
||||
/**
|
||||
@@ -41,11 +48,16 @@ public class BizExpertController extends BaseController
|
||||
{
|
||||
return success(bizExpertService.getByUserId(SecurityUtils.getUserId()));
|
||||
}
|
||||
/**
|
||||
* admin 创建专家: 同时创建 sys_user (用户名=手机号, 密码=手机号)
|
||||
* 返回 SysUser (含明文 password 给前端 toast 用)
|
||||
*/
|
||||
@Log(title = "专家", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody BizExpert bizExpert)
|
||||
{
|
||||
return toAjax(bizExpertService.insert(bizExpert));
|
||||
SysUser created = bizExpertService.insert(bizExpert);
|
||||
return success(created);
|
||||
}
|
||||
@Log(title = "专家", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
@@ -53,6 +65,16 @@ public class BizExpertController extends BaseController
|
||||
{
|
||||
return toAjax(bizExpertService.updateByPrimaryKey(bizExpert));
|
||||
}
|
||||
/**
|
||||
* 启用/禁用专家: 同步 biz_expert.status + sys_user.status
|
||||
* status='Y' 正常, status='N' 禁用
|
||||
*/
|
||||
@Log(title = "专家启停", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{expertId}/status")
|
||||
public AjaxResult updateStatus(@PathVariable String expertId, @RequestParam String status)
|
||||
{
|
||||
return toAjax(bizExpertService.updateStatus(expertId, status));
|
||||
}
|
||||
/**
|
||||
* 个人专家信息更新 (走 userId 路由, 后端强制注入当前登录用户)
|
||||
* 不依赖前端传 expertId; 找不到 expert 行时自动 insert
|
||||
@@ -71,4 +93,67 @@ public class BizExpertController extends BaseController
|
||||
{
|
||||
return toAjax(bizExpertService.deleteByPrimaryKeys(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出专家列表 (中文列头)
|
||||
*/
|
||||
@Log(title = "专家", businessType = BusinessType.EXPORT)
|
||||
@PreAuthorize("@ss.hasPermi('business:expert:export')")
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, BizExpert bizExpert)
|
||||
{
|
||||
List<BizExpert> list = bizExpertService.selectList(bizExpert);
|
||||
// 转换为导出 VO (中文列头 + 审核状态/状态 可读映射)
|
||||
List<BizExpertExportVo> exportList = new java.util.ArrayList<>(list.size());
|
||||
for (BizExpert e : list) {
|
||||
BizExpertExportVo v = new BizExpertExportVo();
|
||||
v.setName(e.getName());
|
||||
v.setPhone(e.getPhone());
|
||||
v.setWorkUnit(e.getWorkUnit());
|
||||
v.setDepartment(e.getDepartment());
|
||||
v.setTitle(e.getTitle());
|
||||
v.setRegion(e.getRegion());
|
||||
v.setIdCard(e.getIdCard());
|
||||
v.setBankCard(e.getBankCard());
|
||||
v.setBankName(e.getBankName());
|
||||
v.setBankProvince(e.getBankProvince());
|
||||
v.setBankCity(e.getBankCity());
|
||||
v.setBankAddress(e.getBankAddress());
|
||||
v.setStatus(e.getStatus());
|
||||
v.setAuditStatus(e.getAuditStatus());
|
||||
v.setAuditBy(e.getAuditBy());
|
||||
v.setAuditTime(e.getAuditTime());
|
||||
v.setCreateTime(e.getCreateTime());
|
||||
exportList.add(v);
|
||||
}
|
||||
ExcelUtil<BizExpertExportVo> util = new ExcelUtil<BizExpertExportVo>(BizExpertExportVo.class);
|
||||
util.exportExcel(response, exportList, "专家数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载专家导入模板
|
||||
*/
|
||||
@GetMapping("/importTemplate")
|
||||
public void importTemplate(HttpServletResponse response)
|
||||
{
|
||||
ExcelUtil<BizExpertImportVo> util = new ExcelUtil<BizExpertImportVo>(BizExpertImportVo.class);
|
||||
util.importTemplateExcel(response, "专家数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入专家 (Excel), 参考 RuoYi 系统用户导入模式
|
||||
* updateSupport=true: 同一手机号已存在 → 跳过 (按成功计)
|
||||
* 返回 message 含成功/失败计数 + 失败明细
|
||||
*/
|
||||
@Log(title = "专家", businessType = BusinessType.IMPORT)
|
||||
@PreAuthorize("@ss.hasPermi('business:expert:import')")
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
|
||||
{
|
||||
ExcelUtil<BizExpertImportVo> util = new ExcelUtil<BizExpertImportVo>(BizExpertImportVo.class);
|
||||
List<BizExpertImportVo> importList = util.importExcel(file.getInputStream());
|
||||
String operName = SecurityUtils.getUsername();
|
||||
String message = bizExpertService.importExpert(importList, updateSupport, operName);
|
||||
return success(message);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import com.ruoyi.business.service.IBizOrgService;
|
||||
|
||||
/**
|
||||
* 公司Controller (赞助方 + 执行方 共用)
|
||||
* GET /business/org/list?orgType=sponsor|execution
|
||||
* GET /business/org/list?orgType=sponsor|executor
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/business/org")
|
||||
|
||||
+2
-2
@@ -100,8 +100,8 @@ public class BizRegisterController extends BaseController {
|
||||
expert.setTitle(doctorTitle);
|
||||
expert.setPracticeCertUrl(licenseCertUrl);
|
||||
expert.setTitleCertUrl(titleCertUrl);
|
||||
expert.setAuditStatus("0");
|
||||
expert.setStatus("0");
|
||||
expert.setAuditStatus("1"); // BizAuditStatusEnum.PENDING = 待审核
|
||||
expert.setStatus("Y"); // biz_expert.status: Y=正常 N=禁用
|
||||
expertService.insert(expert);
|
||||
|
||||
return success("注册成功, 请等待审核").put("userId", userId);
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 公司对象 biz_org
|
||||
* 用途: 赞助方(sponsor) + 执行方(execution) 共用
|
||||
* 用途: 赞助方(sponsor) + 执行方(executor) 共用
|
||||
* 重构说明: 原 biz_support_unit + biz_execution_unit 合并, 通过 org_type 区分
|
||||
*/
|
||||
public class BizOrg extends BaseEntity {
|
||||
@@ -17,7 +17,7 @@ public class BizOrg extends BaseEntity {
|
||||
/** org_name */
|
||||
@Excel(name = "org_name")
|
||||
private String orgName;
|
||||
/** org_type: sponsor赞助方 / execution执行方 */
|
||||
/** org_type: sponsor赞助方 / executor执行方 */
|
||||
@Excel(name = "org_type")
|
||||
private String orgType;
|
||||
/** 企业性质 私营/国营/中外合资/外资/其他 */
|
||||
|
||||
@@ -52,7 +52,7 @@ public class BizProject extends BaseEntity {
|
||||
/** org_name (赞助方/执行方, 由 org_type 区分) */
|
||||
@Excel(name = "org_name")
|
||||
private String orgName;
|
||||
/** org_type: sponsor 赞助方 / execution 执行方 */
|
||||
/** org_type: sponsor 赞助方 / executor 执行方 */
|
||||
@Excel(name = "org_type")
|
||||
private String orgType;
|
||||
/** project_form */
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package com.ruoyi.business.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 专家导出 VO (中文列头, 用户友好)
|
||||
*
|
||||
* <p>仅用于 Excel 导出, 不参与业务逻辑.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizExpertExportVo {
|
||||
|
||||
@Excel(name = "姓名", sort = 1)
|
||||
private String name;
|
||||
|
||||
@Excel(name = "手机号", sort = 2)
|
||||
private String phone;
|
||||
|
||||
@Excel(name = "工作单位", sort = 3)
|
||||
private String workUnit;
|
||||
|
||||
@Excel(name = "科室", sort = 4)
|
||||
private String department;
|
||||
|
||||
@Excel(name = "职称", sort = 5)
|
||||
private String title;
|
||||
|
||||
@Excel(name = "地区", sort = 6)
|
||||
private String region;
|
||||
|
||||
@Excel(name = "身份证件号码", sort = 7)
|
||||
private String idCard;
|
||||
|
||||
@Excel(name = "银行卡号", sort = 8)
|
||||
private String bankCard;
|
||||
|
||||
@Excel(name = "银行名称", sort = 9)
|
||||
private String bankName;
|
||||
|
||||
@Excel(name = "开户行省份", sort = 10)
|
||||
private String bankProvince;
|
||||
|
||||
@Excel(name = "开户行城市", sort = 11)
|
||||
private String bankCity;
|
||||
|
||||
@Excel(name = "开户行地址", sort = 12)
|
||||
private String bankAddress;
|
||||
|
||||
@Excel(name = "状态", sort = 13, readConverterExp = "Y=正常,N=禁用")
|
||||
private String status;
|
||||
|
||||
@Excel(name = "审核状态", sort = 14, readConverterExp = "0=未提交,1=待审核,2=通过,3=拒绝")
|
||||
private String auditStatus;
|
||||
|
||||
@Excel(name = "审核人", sort = 15)
|
||||
private String auditBy;
|
||||
|
||||
@Excel(name = "审核时间", sort = 16, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date auditTime;
|
||||
|
||||
@Excel(name = "创建时间", sort = 17, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getWorkUnit() { return workUnit; }
|
||||
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
|
||||
public String getDepartment() { return department; }
|
||||
public void setDepartment(String department) { this.department = department; }
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
public String getRegion() { return region; }
|
||||
public void setRegion(String region) { this.region = region; }
|
||||
public String getIdCard() { return idCard; }
|
||||
public void setIdCard(String idCard) { this.idCard = idCard; }
|
||||
public String getBankCard() { return bankCard; }
|
||||
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
|
||||
public String getBankName() { return bankName; }
|
||||
public void setBankName(String bankName) { this.bankName = bankName; }
|
||||
public String getBankProvince() { return bankProvince; }
|
||||
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
|
||||
public String getBankCity() { return bankCity; }
|
||||
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
|
||||
public String getBankAddress() { return bankAddress; }
|
||||
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public String getAuditStatus() { return auditStatus; }
|
||||
public void setAuditStatus(String auditStatus) { this.auditStatus = auditStatus; }
|
||||
public String getAuditBy() { return auditBy; }
|
||||
public void setAuditBy(String auditBy) { this.auditBy = auditBy; }
|
||||
public Date getAuditTime() { return auditTime; }
|
||||
public void setAuditTime(Date auditTime) { this.auditTime = auditTime; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.ruoyi.business.domain.vo;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 专家导入 VO
|
||||
*
|
||||
* <p>仅用于 Excel 批量导入 / 模板下载 / 导出, 不参与业务逻辑.
|
||||
* 字段顺序与 Excel 列一致, 调整时同步修改模板下载体验.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizExpertImportVo {
|
||||
|
||||
/** 姓名 */
|
||||
@Excel(name = "姓名", sort = 1)
|
||||
private String name;
|
||||
|
||||
/** 手机号 */
|
||||
@Excel(name = "手机号", sort = 2)
|
||||
private String phone;
|
||||
|
||||
/** 工作单位 */
|
||||
@Excel(name = "工作单位", sort = 3)
|
||||
private String workUnit;
|
||||
|
||||
/** 科室 */
|
||||
@Excel(name = "科室", sort = 4)
|
||||
private String department;
|
||||
|
||||
/** 职称 */
|
||||
@Excel(name = "职称", sort = 5)
|
||||
private String title;
|
||||
|
||||
/** 地区 */
|
||||
@Excel(name = "地区", sort = 6)
|
||||
private String region;
|
||||
|
||||
/** 身份证件号码 */
|
||||
@Excel(name = "身份证件号码", sort = 7)
|
||||
private String idCard;
|
||||
|
||||
/** 身份证件照片 正面URL */
|
||||
@Excel(name = "身份证正面URL", sort = 8)
|
||||
private String idCardFrontUrl;
|
||||
|
||||
/** 身份证件照片 反面URL */
|
||||
@Excel(name = "身份证反面URL", sort = 9)
|
||||
private String idCardBackUrl;
|
||||
|
||||
/** 执业医师证书 URL */
|
||||
@Excel(name = "执业医师证书URL", sort = 10)
|
||||
private String practiceCertUrl;
|
||||
|
||||
/** 职称证明 URL */
|
||||
@Excel(name = "职称证明URL", sort = 11)
|
||||
private String titleCertUrl;
|
||||
|
||||
/** 银行卡号 */
|
||||
@Excel(name = "银行卡号", sort = 12)
|
||||
private String bankCard;
|
||||
|
||||
/** 银行名称 */
|
||||
@Excel(name = "银行名称", sort = 13)
|
||||
private String bankName;
|
||||
|
||||
/** 开户行省份 */
|
||||
@Excel(name = "开户行省份", sort = 14)
|
||||
private String bankProvince;
|
||||
|
||||
/** 开户行城市 */
|
||||
@Excel(name = "开户行城市", sort = 15)
|
||||
private String bankCity;
|
||||
|
||||
/** 开户行地址 */
|
||||
@Excel(name = "开户行地址", sort = 16)
|
||||
private String bankAddress;
|
||||
|
||||
/** 状态 (Y=正常 N=禁用, 留空默认 Y) */
|
||||
@Excel(name = "状态", sort = 17, readConverterExp = "Y=正常,N=禁用")
|
||||
private String status;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getWorkUnit() { return workUnit; }
|
||||
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
|
||||
public String getDepartment() { return department; }
|
||||
public void setDepartment(String department) { this.department = department; }
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
public String getRegion() { return region; }
|
||||
public void setRegion(String region) { this.region = region; }
|
||||
public String getIdCard() { return idCard; }
|
||||
public void setIdCard(String idCard) { this.idCard = idCard; }
|
||||
public String getIdCardFrontUrl() { return idCardFrontUrl; }
|
||||
public void setIdCardFrontUrl(String idCardFrontUrl) { this.idCardFrontUrl = idCardFrontUrl; }
|
||||
public String getIdCardBackUrl() { return idCardBackUrl; }
|
||||
public void setIdCardBackUrl(String idCardBackUrl) { this.idCardBackUrl = idCardBackUrl; }
|
||||
public String getPracticeCertUrl() { return practiceCertUrl; }
|
||||
public void setPracticeCertUrl(String practiceCertUrl) { this.practiceCertUrl = practiceCertUrl; }
|
||||
public String getTitleCertUrl() { return titleCertUrl; }
|
||||
public void setTitleCertUrl(String titleCertUrl) { this.titleCertUrl = titleCertUrl; }
|
||||
public String getBankCard() { return bankCard; }
|
||||
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
|
||||
public String getBankName() { return bankName; }
|
||||
public void setBankName(String bankName) { this.bankName = bankName; }
|
||||
public String getBankProvince() { return bankProvince; }
|
||||
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
|
||||
public String getBankCity() { return bankCity; }
|
||||
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
|
||||
public String getBankAddress() { return bankAddress; }
|
||||
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
}
|
||||
+19
-1
@@ -2,6 +2,8 @@ package com.ruoyi.business.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.vo.BizExpertImportVo;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
|
||||
/**
|
||||
* 专家Service接口
|
||||
@@ -11,10 +13,26 @@ public interface IBizExpertService
|
||||
BizExpert getById(String expertId);
|
||||
BizExpert getByUserId(Long userId);
|
||||
List<BizExpert> selectList(BizExpert entity);
|
||||
int insert(BizExpert entity);
|
||||
/**
|
||||
* 新建专家: 同时创建 sys_user (用户名=手机号, 密码=手机号, role_type=doctor)
|
||||
* 若 sys_user.username(=phone) 已存在, 抛 ServiceException("该手机号已注册")
|
||||
* 返回 SysUser 含 username + 明文 password (仅本次返回,前端 toast 显示后即丢)
|
||||
*/
|
||||
SysUser insert(BizExpert entity);
|
||||
int updateByPrimaryKey(BizExpert entity);
|
||||
/**
|
||||
* 启用/禁用专家: 同步更新 biz_expert.status + sys_user.status
|
||||
* status='Y' 正常, status='N' 禁用
|
||||
*/
|
||||
int updateStatus(String expertId, String status);
|
||||
/** 按 userId 更新或新建 (upsert) */
|
||||
int updateProfileByUserId(BizExpert entity);
|
||||
int deleteByPrimaryKey(String expertId);
|
||||
int deleteByPrimaryKeys(String[] expertId);
|
||||
|
||||
/**
|
||||
* 批量导入专家: 每行调用 insert, updateSupport=true 时跳过已存在手机号(视为成功)
|
||||
* 返回 String 含成功/失败计数 + 失败明细 (前端 toast 显示)
|
||||
*/
|
||||
String importExpert(List<BizExpertImportVo> importList, boolean updateSupport, String operName);
|
||||
}
|
||||
|
||||
+187
-1
@@ -4,8 +4,13 @@ import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.vo.BizExpertImportVo;
|
||||
import com.ruoyi.business.mapper.BizExpertMapper;
|
||||
import com.ruoyi.business.service.IBizExpertService;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
|
||||
@Service
|
||||
public class BizExpertServiceImpl implements IBizExpertService
|
||||
@@ -13,6 +18,9 @@ public class BizExpertServiceImpl implements IBizExpertService
|
||||
@Autowired
|
||||
private BizExpertMapper bizExpertMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Override
|
||||
public BizExpert getById(String expertId)
|
||||
{ return bizExpertMapper.selectByPrimaryKey(expertId); }
|
||||
@@ -22,11 +30,86 @@ public class BizExpertServiceImpl implements IBizExpertService
|
||||
@Override
|
||||
public List<BizExpert> selectList(BizExpert entity)
|
||||
{ return bizExpertMapper.selectList(entity); }
|
||||
|
||||
/**
|
||||
* admin 创建专家: 同时创建 sys_user (用户名=手机号, 密码=手机号, role_type=doctor)
|
||||
* 1. 校验 phone 没注册过 (抛 ServiceException)
|
||||
* 2. 创建 sys_user + bcrypt 加密密码
|
||||
* 3. 设置 role_type=doctor (与公开注册一致)
|
||||
* 4. 创建 biz_expert 绑定 user_id
|
||||
* 5. 返回 SysUser 含明文 password (前端 toast 用完即丢)
|
||||
*/
|
||||
@Override
|
||||
public int insert(BizExpert entity) { com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "expertId"); return bizExpertMapper.insert(entity); }
|
||||
public SysUser insert(BizExpert entity) {
|
||||
String phone = entity.getPhone();
|
||||
if (phone == null || phone.isEmpty()) {
|
||||
throw new ServiceException("手机号不能为空");
|
||||
}
|
||||
// 0. 校验 phone 唯一 (查 sys_user, 若 username=phone 已存在即重复)
|
||||
if (sysUserService.isPhoneRegistered(phone)) {
|
||||
throw new ServiceException("该手机号已注册,请直接登录");
|
||||
}
|
||||
|
||||
// 1. 创建 sys_user (用户名=phone, 密码=phone)
|
||||
SysUser newUser = new SysUser();
|
||||
newUser.setUserName(phone);
|
||||
newUser.setNickName(entity.getName());
|
||||
newUser.setPhonenumber(phone);
|
||||
newUser.setPassword(SecurityUtils.encryptPassword(phone));
|
||||
newUser.setStatus("0");
|
||||
newUser.setDelFlag("0");
|
||||
newUser.setCreateBy(SecurityUtils.getUsername());
|
||||
sysUserService.insertUser(newUser);
|
||||
Long userId = newUser.getUserId();
|
||||
|
||||
// 2. role_type = doctor (跟公开注册一致,DB 默认 executor, 专家需 doctor)
|
||||
sysUserService.updateRoleType(userId, "doctor");
|
||||
|
||||
// 3. 创建 biz_expert 绑定 user_id
|
||||
entity.setUserId(userId);
|
||||
com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "expertId");
|
||||
bizExpertMapper.insert(entity);
|
||||
|
||||
// 4. 把明文密码回填 SysUser (仅本次返回,前端 toast 显示)
|
||||
newUser.setPassword(phone);
|
||||
return newUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateByPrimaryKey(BizExpert entity)
|
||||
{ return bizExpertMapper.updateByPrimaryKey(entity); }
|
||||
|
||||
/**
|
||||
* 启用/禁用专家: 同步 biz_expert.status + sys_user.status
|
||||
* biz_expert.status: 'Y'=正常 'N'=禁用
|
||||
* sys_user.status: '0'=正常 '1'=停用 (RuoYi 框架约定, 同步时转换)
|
||||
*/
|
||||
@Override
|
||||
public int updateStatus(String expertId, String status) {
|
||||
if (status == null || (!"Y".equals(status) && !"N".equals(status))) {
|
||||
throw new ServiceException("status 必须是 'Y'(正常) 或 'N'(禁用)");
|
||||
}
|
||||
BizExpert existed = bizExpertMapper.selectByPrimaryKey(expertId);
|
||||
if (existed == null) {
|
||||
throw new ServiceException("专家不存在");
|
||||
}
|
||||
// 1. 更新 biz_expert.status
|
||||
BizExpert update = new BizExpert();
|
||||
update.setExpertId(expertId);
|
||||
update.setStatus(status);
|
||||
update.setUpdateBy(SecurityUtils.getUsername());
|
||||
int n = bizExpertMapper.updateByPrimaryKey(update);
|
||||
// 2. 同步 sys_user.status (登录账号启停, 'Y'→'0', 'N'→'1')
|
||||
if (n > 0 && existed.getUserId() != null) {
|
||||
SysUser u = new SysUser();
|
||||
u.setUserId(existed.getUserId());
|
||||
u.setStatus("Y".equals(status) ? "0" : "1");
|
||||
u.setUpdateBy(SecurityUtils.getUsername());
|
||||
sysUserService.updateUser(u);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateProfileByUserId(BizExpert entity)
|
||||
{
|
||||
@@ -42,4 +125,107 @@ public class BizExpertServiceImpl implements IBizExpertService
|
||||
@Override
|
||||
public int deleteByPrimaryKeys(String[] expertId)
|
||||
{ return bizExpertMapper.deleteByPrimaryKeys(expertId); }
|
||||
|
||||
/**
|
||||
* 批量导入专家:
|
||||
* 1. 每行 必填校验 (姓名/手机号/工作单位/科室/职称, 手机号正则 1[0-9]{10})
|
||||
* 2. status 留空 → Y (正常); 非法值 → 抛错
|
||||
* 3. sys_user 同步创建 (与单条 insert 共用 sys_userService.insertUser)
|
||||
* 4. updateSupport=true 时: 同一手机号已存在 → 跳过 (按成功计)
|
||||
* 5. 单行失败不中断, 错误信息累计
|
||||
*/
|
||||
@Override
|
||||
public String importExpert(List<BizExpertImportVo> importList, boolean updateSupport, String operName) {
|
||||
if (importList == null || importList.isEmpty()) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
}
|
||||
int successNum = 0;
|
||||
int failureNum = 0;
|
||||
StringBuilder successMsg = new StringBuilder();
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < importList.size(); i++) {
|
||||
BizExpertImportVo vo = importList.get(i);
|
||||
int rowNo = i + 2; // Excel 行号 (1=表头)
|
||||
try {
|
||||
// 必填校验
|
||||
if (vo.getName() == null || vo.getName().trim().isEmpty()) {
|
||||
throw new ServiceException("姓名不能为空");
|
||||
}
|
||||
if (vo.getPhone() == null || vo.getPhone().trim().isEmpty()) {
|
||||
throw new ServiceException("手机号不能为空");
|
||||
}
|
||||
if (!vo.getPhone().matches("^1[0-9]\\d{9}$")) {
|
||||
throw new ServiceException("手机号格式不正确");
|
||||
}
|
||||
if (vo.getWorkUnit() == null || vo.getWorkUnit().trim().isEmpty()) {
|
||||
throw new ServiceException("工作单位不能为空");
|
||||
}
|
||||
if (vo.getDepartment() == null || vo.getDepartment().trim().isEmpty()) {
|
||||
throw new ServiceException("科室不能为空");
|
||||
}
|
||||
if (vo.getTitle() == null || vo.getTitle().trim().isEmpty()) {
|
||||
throw new ServiceException("职称不能为空");
|
||||
}
|
||||
// status 校验, 留空 → Y
|
||||
String status = vo.getStatus();
|
||||
if (status == null || status.trim().isEmpty()) {
|
||||
status = "Y";
|
||||
} else if (!"Y".equals(status) && !"N".equals(status)) {
|
||||
throw new ServiceException("状态只能是 Y(正常) 或 N(禁用)");
|
||||
}
|
||||
|
||||
// 同一手机号已存在 → updateSupport=true 跳过, 否则报错
|
||||
if (sysUserService.isPhoneRegistered(vo.getPhone())) {
|
||||
if (updateSupport) {
|
||||
successNum++;
|
||||
successMsg.append("<br/>" + successNum + "、手机号 " + vo.getPhone() + " 已存在, 已跳过");
|
||||
continue;
|
||||
}
|
||||
throw new ServiceException("手机号 " + vo.getPhone() + " 已存在");
|
||||
}
|
||||
|
||||
// 复用单条 insert 逻辑 (含 sys_user 创建)
|
||||
BizExpert entity = new BizExpert();
|
||||
entity.setName(vo.getName());
|
||||
entity.setPhone(vo.getPhone());
|
||||
entity.setWorkUnit(vo.getWorkUnit());
|
||||
entity.setDepartment(vo.getDepartment());
|
||||
entity.setTitle(vo.getTitle());
|
||||
entity.setRegion(vo.getRegion());
|
||||
entity.setIdCard(vo.getIdCard());
|
||||
entity.setIdCardFrontUrl(vo.getIdCardFrontUrl());
|
||||
entity.setIdCardBackUrl(vo.getIdCardBackUrl());
|
||||
entity.setPracticeCertUrl(vo.getPracticeCertUrl());
|
||||
entity.setTitleCertUrl(vo.getTitleCertUrl());
|
||||
entity.setBankCard(vo.getBankCard());
|
||||
entity.setBankName(vo.getBankName());
|
||||
entity.setBankProvince(vo.getBankProvince());
|
||||
entity.setBankCity(vo.getBankCity());
|
||||
entity.setBankAddress(vo.getBankAddress());
|
||||
entity.setStatus(status);
|
||||
// admin 批量导入默认通过审核 (与单条 admin 创建一致)
|
||||
entity.setAuditStatus("2");
|
||||
entity.setAuditBy(operName);
|
||||
entity.setAuditTime(new java.util.Date());
|
||||
entity.setCreateBy(operName);
|
||||
|
||||
insert(entity);
|
||||
successNum++;
|
||||
successMsg.append("<br/>" + successNum + "、账号 " + vo.getPhone() + " 导入成功");
|
||||
} catch (Exception e) {
|
||||
failureNum++;
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) msg = e.getClass().getSimpleName();
|
||||
failureMsg.append("<br/>" + failureNum + "、第 " + rowNo + " 行: " + msg);
|
||||
}
|
||||
}
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉, 部分导入失败, 共 " + failureNum + " 条:");
|
||||
}
|
||||
if (successNum > 0) {
|
||||
successMsg.insert(0, "恭喜您, 数据已全部导入成功!共 " + successNum + " 条, 数据如下:");
|
||||
}
|
||||
return successMsg.toString() + failureMsg.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
<sql id="selectFields">
|
||||
select expert_id, user_id, name, phone, region, id_card, work_unit, department, title, practice_cert_url, title_cert_url, bank_card, bank_name, bank_province, bank_city, bank_address, id_card_front_url, id_card_back_url, audit_status, audit_by, audit_time, create_by, create_time, update_by, update_time
|
||||
select expert_id, user_id, name, phone, region, id_card, work_unit, department, title, practice_cert_url, title_cert_url, bank_card, bank_name, bank_province, bank_city, bank_address, id_card_front_url, id_card_back_url, audit_status, audit_by, audit_time, status, create_by, create_time, update_by, update_time
|
||||
from biz_expert
|
||||
</sql>
|
||||
<select id="selectByUserId" resultMap="BizExpertResult" parameterType="Long">
|
||||
@@ -197,7 +197,7 @@
|
||||
<if test="auditStatus != null and auditStatus != ''">audit_status = #{auditStatus},</if>
|
||||
<if test="auditOpinion != null and auditOpinion != ''">audit_opinion = #{auditOpinion},</if>
|
||||
<if test="auditBy != null and auditBy != ''">audit_by = #{auditBy},</if>
|
||||
<if test="auditTime != null and auditTime != ''">audit_time = #{auditTime},</if>
|
||||
<if test="auditTime != null">audit_time = #{auditTime},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="phone != null">phone = #{phone},</if>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.ruoyi.common.enums;
|
||||
|
||||
/**
|
||||
* 业务审核状态
|
||||
* <p>
|
||||
* 通用枚举: 0=未提交, 1=待审核, 2=通过, 3=拒绝
|
||||
* <p>
|
||||
* 适用于 biz_expert.audit_status, biz_project.audit_status 等业务审核场景.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public enum BizAuditStatusEnum
|
||||
{
|
||||
/** 草稿/未提交 (用户保存但未提交审核) */
|
||||
UNSUBMITTED("0", "未提交"),
|
||||
|
||||
/** 已提交, 等待管理员审核 */
|
||||
PENDING("1", "待审核"),
|
||||
|
||||
/** 审核通过 */
|
||||
APPROVED("2", "通过"),
|
||||
|
||||
/** 审核拒绝 */
|
||||
REJECTED("3", "拒绝");
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
BizAuditStatusEnum(String code, String info)
|
||||
{
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 code 解析枚举 (找不到返回 null, 调用方自行 fallback)
|
||||
*/
|
||||
public static BizAuditStatusEnum of(String code)
|
||||
{
|
||||
if (code == null) return null;
|
||||
for (BizAuditStatusEnum e : values())
|
||||
{
|
||||
if (e.code.equals(code)) return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.system.domain.vo;
|
||||
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
|
||||
/**
|
||||
* 用户扩展视图对象
|
||||
* <p>
|
||||
* 继承自 SysUser, 仅在 list 场景下附加业务字段 (sys_user.role_type),
|
||||
* 不影响 SysUser 实体本身, 不参与新增/修改/详情接口。
|
||||
* </p>
|
||||
*/
|
||||
public class SysUserExtendVo extends SysUser
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 业务角色 (admin / leader / manager / doctor / executor / sponsor) */
|
||||
private String roleType;
|
||||
|
||||
public String getRoleType()
|
||||
{
|
||||
return roleType;
|
||||
}
|
||||
|
||||
public void setRoleType(String roleType)
|
||||
{
|
||||
this.roleType = roleType;
|
||||
}
|
||||
}
|
||||
@@ -91,3 +91,18 @@ export const bizGet = (entity, id) => request.get(`/business/${entity}/${id}`)
|
||||
export const bizAdd = (entity, data) => request.post(`/business/${entity}`, data)
|
||||
export const bizUpdate = (entity, data) => request.put(`/business/${entity}`, data)
|
||||
export const bizDelete = (entity, ids) => request.delete(`/business/${entity}/${ids}`)
|
||||
|
||||
// 人员批量导入 (unitType: 'sponsor' | 'executor')
|
||||
export const importPerson = (unitType, file) => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
return request.post(`/business/person/${unitType}Import`, form, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
// 人员导入模板下载
|
||||
export const downloadImportTemplate = (unitType) =>
|
||||
request.get(`/business/person/${unitType}ImportTemplate`, { responseType: 'blob' })
|
||||
// 专家导入模板下载
|
||||
export const downloadExpertTemplate = () =>
|
||||
request.get('/business/expert/importTemplate', { responseType: 'blob' })
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<!--
|
||||
业务审核状态标签
|
||||
通用封装, 后端 BizAuditStatusEnum 对应
|
||||
0=未提交 1=待审核 2=通过 3=拒绝
|
||||
-->
|
||||
<el-tag
|
||||
v-if="resolved"
|
||||
:type="resolved.type"
|
||||
:effect="effect"
|
||||
:size="size"
|
||||
>{{ resolved.text }}</el-tag>
|
||||
<span v-else style="color:#c0c4cc">{{ emptyText }}</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
/**
|
||||
* 审核状态标签
|
||||
*
|
||||
* 用法:
|
||||
* <audit-status-tag :status="row.auditStatus" />
|
||||
* <audit-status-tag :status="row.auditStatus" size="small" effect="dark" />
|
||||
*
|
||||
* 兼容: 若传入旧值 ('审核通过'/'已退回'/'已驳回'/'待审核' 等中文), 自动归一化到新枚举
|
||||
*/
|
||||
const props = defineProps({
|
||||
status: { type: [String, Number, null], default: null },
|
||||
size: { type: String, default: 'small' }, // small / default / large
|
||||
effect: { type: String, default: 'light' }, // light / dark / plain
|
||||
emptyText: { type: String, default: '-' },
|
||||
})
|
||||
|
||||
// 映射表: 后端 BizAuditStatusEnum 一致
|
||||
const MAP = {
|
||||
'0': { text: '未提交', type: 'info' },
|
||||
'1': { text: '待审核', type: 'warning' },
|
||||
'2': { text: '通过', type: 'success' },
|
||||
'3': { text: '拒绝', type: 'danger' },
|
||||
}
|
||||
|
||||
// 旧值兼容 (历史脏数据 / 老代码)
|
||||
const LEGACY = {
|
||||
'待审核': '1',
|
||||
'审核通过': '2',
|
||||
'通过': '2',
|
||||
'已退回': '3',
|
||||
'已驳回': '3',
|
||||
'退回': '3',
|
||||
'驳回': '3',
|
||||
'拒绝': '3',
|
||||
}
|
||||
|
||||
const resolved = computed(() => {
|
||||
let s = props.status
|
||||
if (s == null || s === '') return null
|
||||
s = String(s).trim()
|
||||
if (MAP[s]) return MAP[s]
|
||||
if (LEGACY[s]) return MAP[LEGACY[s]]
|
||||
return null
|
||||
})
|
||||
</script>
|
||||
@@ -7,17 +7,18 @@
|
||||
placeholder="证书"
|
||||
/>
|
||||
|
||||
block=true 时撑满父容器宽度 (用于表单行内全宽上传), 字体缩小到 14px
|
||||
readonly=true 时不触发上传, 点击缩略图弹 el-image 预览
|
||||
上传走 OSS 直传 (utils/oss.js 的 uploadToOss)
|
||||
-->
|
||||
<template>
|
||||
<div class="ht-image-upload-content" :class="{ 'has-file': modelValue }" @click="handleClick">
|
||||
<div class="ht-image-upload-content" :class="{ 'has-file': modelValue, 'is-block': block }" @click="handleClick">
|
||||
<div v-if="!modelValue && !readonly" class="ht-image-placeholder-wrap">
|
||||
<span class="placeholder-text">{{ placeholder }}</span>
|
||||
</div>
|
||||
<div v-else-if="!modelValue && readonly" style="width: 90px;height: 60px; background: #f2f4f5;"></div>
|
||||
<div v-else-if="!modelValue && readonly" :style="readonlyBoxStyle"></div>
|
||||
<img v-else-if="!readonly" class="ht-image" :src="modelValue" />
|
||||
<el-image v-else :src="modelValue" style="width: 90px;height: 60px; display: flex; justify-content: center; align-items: center; background: #f2f4f5;"
|
||||
<el-image v-else :src="modelValue" :style="readonlyBoxStyle"
|
||||
:preview-src-list="[modelValue]"></el-image>
|
||||
</div>
|
||||
<input
|
||||
@@ -30,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
|
||||
@@ -38,7 +39,9 @@ const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
dir: { type: String, default: 'ry8080/image/' },
|
||||
placeholder: { type: String, default: '点击上传' },
|
||||
readonly: { type: Boolean, default: false }
|
||||
readonly: { type: Boolean, default: false },
|
||||
/** true 时撑满父容器宽度, 用于表单行内全宽上传 */
|
||||
block: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
@@ -46,6 +49,13 @@ const emit = defineEmits(['update:modelValue'])
|
||||
const fileInput = ref(null)
|
||||
const uploading = ref(false)
|
||||
|
||||
const readonlyBoxStyle = computed(() => {
|
||||
if (props.block) {
|
||||
return 'width: 100%; height: 100px; background: #f2f4f5; display: flex; justify-content: center; align-items: center;'
|
||||
}
|
||||
return 'width: 90px; height: 60px; background: #f2f4f5; display: flex; justify-content: center; align-items: center;'
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
if (props.readonly || uploading.value) return
|
||||
fileInput.value?.click()
|
||||
@@ -90,6 +100,10 @@ async function onFileChange(e) {
|
||||
cursor: pointer;
|
||||
background: #fafafa;
|
||||
}
|
||||
.ht-image-upload-content.is-block {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
.ht-image-upload-content:hover {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
@@ -105,11 +119,16 @@ async function onFileChange(e) {
|
||||
justify-content: center;
|
||||
}
|
||||
.placeholder-text {
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #8c8c8c;
|
||||
line-height: 1;
|
||||
}
|
||||
.ht-image-upload-content.is-block .placeholder-text {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
.ht-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -61,7 +61,8 @@ const MENU = {
|
||||
{ path: '/admin/department', title: '科室管理', icon: Folder },
|
||||
{ path: '/admin/title', title: '职称管理', icon: Medal },
|
||||
{ path: '/admin/experts', title: '专家管理', icon: UserFilled },
|
||||
{ path: '/admin/orgs', title: '公司管理', icon: Connection },
|
||||
{ path: '/admin/sponsor-orgs', title: '支持单位管理', icon: Connection },
|
||||
{ path: '/admin/executor-orgs', title: '服务机构管理', icon: OfficeBuilding },
|
||||
{ path: '/admin/account', title: '账号信息', icon: User }
|
||||
],
|
||||
leader: [
|
||||
@@ -76,7 +77,8 @@ const MENU = {
|
||||
{ path: '/manager/projects', title: '项目管理', icon: Document },
|
||||
{ path: '/manager/meetings', title: '会议管理', icon: Calendar },
|
||||
{ path: '/manager/experts', title: '专家审核', icon: User },
|
||||
{ path: '/manager/orgs', title: '公司管理', icon: OfficeBuilding },
|
||||
{ path: '/manager/sponsor-orgs', title: '支持单位管理', icon: Connection },
|
||||
{ path: '/manager/executor-orgs', title: '服务机构管理', icon: OfficeBuilding },
|
||||
{ path: '/manager/support-intent', title: '支持意向', icon: Tickets },
|
||||
{ path: '/manager/exec-intent', title: '执行意向', icon: Tickets },
|
||||
{ path: '/manager/accounts', title: '账号管理', icon: Setting }
|
||||
|
||||
@@ -28,6 +28,17 @@ const routes = [
|
||||
{ path: 'department', name: 'admin-department', component: () => import('@/views/admin/Department.vue'), meta: { title: '科室管理' } },
|
||||
{ path: 'title', name: 'admin-title', component: () => import('@/views/admin/Title.vue'), meta: { title: '职称管理' } },
|
||||
{ path: 'experts', name: 'admin-experts', component: () => import('@/views/admin/Experts.vue'), meta: { title: '专家管理' } },
|
||||
{ path: 'experts/new', name: 'admin-experts-new', component: () => import('@/views/admin/ExpertNew.vue'), meta: { title: '新建专家' } },
|
||||
{ path: 'experts/edit/:id', name: 'admin-experts-edit', component: () => import('@/views/admin/ExpertNew.vue'), meta: { title: '编辑专家' } },
|
||||
{ path: 'experts/view/:id', name: 'admin-experts-view', component: () => import('@/views/admin/ExpertDetail.vue'), meta: { title: '专家详情' } },
|
||||
{ path: 'sponsor-orgs', name: 'admin-sponsor-orgs', component: () => import('@/views/admin/SponsorOrgs.vue'), meta: { title: '支持单位管理' } },
|
||||
{ path: 'executor-orgs', name: 'admin-executor-orgs', component: () => import('@/views/admin/ExecutorOrgs.vue'), meta: { title: '服务机构管理' } },
|
||||
{ path: 'sponsor-people', name: 'admin-sponsor-people', component: () => import('@/views/admin/SponsorPeople.vue'), meta: { title: '支持单位下人员' } },
|
||||
{ path: 'sponsor-people/new', name: 'admin-sponsor-people-new', component: () => import('@/views/admin/SponsorPersonNew.vue'), meta: { title: '新建人员' } },
|
||||
{ path: 'sponsor-people/edit/:id', name: 'admin-sponsor-people-edit', component: () => import('@/views/admin/SponsorPersonNew.vue'), meta: { title: '编辑人员' } },
|
||||
{ path: 'executor-people', name: 'admin-executor-people', component: () => import('@/views/admin/ExecutorPeople.vue'), meta: { title: '服务机构下人员' } },
|
||||
{ path: 'executor-people/new', name: 'admin-executor-people-new', component: () => import('@/views/admin/ExecutorPersonNew.vue'), meta: { title: '新建人员' } },
|
||||
{ path: 'executor-people/edit/:id', name: 'admin-executor-people-edit', component: () => import('@/views/admin/ExecutorPersonNew.vue'), meta: { title: '编辑人员' } },
|
||||
{ path: 'orgs', name: 'admin-orgs', component: () => import('@/views/admin/Orgs.vue'), meta: { title: '公司管理' } },
|
||||
{ path: 'account', name: 'admin-account', component: () => import('@/views/admin/Account.vue'), meta: { title: '账号信息' } }
|
||||
]
|
||||
@@ -51,6 +62,14 @@ const routes = [
|
||||
{ path: 'meetings', name: 'manager-meetings', component: () => import('@/views/manager/Meetings.vue'), meta: { title: '会议管理' } },
|
||||
{ path: 'meetings/new', name: 'manager-meetings-new', component: () => import('@/views/manager/MeetingNew.vue'), meta: { title: '新建会议' } },
|
||||
{ path: 'experts', name: 'manager-experts', component: () => import('@/views/manager/Experts.vue'), meta: { title: '专家审核' } },
|
||||
{ path: 'sponsor-orgs', name: 'manager-sponsor-orgs', component: () => import('@/views/manager/SponsorOrgs.vue'), meta: { title: '支持单位管理' } },
|
||||
{ path: 'executor-orgs', name: 'manager-executor-orgs', component: () => import('@/views/manager/ExecutorOrgs.vue'), meta: { title: '服务机构管理' } },
|
||||
{ path: 'sponsor-people', name: 'manager-sponsor-people', component: () => import('@/views/manager/SponsorPeople.vue'), meta: { title: '支持单位下人员' } },
|
||||
{ path: 'sponsor-people/new', name: 'manager-sponsor-people-new', component: () => import('@/views/manager/SponsorPersonNew.vue'), meta: { title: '新建人员' } },
|
||||
{ path: 'sponsor-people/edit/:id', name: 'manager-sponsor-people-edit', component: () => import('@/views/manager/SponsorPersonNew.vue'), meta: { title: '编辑人员' } },
|
||||
{ path: 'executor-people', name: 'manager-executor-people', component: () => import('@/views/manager/ExecutorPeople.vue'), meta: { title: '服务机构下人员' } },
|
||||
{ path: 'executor-people/new', name: 'manager-executor-people-new', component: () => import('@/views/manager/ExecutorPersonNew.vue'), meta: { title: '新建人员' } },
|
||||
{ path: 'executor-people/edit/:id', name: 'manager-executor-people-edit', component: () => import('@/views/manager/ExecutorPersonNew.vue'), meta: { title: '编辑人员' } },
|
||||
{ path: 'orgs', name: 'manager-orgs', component: () => import('@/views/manager/Orgs.vue'), meta: { title: '公司管理' } },
|
||||
{ path: 'support-intent', name: 'manager-support-intent', component: () => import('@/views/manager/SupportIntent.vue'), meta: { title: '支持意向' } },
|
||||
{ path: 'exec-intent', name: 'manager-exec-intent', component: () => import('@/views/manager/ExecIntent.vue'), meta: { title: '执行意向' } },
|
||||
@@ -76,6 +95,8 @@ const routes = [
|
||||
{ path: 'meetings', name: 'executor-meetings', component: () => import('@/views/executor/Meetings.vue'), meta: { title: '会议执行' } },
|
||||
{ path: 'projects', name: 'executor-projects', component: () => import('@/views/executor/Projects.vue'), meta: { title: '项目列表' } },
|
||||
{ path: 'people', name: 'executor-people', component: () => import('@/views/executor/People.vue'), meta: { title: '人员管理' } },
|
||||
{ path: 'people/new', name: 'executor-people-new', component: () => import('@/views/executor/NewPerson.vue'), meta: { title: '新建人员' } },
|
||||
{ path: 'people/edit/:id', name: 'executor-people-edit', component: () => import('@/views/executor/NewPerson.vue'), meta: { title: '编辑人员' } },
|
||||
{ path: 'account', name: 'executor-account', component: () => import('@/views/executor/Account.vue'), meta: { title: '账号信息' } },
|
||||
{ path: 'labor', name: 'executor-labor', component: () => import('@/views/executor/Labor.vue'), meta: { title: '劳务凭证' } }
|
||||
]
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
<template>
|
||||
<div class="admin-account">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看当前登录账号、修改个人信息与密码</div>
|
||||
<div class="page-card admin-account">
|
||||
<div class="breadcrumb">首页 / 账号信息</div>
|
||||
|
||||
<el-descriptions :column="2" border style="margin-bottom: 24px">
|
||||
<el-descriptions-item label="账号">{{ profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ profile.nickName || profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ roleLabel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ profile.phonenumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ profile.email || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最近登录">{{ profile.loginDate || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions :column="2" border style="margin-bottom: 24px">
|
||||
<el-descriptions-item label="账号">{{ profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ profile.nickName || profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ roleLabel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ profile.phonenumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ profile.email || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最近登录">{{ profile.loginDate || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-tabs v-model="tab">
|
||||
<el-tab-pane label="修改资料" name="profile">
|
||||
<el-form :model="form" label-width="100px" style="max-width: 480px">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="form.nickName" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="form.phonenumber" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click="saveProfile">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tabs v-model="tab">
|
||||
<el-tab-pane label="修改资料" name="profile">
|
||||
<el-form :model="form" label-width="100px" class="account-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="form.nickName" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="form.phonenumber" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click="saveProfile">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="修改密码" name="password">
|
||||
<el-form :model="pwd" label-width="100px" style="max-width: 480px">
|
||||
<el-form-item label="旧密码">
|
||||
<el-input v-model="pwd.oldPassword" type="password" show-password placeholder="请输入旧密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input v-model="pwd.newPassword" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认新密码">
|
||||
<el-input v-model="pwd.confirmPassword" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click="changePassword">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<el-tab-pane label="修改密码" name="password">
|
||||
<el-form :model="pwd" label-width="100px" class="account-form">
|
||||
<el-form-item label="旧密码">
|
||||
<el-input v-model="pwd.oldPassword" type="password" show-password placeholder="请输入旧密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input v-model="pwd.newPassword" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认新密码">
|
||||
<el-input v-model="pwd.confirmPassword" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click="changePassword">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -107,7 +105,8 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-account { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.account-form { max-width: 480px; }
|
||||
</style>
|
||||
@@ -1,56 +1,54 @@
|
||||
<template>
|
||||
<div class="admin-department">
|
||||
<div class="page-card">
|
||||
<div class="desc">维护医生可用的科室字典。停用的科室不会出现在医生选择下拉中。</div>
|
||||
<div class="page-card admin-department">
|
||||
<div class="breadcrumb">首页 / 科室管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="科室名称:">
|
||||
<el-input v-model="q.name" placeholder="输入名称模糊搜索" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
<el-button type="success" @click="openAdd">新增科室</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="科室名称"><el-input v-model="q.name" placeholder="输入名称模糊搜索" clearable /></el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
<el-button type="success" @click="openAdd">新增科室</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="deptId" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="科室名称" min-width="200" />
|
||||
<el-table-column prop="sort" label="排序" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="deptId" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="科室名称" min-width="200" />
|
||||
<el-table-column prop="sort" label="排序" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
class="pager"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
v-model:current-page="q.pageNum"
|
||||
v-model:page-size="q.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:page-size="q.pageSize"
|
||||
:current-page="q.pageNum"
|
||||
@current-change="(p) => { q.pageNum = p; load() }"
|
||||
@size-change="(s) => { q.pageSize = s; q.pageNum = 1; load() }"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -92,8 +90,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await listDepartments({ ...q })
|
||||
rows.value = r.rows || []
|
||||
total.value = r.total || 0
|
||||
rows.value = (r.data && r.data.rows) || r.rows || []
|
||||
total.value = (r.data && r.data.total) || r.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
@@ -164,9 +162,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-department { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 16px; }
|
||||
.pager { margin-top: 16px; text-align: right; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟦 后台 服务机构管理 (admin/executor-orgs)
|
||||
说明: 原 admin/Orgs.vue 拆出 executor 一支, 硬编码 orgType='executor'
|
||||
接口: bizList('org', q) / bizAdd / bizUpdate
|
||||
-->
|
||||
<div class="page-card admin-executor-orgs">
|
||||
<div class="breadcrumb">首页 / 服务机构管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="正常" value="正常" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
<el-option label="0正常" value="0" />
|
||||
<el-option label="1禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="openAdd">新增服务机构</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatDate(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<el-button link type="primary" @click="onPeople(row)">人员管理</el-button>
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑服务机构' : '新增服务机构'" width="640px">
|
||||
<el-form :model="form" label-width="120px" :rules="rules" ref="formRef">
|
||||
<el-form-item label="公司名称" prop="orgName">
|
||||
<el-input v-model="form.orgName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司地址">
|
||||
<el-input v-model="form.address" />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="form.taxNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="form.contactName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="form.contactPhone" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isEdit" label="状态">
|
||||
<el-select v-model="form.status" style="width:160px">
|
||||
<el-option label="0正常" value="0" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="1禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList, bizAdd, bizUpdate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = reactive({ orgType: 'executor', orgName: '', taxNo: '', status: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '正常' || s === '0' || s === '合作中') return 'success'
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '禁用' || row.status === '1'
|
||||
}
|
||||
function formatDate(d) {
|
||||
if (!d) return ''
|
||||
const str = String(d)
|
||||
return str.length >= 10 ? str.slice(0, 10) : str
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(q, { orgType: 'executor', orgName: '', taxNo: '', status: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
// executor 状态语义: 恢复 = '0', 禁用 = '1' (数字)
|
||||
const newStatus = disabled ? '0' : '1'
|
||||
const label = disabled ? '恢复' : '禁用'
|
||||
if (!disabled) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.orgName}」吗? ${label}后该企业下所有账号均不可登录`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
}
|
||||
try {
|
||||
await bizUpdate('org', { orgId: row.orgId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const saving = ref(false)
|
||||
const formRef = ref(null)
|
||||
const form = reactive({
|
||||
orgId: null, orgType: 'executor', orgName: '', address: '', taxNo: '',
|
||||
contactName: '', contactPhone: '', status: '0'
|
||||
})
|
||||
const rules = {
|
||||
orgName: [{ required: true, message: '请输入公司名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
isEdit.value = false
|
||||
Object.assign(form, {
|
||||
orgId: null, orgType: 'executor', orgName: '', address: '', taxNo: '',
|
||||
contactName: '', contactPhone: '', status: '0'
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function onEdit(row) {
|
||||
isEdit.value = true
|
||||
Object.assign(form, { ...row, orgType: 'executor' })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function onPeople(row) {
|
||||
router.push({ path: '/admin/executor-people', query: { orgId: row.orgId, orgName: row.orgName } })
|
||||
}
|
||||
function onView(row) {
|
||||
ElMessageBox.alert(
|
||||
`类型: 执行方\n` +
|
||||
`名称: ${row.orgName}\n` +
|
||||
`地址: ${row.address || '-'}\n` +
|
||||
`税号: ${row.taxNo || '-'}\n` +
|
||||
`联系人: ${row.contactName || '-'} (${row.contactPhone || '-'})\n` +
|
||||
`状态: ${row.status}\n` +
|
||||
`创建日期: ${formatDate(row.createTime)}`,
|
||||
'服务机构详情',
|
||||
{ confirmButtonText: '关闭' }
|
||||
).catch(() => {})
|
||||
}
|
||||
async function onSave() {
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await bizUpdate('org', { ...form })
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await bizAdd('org', { ...form })
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '保存失败') }
|
||||
finally { saving.value = false }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-executor-orgs { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟦 后台 服务机构下人员 (admin/executor-people)
|
||||
入口: org 表格行内「人员管理」按钮 (router.push 带 orgId/orgName)
|
||||
接口: bizList('person', q) / bizUpdate / bizAdd / executorImport / executorImportTemplate
|
||||
-->
|
||||
<div class="page-card admin-executor-people">
|
||||
<div class="breadcrumb">
|
||||
首页 / 服务机构管理 / 人员管理{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="姓名" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="手机号" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称">
|
||||
<el-input v-model="q.orgName" placeholder="企业名称" clearable style="width:180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="q.department" placeholder="部门" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="q.position" placeholder="职务" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建人员</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<span class="filter-tip">*批量导入模板与列表表项一致</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||||
<el-table-column prop="orgName" label="企业名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" min-width="100" />
|
||||
<el-table-column prop="position" label="职务" min-width="100" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status || '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '启用' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择文件">
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
|
||||
<el-button>选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
|
||||
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '禁用' || row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...q,
|
||||
orgType: 'executor',
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
pageNum: page.pageNum,
|
||||
pageSize: page.pageSize,
|
||||
}
|
||||
const { data } = await bizList('person', params)
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(q, { name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '正常' : '禁用'
|
||||
const label = disabled ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.name}」吗?`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizUpdate('person', { personId: row.personId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
// ========== 新建人员 ==========
|
||||
function goNew() {
|
||||
router.push({
|
||||
path: '/admin/executor-people/new',
|
||||
query: {
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
...(orgName.value ? { orgName: orgName.value } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 批量导入 ==========
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importFile = ref(null)
|
||||
|
||||
function openImport() { importFile.value = null; importOpen.value = true }
|
||||
function onFileChange(file) { importFile.value = file.raw }
|
||||
function onFileRemove() { importFile.value = null }
|
||||
async function downloadTemplate() {
|
||||
try {
|
||||
const res = await downloadImportTemplate('executor')
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '服务机构人员导入模板.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch { ElMessage.error('模板下载失败') }
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
||||
importing.value = true
|
||||
try {
|
||||
const res = await importPerson('executor', importFile.value)
|
||||
const payload = res.data || res
|
||||
const ok = payload.ok ?? 0
|
||||
const total = payload.total ?? 0
|
||||
const failed = (payload.results || []).filter(r => !r.ok)
|
||||
if (failed.length === 0) {
|
||||
ElMessage.success(`导入成功 ${ok}/${total}`)
|
||||
} else {
|
||||
ElMessageBox.alert(
|
||||
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
|
||||
failed.map(f => ` 第${f.rowNo}行 ${f.name || ''}: ${f.message}`).join('\n'),
|
||||
'导入结果', { type: 'warning' }
|
||||
)
|
||||
}
|
||||
importOpen.value = false
|
||||
load()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '导入失败')
|
||||
} finally {
|
||||
importing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-executor-people { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<!--
|
||||
专家详情独立页 (只读)
|
||||
1:1 抄 ExpertNew.vue 风格 (page-card + 面包屑 + form-card nested + 2 列)
|
||||
区别: 所有控件禁用, 仅展示
|
||||
-->
|
||||
<div class="page-card expert-detail">
|
||||
<!-- 面包屑 -->
|
||||
<div class="breadcrumb">
|
||||
<a @click="goBack">专家管理</a> > 专家详情
|
||||
</div>
|
||||
|
||||
<!-- 表单卡片 -->
|
||||
<el-card v-loading="loading" shadow="never" class="form-card nested">
|
||||
<el-form :model="form" label-width="120px" disabled>
|
||||
<!-- Row 1: 姓名 | 手机号 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="form.phone" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 2: 工作单位 | 科室 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作单位">
|
||||
<el-input v-model="form.workUnit" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="科室">
|
||||
<el-input v-model="form.department" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 3: 职称 | 地区 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职称">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地区">
|
||||
<el-input v-model="form.region" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 4: 执业医师证书 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="执业医师证书">
|
||||
<oss-image-uploader
|
||||
v-model="form.practiceCertUrl"
|
||||
dir="ry8080/expert/practice/"
|
||||
placeholder="+"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 5: 职称证明 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="职称证明">
|
||||
<oss-image-uploader
|
||||
v-model="form.titleCertUrl"
|
||||
dir="ry8080/expert/title/"
|
||||
placeholder="+"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 6: 身份证件号码 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证件号码">
|
||||
<el-input v-model="form.idCard" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 7: 身份证件照片 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证件照片">
|
||||
<id-card-uploader
|
||||
:front-url="form.idCardFrontUrl"
|
||||
:back-url="form.idCardBackUrl"
|
||||
dir="ry8080/expert/idcard/"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 8: 银行卡号 | 银行名称 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行卡号">
|
||||
<el-input v-model="form.bankCard" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行名称">
|
||||
<el-input v-model="form.bankName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 9: 开户行省份 | 开户行城市 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行省份">
|
||||
<el-input v-model="form.bankProvince" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行城市">
|
||||
<el-input v-model="form.bankCity" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 10: 开户行地址 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="开户行地址">
|
||||
<el-input v-model="form.bankAddress" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="form-actions">
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { bizGet } from '@/api/public'
|
||||
import OssImageUploader from '@/components/OssImageUploader.vue'
|
||||
import IdCardUploader from '@/components/IdCardUploader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const expertId = route.params.id
|
||||
|
||||
const loading = ref(false)
|
||||
const form = reactive({
|
||||
expertId: null,
|
||||
name: '',
|
||||
phone: '',
|
||||
workUnit: '',
|
||||
department: '',
|
||||
title: '',
|
||||
region: '',
|
||||
practiceCertUrl: '',
|
||||
titleCertUrl: '',
|
||||
idCard: '',
|
||||
idCardFrontUrl: '',
|
||||
idCardBackUrl: '',
|
||||
bankCard: '',
|
||||
bankName: '',
|
||||
bankProvince: '',
|
||||
bankCity: '',
|
||||
bankAddress: '',
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
router.push('/admin/experts')
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!expertId) {
|
||||
ElMessage.error('缺少专家ID')
|
||||
goBack()
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await bizGet('expert', expertId)
|
||||
if (res?.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
Object.assign(form, {
|
||||
expertId: d.expertId,
|
||||
name: d.name || '',
|
||||
phone: d.phone || '',
|
||||
workUnit: d.workUnit || '',
|
||||
department: d.department || '',
|
||||
title: d.title || '',
|
||||
region: d.region || '',
|
||||
practiceCertUrl: d.practiceCertUrl || '',
|
||||
titleCertUrl: d.titleCertUrl || '',
|
||||
idCard: d.idCard || '',
|
||||
idCardFrontUrl: d.idCardFrontUrl || '',
|
||||
idCardBackUrl: d.idCardBackUrl || '',
|
||||
bankCard: d.bankCard || '',
|
||||
bankName: d.bankName || '',
|
||||
bankProvince: d.bankProvince || '',
|
||||
bankCity: d.bankCity || '',
|
||||
bankAddress: d.bankAddress || '',
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '加载失败')
|
||||
goBack()
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
||||
goBack()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadDetail)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1:1 抄 ExpertNew.vue */
|
||||
.expert-detail { max-width: 1200px; }
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
|
||||
.breadcrumb a:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
|
||||
.form-card.nested { background: transparent; border: none; box-shadow: none; }
|
||||
:deep(.form-card .el-card__body) { padding: 24px 28px; }
|
||||
|
||||
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
|
||||
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
:deep(.el-button) { border-radius: 4px; }
|
||||
:deep(.el-input__wrapper), :deep(.el-textarea__inner) { border-radius: 4px; }
|
||||
|
||||
/* 只读时禁用表单 input 鼠标样式, 保留视觉 */
|
||||
:deep(.el-form.is-disabled .el-input__wrapper) { background-color: #f5f7fa; box-shadow: 0 0 0 1px #e4e7ed inset; }
|
||||
</style>
|
||||
@@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<!--
|
||||
1:1 抄 sponsor/NewPerson.vue 风格
|
||||
15 字段专家新建/编辑独立页
|
||||
编辑/新建复用: 通过 route.params.id 区分
|
||||
-->
|
||||
<div class="page-card expert-new">
|
||||
<!-- 面包屑 -->
|
||||
<div class="breadcrumb">
|
||||
<a @click="goBack">专家管理</a> > {{ isEdit ? '编辑专家' : '新建专家' }}
|
||||
</div>
|
||||
|
||||
<!-- 表单卡片 -->
|
||||
<el-card v-loading="loadingDetail" shadow="never" class="form-card nested">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
|
||||
<!-- Row 1: 姓名 | 手机号 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 2: 工作单位 | 科室 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作单位" prop="workUnit">
|
||||
<el-input v-model="form.workUnit" placeholder="请输入工作单位" maxlength="200" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="科室" prop="department">
|
||||
<doctor-dept-select
|
||||
v-model="form.department"
|
||||
value-field="label"
|
||||
placeholder="输入关键词搜索"
|
||||
filterable
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 3: 职称 | 地区 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职称" prop="title">
|
||||
<doctor-title-select
|
||||
v-model="form.title"
|
||||
value-field="label"
|
||||
placeholder="输入关键词搜索"
|
||||
filterable
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地区">
|
||||
<area-cascader
|
||||
v-model="form.region"
|
||||
format="string"
|
||||
join-sep="/"
|
||||
placeholder="请选择省/市/区"
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 4: 执业医师证书 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="执业医师证书">
|
||||
<oss-image-uploader
|
||||
v-model="form.practiceCertUrl"
|
||||
dir="ry8080/expert/practice/"
|
||||
placeholder="+"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 5: 职称证明 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="职称证明">
|
||||
<oss-image-uploader
|
||||
v-model="form.titleCertUrl"
|
||||
dir="ry8080/expert/title/"
|
||||
placeholder="+"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 6: 身份证件号码 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证件号码">
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证件号码" maxlength="18" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 7: 身份证件照片 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证件照片">
|
||||
<id-card-uploader
|
||||
v-model:front-url="form.idCardFrontUrl"
|
||||
v-model:back-url="form.idCardBackUrl"
|
||||
dir="ry8080/expert/idcard/"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 8: 银行卡号 | 银行名称 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行卡号">
|
||||
<el-input v-model="form.bankCard" placeholder="请输入银行卡号" maxlength="19" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行名称">
|
||||
<el-input v-model="form.bankName" placeholder="如: 中国工商银行" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 9: 开户行省份 | 开户行城市 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行省份">
|
||||
<el-input v-model="form.bankProvince" placeholder="如: 北京市" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行城市">
|
||||
<el-input v-model="form.bankCity" placeholder="如: 北京市" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Row 10: 开户行地址 [span24] -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="开户行地址">
|
||||
<el-input v-model="form.bankAddress" placeholder="请输入开户行详细地址" maxlength="200" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
<el-button @click="confirmCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue'
|
||||
import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue'
|
||||
import AreaCascader from '@/components/AreaCascader.vue'
|
||||
import IdCardUploader from '@/components/IdCardUploader.vue'
|
||||
import OssImageUploader from '@/components/OssImageUploader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const formRef = ref(null)
|
||||
const saving = ref(false)
|
||||
const loadingDetail = ref(false)
|
||||
const isEdit = !!route.params.id
|
||||
const expertId = route.params.id || null
|
||||
|
||||
const form = reactive({
|
||||
expertId: null,
|
||||
name: '',
|
||||
phone: '',
|
||||
workUnit: '',
|
||||
department: '',
|
||||
title: '',
|
||||
region: '',
|
||||
practiceCertUrl: '',
|
||||
titleCertUrl: '',
|
||||
idCard: '',
|
||||
idCardFrontUrl: '',
|
||||
idCardBackUrl: '',
|
||||
bankCard: '',
|
||||
bankName: '',
|
||||
bankProvince: '',
|
||||
bankCity: '',
|
||||
bankAddress: '',
|
||||
})
|
||||
|
||||
// 5 必填字段 + 手机号正则 (项目标准)
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[0-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
|
||||
],
|
||||
workUnit: [{ required: true, message: '请输入工作单位', trigger: 'blur' }],
|
||||
department:[{ required: true, message: '请选择科室', trigger: 'change' }],
|
||||
title: [{ required: true, message: '请选择职称', trigger: 'change' }],
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push('/admin/experts')
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!expertId) return
|
||||
loadingDetail.value = true
|
||||
try {
|
||||
const res = await bizGet('expert', expertId)
|
||||
if (res?.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
Object.assign(form, {
|
||||
expertId: d.expertId,
|
||||
name: d.name || '',
|
||||
phone: d.phone || '',
|
||||
workUnit: d.workUnit || '',
|
||||
department: d.department || '',
|
||||
title: d.title || '',
|
||||
region: d.region || '',
|
||||
practiceCertUrl: d.practiceCertUrl || '',
|
||||
titleCertUrl: d.titleCertUrl || '',
|
||||
idCard: d.idCard || '',
|
||||
idCardFrontUrl: d.idCardFrontUrl || '',
|
||||
idCardBackUrl: d.idCardBackUrl || '',
|
||||
bankCard: d.bankCard || '',
|
||||
bankName: d.bankName || '',
|
||||
bankProvince: d.bankProvince || '',
|
||||
bankCity: d.bankCity || '',
|
||||
bankAddress: d.bankAddress || '',
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '加载失败')
|
||||
goBack()
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
||||
goBack()
|
||||
} finally {
|
||||
loadingDetail.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCancel() {
|
||||
// 检查表单是否有内容 (新建时只要 form.name 即可)
|
||||
const hasContent = form.name || form.phone || form.workUnit || form.department || form.title
|
||||
if (!isEdit && !hasContent) { goBack(); return }
|
||||
ElMessageBox.confirm('确定取消?未保存的内容将丢失', '提示', { type: 'warning' })
|
||||
.then(() => goBack())
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 业务"重复/已存在"提示用 warning 黄 (memory 规则)
|
||||
function isDuplicateError(msg) {
|
||||
return /已存在|重复|已注册|duplicate|uk_expert_phone/i.test(msg || '')
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
} catch {
|
||||
ElMessage.warning('请填写必填项')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form }
|
||||
if (isEdit) {
|
||||
// 编辑: 不改 expertId 主键, 也不重置 auditStatus
|
||||
delete payload.expertId
|
||||
} else {
|
||||
// 新建: admin 创建, 自动通过审核 (BizAuditStatusEnum.APPROVED='2'), 状态正常
|
||||
payload.auditStatus = '2'
|
||||
payload.auditBy = userStore.user?.userName || userStore.user?.nickName || 'admin'
|
||||
payload.auditTime = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
payload.status = 'Y'
|
||||
}
|
||||
const res = isEdit
|
||||
? await bizUpdate('expert', payload)
|
||||
: await bizAdd('expert', payload)
|
||||
if (res?.code === 200) {
|
||||
if (!isEdit && res.data) {
|
||||
// 新建成功: 后端同时创建 sys_user, 提示账号信息
|
||||
const u = res.data
|
||||
ElMessageBox.alert(
|
||||
`专家账号已创建\n账号: ${u.userName || form.phone}\n初始密码: ${u.password || form.phone}\n请记录后告知使用人 (密码仅本次显示)`,
|
||||
'创建成功',
|
||||
{ type: 'success', confirmButtonText: '我已记录, 返回' }
|
||||
).then(() => goBack())
|
||||
.catch(() => goBack())
|
||||
} else {
|
||||
ElMessage.success(isEdit ? '修改成功' : '新建成功')
|
||||
goBack()
|
||||
}
|
||||
} else {
|
||||
const msg = res?.msg || '保存失败'
|
||||
if (isDuplicateError(msg)) ElMessage.warning('该手机号已注册,请更换')
|
||||
else ElMessage.error(msg)
|
||||
}
|
||||
} catch (e) {
|
||||
const msg = e?.msg || e?.message || '保存失败'
|
||||
if (isDuplicateError(msg)) ElMessage.warning('该手机号已注册,请更换')
|
||||
else ElMessage.error(msg)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (isEdit) loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1:1 抄 sponsor/NewPerson.vue */
|
||||
.expert-new { max-width: 1200px; }
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
|
||||
.breadcrumb a:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
|
||||
.form-card.nested { background: transparent; border: none; box-shadow: none; }
|
||||
:deep(.form-card .el-card__body) { padding: 24px 28px; }
|
||||
|
||||
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
|
||||
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
:deep(.el-button) { border-radius: 4px; }
|
||||
:deep(.el-input__wrapper), :deep(.el-textarea__inner) { border-radius: 4px; }
|
||||
</style>
|
||||
+253
-245
@@ -4,96 +4,95 @@
|
||||
字段映射: 抄 manager/Experts.vue 11 列 + 操作列, 容器/风格改用 admin 通用 page-card + desc
|
||||
接口: bizList('expert', q) / bizAdd / bizUpdate
|
||||
-->
|
||||
<div class="admin-experts">
|
||||
<div class="page-card">
|
||||
<div class="desc">维护全平台评审专家档案, 管理其执业证书、职称证明与账号状态</div>
|
||||
<div class="page-card admin-experts">
|
||||
<div class="breadcrumb">首页 / 专家管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="输入姓名" clearable style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="输入手机号" clearable maxlength="11" style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作单位">
|
||||
<el-input v-model="q.workUnit" placeholder="输入工作单位" clearable style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审核状态">
|
||||
<el-select v-model="q.auditStatus" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="待审核" value="0" />
|
||||
<el-option label="通过" value="1" />
|
||||
<el-option label="退回" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable style="width:120px">
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="禁用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名"><el-input v-model="q.name" placeholder="输入姓名" clearable style="width:160px" /></el-form-item>
|
||||
<el-form-item label="手机号"><el-input v-model="q.phone" placeholder="输入手机号" clearable maxlength="11" style="width:160px" /></el-form-item>
|
||||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" placeholder="输入工作单位" clearable style="width:200px" /></el-form-item>
|
||||
<el-form-item label="审核状态">
|
||||
<el-select v-model="q.auditStatus" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="未提交" value="0" />
|
||||
<el-option label="待审核" value="1" />
|
||||
<el-option label="通过" value="2" />
|
||||
<el-option label="拒绝" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable style="width:120px">
|
||||
<el-option label="正常" value="Y" />
|
||||
<el-option label="禁用" value="N" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量按钮区 (与 People.vue batch-bar 风格一致) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建专家</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<el-button :disabled="!selection.length" @click="onBatchAudit">批量审核</el-button>
|
||||
<el-button @click="openImportPracticeCert">批量导入执业证书</el-button>
|
||||
<el-button @click="openImportTitleCert">批量导入职称证明</el-button>
|
||||
</div>
|
||||
|
||||
<div class="page-card">
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="openAdd">新建专家</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<el-button :disabled="!selection.length" @click="onBatchAudit">批量审核</el-button>
|
||||
<el-button @click="openImportPracticeCert">批量导入执业证书</el-button>
|
||||
<el-button @click="openImportTitleCert">批量导入职称证明</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="name" label="姓名" width="100" fixed />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
<el-table-column prop="workUnit" label="工作单位" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="科室" width="100" />
|
||||
<el-table-column prop="title" label="职称" width="100" />
|
||||
<el-table-column label="执业证书" width="170">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.practiceCertUrl">
|
||||
<el-button link type="primary" @click="onViewCert(row.practiceCertUrl)">查看</el-button>
|
||||
<el-button link type="primary" @click="onDownloadCert(row.practiceCertUrl, row.phone)">下载</el-button>
|
||||
</template>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="name" label="姓名" width="100" fixed />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
<el-table-column prop="workUnit" label="工作单位" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="科室" width="100" />
|
||||
<el-table-column prop="title" label="职称" width="100" />
|
||||
<el-table-column label="执业证书" width="170">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.practiceCertUrl">
|
||||
<el-button link type="primary" @click="onPreviewCert(row.practiceCertUrl)">查看</el-button>
|
||||
<el-button link type="primary" @click="onDownloadCert(row.practiceCertUrl, row.phone)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="职称证明" width="170">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.titleCertUrl">
|
||||
<el-button link type="primary" @click="onViewCert(row.titleCertUrl)">查看</el-button>
|
||||
<el-button link type="primary" @click="onDownloadCert(row.titleCertUrl, row.phone)">下载</el-button>
|
||||
</template>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="职称证明" width="170">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.titleCertUrl">
|
||||
<el-button link type="primary" @click="onPreviewCert(row.titleCertUrl)">查看</el-button>
|
||||
<el-button link type="primary" @click="onDownloadCert(row.titleCertUrl, row.phone)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="['stage-tag', auditStatusClass(row.auditStatus)]">{{ auditStatusText(row.auditStatus) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="auditTime" label="审批时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.auditTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="auditBy" label="审批人" width="120" align="center" />
|
||||
<el-table-column label="操作" width="260" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link :type="row.status === '1' ? 'danger' : 'success'" @click="row.status === '1' ? onDisable(row) : onEnable(row)">
|
||||
{{ row.status === '1' ? '禁用' : '恢复' }}
|
||||
</el-button>
|
||||
<el-button link type="info" v-if="row.auditStatus === '2'" @click="onViewAuditOpinion(row)">已退回</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<audit-status-tag :status="row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="启用状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.status === 'N' ? 'danger' : 'success'">
|
||||
{{ row.status === 'N' ? '禁用' : '正常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||||
<el-button link type="primary" @click="goEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="goView(row)">查看</el-button>
|
||||
<el-button link :type="row.status === 'N' ? 'success' : 'danger'" @click="row.status === 'N' ? onEnable(row) : onDisable(row)">
|
||||
{{ row.status === 'N' ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
<el-button link type="info" v-if="row.auditStatus === '3'" @click="onViewAuditOpinion(row)">已拒绝</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
@@ -102,35 +101,11 @@
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
style="margin-top: 12px; text-align: right;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 新建/编辑专家 -->
|
||||
<el-dialog v-model="editOpen" :title="editForm.expertId ? '编辑专家' : '新建专家'" width="640px">
|
||||
<el-form :model="editForm" label-width="120px" :rules="editRules" ref="editFormRef">
|
||||
<el-form-item label="姓名" prop="name"><el-input v-model="editForm.name" /></el-form-item>
|
||||
<el-form-item label="手机号" prop="phone"><el-input v-model="editForm.phone" maxlength="11" /></el-form-item>
|
||||
<el-form-item label="工作单位" prop="workUnit"><el-input v-model="editForm.workUnit" /></el-form-item>
|
||||
<el-form-item label="科室" prop="department"><el-input v-model="editForm.department" placeholder="如: 呼吸科" /></el-form-item>
|
||||
<el-form-item label="职称" prop="title">
|
||||
<el-select v-model="editForm.title" placeholder="请选择" style="width:100%">
|
||||
<el-option label="住院医师" value="住院医师" />
|
||||
<el-option label="主治医师" value="主治医师" />
|
||||
<el-option label="副主任医师" value="副主任医师" />
|
||||
<el-option label="主任医师" value="主任医师" />
|
||||
<el-option label="教授" value="教授" />
|
||||
<el-option label="副教授" value="副教授" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执业证书 URL"><el-input v-model="editForm.practiceCertUrl" /></el-form-item>
|
||||
<el-form-item label="职称证明 URL"><el-input v-model="editForm.titleCertUrl" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editOpen=false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitEdit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 新建/编辑专家: 跳转独立页 /admin/experts/new 或 /experts/edit/:id -->
|
||||
<!-- (旧的 edit dialog 已删) -->
|
||||
|
||||
<!-- 审核 -->
|
||||
<el-dialog v-model="auditOpen" title="审核" width="520px">
|
||||
@@ -138,8 +113,8 @@
|
||||
<el-form :model="auditForm" label-width="100px">
|
||||
<el-form-item label="审核结果">
|
||||
<el-radio-group v-model="auditForm.result">
|
||||
<el-radio value="1">通过</el-radio>
|
||||
<el-radio value="2">退回</el-radio>
|
||||
<el-radio value="2">通过</el-radio>
|
||||
<el-radio value="3">拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见">
|
||||
@@ -152,27 +127,33 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量导入专家 -->
|
||||
<el-dialog v-model="importOpen" title="批量导入专家" width="520px">
|
||||
<div style="margin-bottom:12px;font-size:13px">
|
||||
<el-link type="primary" @click="onDownloadTpl">批量导入模板下载</el-link>
|
||||
<!-- 批量导入专家 (RuoYi 用户导入风格) -->
|
||||
<el-dialog v-model="importOpen" title="批量导入专家" width="400px" append-to-body>
|
||||
<el-upload
|
||||
ref="importUploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="importHeaders"
|
||||
:action="importUrl"
|
||||
:disabled="importing"
|
||||
:on-progress="onImportProgress"
|
||||
:on-success="onImportSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处, 或<em>点击上传</em></div>
|
||||
<div slot="tip" class="el-upload__tip el-upload__tip--inline">
|
||||
<el-checkbox v-model="importUpdateSupport" :disabled="importing" /> 是否更新已存在的专家
|
||||
<el-link style="font-size:12px;vertical-align:baseline;margin-left:8px" type="primary" :underline="false" @click.stop="onDownloadTpl">下载模板</el-link>
|
||||
</div>
|
||||
</el-upload>
|
||||
<!-- 导入结果反馈 (RuoYi 风格: 内联在 dialog 内) -->
|
||||
<div v-if="importResultMsg" class="upload-result" v-html="importResultMsg" />
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="importOpen=false">取 消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确 定</el-button>
|
||||
</div>
|
||||
<p style="color:#606266;margin-bottom:8px">*批量导入模板与列表表项一致</p>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="审核结果">
|
||||
<el-radio-group v-model="importAuditResult">
|
||||
<el-radio value="1">通过</el-radio>
|
||||
<el-radio value="2">退回</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择文件">
|
||||
<el-input v-model="importFileUrl" placeholder="上传文件 URL 或选择文件" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen=false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量导入执业证书 -->
|
||||
@@ -195,34 +176,40 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情查看 -->
|
||||
<el-dialog v-model="viewOpen" title="专家详情" width="640px">
|
||||
<el-descriptions :column="2" border v-if="currentRow">
|
||||
<el-descriptions-item label="姓名">{{ currentRow.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ currentRow.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工作单位">{{ currentRow.workUnit }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科室">{{ currentRow.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职称">{{ currentRow.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">{{ auditStatusText(currentRow.auditStatus) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="执业证书">
|
||||
<el-link v-if="currentRow.practiceCertUrl" type="primary" @click="onViewCert(currentRow.practiceCertUrl)">查看证书</el-link>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="职称证明">
|
||||
<el-link v-if="currentRow.titleCertUrl" type="primary" @click="onViewCert(currentRow.titleCertUrl)">查看证书</el-link>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间">{{ fmtTime(currentRow.auditTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人">{{ currentRow.auditBy }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- 详情查看: 已改跳独立页 /admin/experts/view/:id, 这里删除 dialog -->
|
||||
|
||||
<!-- 证书/证明 图片预览 dialog -->
|
||||
<el-dialog v-model="previewOpen" title="证书预览" width="640px" :show-close="true" destroy-on-close>
|
||||
<div class="cert-preview">
|
||||
<el-image
|
||||
:src="previewUrl"
|
||||
fit="contain"
|
||||
:preview-src-list="[previewUrl]"
|
||||
style="width:100%;max-height:70vh"
|
||||
loading="lazy"
|
||||
>
|
||||
<template #error>
|
||||
<div class="cert-preview-error">图片加载失败, 请<a @click="onDownloadCert(previewUrl)">下载</a>查看</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="onDownloadCert(previewUrl)">下载</el-button>
|
||||
<el-button type="primary" @click="previewOpen=false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { bizList, bizAdd, bizUpdate } from '@/api/public'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import request from '@/utils/request'
|
||||
import { bizList, bizUpdate, downloadExpertTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import AuditStatusTag from '@/components/AuditStatusTag.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = reactive({ name: '', phone: '', workUnit: '', auditStatus: '', status: '' })
|
||||
const rows = ref([])
|
||||
@@ -231,12 +218,23 @@ const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
// 跳独立页 (新建/编辑)
|
||||
function goNew() {
|
||||
router.push('/admin/experts/new')
|
||||
}
|
||||
function goEdit(row) {
|
||||
router.push('/admin/experts/edit/' + row.expertId)
|
||||
}
|
||||
function goView(row) {
|
||||
router.push('/admin/experts/view/' + row.expertId)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('expert', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []
|
||||
page.total = data?.total || 0
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
@@ -247,16 +245,7 @@ function reset() {
|
||||
}
|
||||
function onSel(rs) { selection.value = rs }
|
||||
|
||||
function auditStatusText(s) {
|
||||
if (s === '1') return '通过'
|
||||
if (s === '2') return '退回'
|
||||
return '待审核'
|
||||
}
|
||||
function auditStatusClass(s) {
|
||||
if (s === '1') return 'stage-done'
|
||||
if (s === '2') return 'stage-reject'
|
||||
return 'stage-reviewing'
|
||||
}
|
||||
// auditStatusText / auditStatusClass 已删除: 改用 AuditStatusTag 组件
|
||||
function fmtTime(d) {
|
||||
if (!d) return ''
|
||||
const dt = new Date(d)
|
||||
@@ -264,62 +253,19 @@ function fmtTime(d) {
|
||||
return `${dt.getFullYear()}.${pad(dt.getMonth() + 1)}.${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`
|
||||
}
|
||||
|
||||
// ===== 新建/编辑 =====
|
||||
const editOpen = ref(false)
|
||||
const editFormRef = ref(null)
|
||||
const editForm = reactive({
|
||||
expertId: '', name: '', phone: '', workUnit: '', department: '', title: '',
|
||||
practiceCertUrl: '', titleCertUrl: ''
|
||||
})
|
||||
const editRules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||
workUnit: [{ required: true, message: '请输入工作单位', trigger: 'blur' }],
|
||||
department: [{ required: true, message: '请输入科室', trigger: 'blur' }],
|
||||
title: [{ required: true, message: '请选择职称', trigger: 'change' }]
|
||||
}
|
||||
function openAdd() {
|
||||
Object.assign(editForm, { expertId: '', name: '', phone: '', workUnit: '', department: '', title: '', practiceCertUrl: '', titleCertUrl: '' })
|
||||
editOpen.value = true
|
||||
}
|
||||
function onEdit(row) {
|
||||
Object.assign(editForm, {
|
||||
expertId: row.expertId, name: row.name, phone: row.phone, workUnit: row.workUnit,
|
||||
department: row.department, title: row.title,
|
||||
practiceCertUrl: row.practiceCertUrl || '', titleCertUrl: row.titleCertUrl || ''
|
||||
})
|
||||
editOpen.value = true
|
||||
}
|
||||
async function submitEdit() {
|
||||
await editFormRef.value.validate()
|
||||
submitting.value = true
|
||||
try {
|
||||
if (editForm.expertId) {
|
||||
await bizUpdate('expert', { ...editForm })
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await bizAdd('expert', {
|
||||
...editForm,
|
||||
auditStatus: '1', auditBy: 'admin',
|
||||
auditTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||||
status: '1'
|
||||
})
|
||||
ElMessage.success('新建成功')
|
||||
}
|
||||
editOpen.value = false
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '保存失败') }
|
||||
finally { submitting.value = false }
|
||||
}
|
||||
// auditStatusText / auditStatusClass 已删除: 改用 AuditStatusTag 组件
|
||||
|
||||
// ===== 新建/编辑: 跳独立页 (本页面不再保留 dialog) =====
|
||||
|
||||
// ===== 审核 =====
|
||||
const auditOpen = ref(false)
|
||||
const auditForm = reactive({ expertId: '', name: '', phone: '', result: '1', opinion: '' })
|
||||
// result: '2'=通过, '3'=拒绝 (BizAuditStatusEnum: 0未提交 1待审核 2通过 3拒绝)
|
||||
const auditForm = reactive({ expertId: '', name: '', phone: '', result: '2', opinion: '' })
|
||||
function onAudit(row) {
|
||||
auditForm.expertId = row.expertId
|
||||
auditForm.name = row.name
|
||||
auditForm.phone = row.phone
|
||||
auditForm.result = '1'
|
||||
auditForm.result = '2'
|
||||
auditForm.opinion = ''
|
||||
auditOpen.value = true
|
||||
}
|
||||
@@ -333,7 +279,7 @@ async function submitAudit() {
|
||||
auditBy: 'admin',
|
||||
auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
})
|
||||
ElMessage.success(auditForm.result === '1' ? '审核通过' : '已退回')
|
||||
ElMessage.success(auditForm.result === '2' ? '审核通过' : '已拒绝')
|
||||
auditOpen.value = false
|
||||
load()
|
||||
} catch { ElMessage.error('审核失败') }
|
||||
@@ -351,7 +297,7 @@ async function onBatchAudit() {
|
||||
for (const r of selection.value) {
|
||||
try {
|
||||
await bizUpdate('expert', {
|
||||
expertId: r.expertId, auditStatus: '1', auditOpinion: '批量通过',
|
||||
expertId: r.expertId, auditStatus: '2', auditOpinion: '批量通过',
|
||||
auditBy: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
})
|
||||
ok++
|
||||
@@ -363,34 +309,39 @@ async function onBatchAudit() {
|
||||
load()
|
||||
}
|
||||
|
||||
// ===== 启用/禁用 =====
|
||||
// ===== 启用/禁用: 专用 endpoint (PUT /business/expert/{expertId}/status) =====
|
||||
async function onDisable(row) {
|
||||
try {
|
||||
await bizUpdate('expert', { expertId: row.expertId, status: '0' })
|
||||
await request.put(`/business/expert/${row.expertId}/status`, null, { params: { status: 'N' } })
|
||||
ElMessage.success('已禁用'); load()
|
||||
} catch { ElMessage.error('禁用失败') }
|
||||
} catch (e) { ElMessage.error(e?.msg || '禁用失败') }
|
||||
}
|
||||
async function onEnable(row) {
|
||||
try {
|
||||
await bizUpdate('expert', { expertId: row.expertId, status: '1' })
|
||||
await request.put(`/business/expert/${row.expertId}/status`, null, { params: { status: 'Y' } })
|
||||
ElMessage.success('已恢复'); load()
|
||||
} catch { ElMessage.error('恢复失败') }
|
||||
} catch (e) { ElMessage.error(e?.msg || '恢复失败') }
|
||||
}
|
||||
|
||||
// ===== 详情 / 退回意见 =====
|
||||
const viewOpen = ref(false)
|
||||
const currentRow = ref(null)
|
||||
function onView(row) { currentRow.value = row; viewOpen.value = true }
|
||||
// 详情查看已改跳独立页 /admin/experts/view/:id (见 goView)
|
||||
function onViewAuditOpinion(row) {
|
||||
ElMessageBox.alert(
|
||||
`退回时间: ${fmtTime(row.auditTime) || '-'}\n审批人: ${row.auditBy || '-'}\n退回意见: ${row.auditOpinion || '(无)'}`,
|
||||
`专家 ${row.name} 审核已退回`,
|
||||
`专家 ${row.name} 审核已拒绝`,
|
||||
{ type: 'warning', confirmButtonText: '知道了' }
|
||||
).catch(() => {})
|
||||
}
|
||||
|
||||
// ===== 证书查看/下载 =====
|
||||
function onViewCert(url) { if (url) window.open(url, '_blank') }
|
||||
// 图片预览: dialog 弹窗直接显示 (用 el-image 自带 preview-src-list 支持点击放大)
|
||||
const previewOpen = ref(false)
|
||||
const previewUrl = ref('')
|
||||
function onPreviewCert(url) {
|
||||
if (!url) return
|
||||
previewUrl.value = url
|
||||
previewOpen.value = true
|
||||
}
|
||||
function onDownloadCert(url, phone) {
|
||||
if (!url) return
|
||||
const a = document.createElement('a')
|
||||
@@ -399,22 +350,65 @@ function onDownloadCert(url, phone) {
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a)
|
||||
}
|
||||
|
||||
// ===== 批量导入 dialog =====
|
||||
// ===== 批量导入 dialog (RuoYi 用户导入风格) =====
|
||||
const importOpen = ref(false)
|
||||
const importAuditResult = ref('1')
|
||||
const importFileUrl = ref('')
|
||||
const importing = ref(false)
|
||||
const importUploadRef = ref(null)
|
||||
const importUpdateSupport = ref(false)
|
||||
const importResultMsg = ref('')
|
||||
// importHeaders 与 importUrl 用于 el-upload auto-upload=false 时的手动提交
|
||||
const importHeaders = computed(() => ({
|
||||
Authorization: 'Bearer ' + (localStorage.getItem('ry_token') || '')
|
||||
}))
|
||||
const importUrl = computed(() => '/dev-api/business/expert/importData?updateSupport=' + importUpdateSupport.value)
|
||||
|
||||
function openImport() {
|
||||
importAuditResult.value = '1'; importFileUrl.value = ''; importOpen.value = true
|
||||
importResultMsg.value = ''
|
||||
importUpdateSupport.value = false
|
||||
// 清空 el-upload 已选文件 (手动触发, 否则下次打开还显示上次的)
|
||||
if (importUploadRef.value) {
|
||||
importUploadRef.value.clearFiles()
|
||||
}
|
||||
importOpen.value = true
|
||||
}
|
||||
function onDownloadTpl() { ElMessage.info('批量导入模板下载 — 待后端提供静态文件') }
|
||||
async function submitImport() {
|
||||
if (!importFileUrl.value) return ElMessage.warning('请选择文件')
|
||||
|
||||
function onImportProgress() {
|
||||
importing.value = true
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
}
|
||||
|
||||
function onImportSuccess(res, file) {
|
||||
importing.value = false
|
||||
ElMessage.info(`已提交批量导入任务 — 待后端实现`)
|
||||
importOpen.value = false
|
||||
// 后端返回 {code:200, msg:"..."}
|
||||
importResultMsg.value = res?.msg || '导入成功'
|
||||
if (res?.code === 200) {
|
||||
ElMessage.success('导入完成')
|
||||
load()
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '导入失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 与 SponsorPeople.vue downloadTemplate 同款: 走 RuoYi 极简模式 (blob + a.download + 延迟释放)
|
||||
// 注意: a 必须挂到 body 上 (Firefox 要求), URL.revokeObjectURL 要延后 (避免下载未启动就释放)
|
||||
function onDownloadTpl() {
|
||||
downloadExpertTemplate().then((res) => {
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '专家导入模板.xlsx'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
||||
}).catch((e) => {
|
||||
ElMessage.error(e?.message || '模板下载失败')
|
||||
})
|
||||
}
|
||||
|
||||
function submitImport() {
|
||||
if (!importUploadRef.value) return ElMessage.warning('上传组件未就绪')
|
||||
importUploadRef.value.submit()
|
||||
}
|
||||
|
||||
// 批量导入执业证书
|
||||
@@ -447,14 +441,28 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-experts { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 0; }
|
||||
.batch-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
|
||||
.stage-tag { display: inline-block; padding: 2px 10px; border-radius: 3px; font-size: 12px; border: 1px solid; }
|
||||
.stage-done { color: #67c23a; border-color: #c2e7b0; background: #f0f9eb; }
|
||||
.stage-reviewing { color: #e6a23c; border-color: #f5dab1; background: #fdf6ec; }
|
||||
.stage-reject { color: #f56c6c; border-color: #fbc4c4; background: #fef0f0; }
|
||||
/* 证书预览 dialog */
|
||||
.cert-preview { display: flex; justify-content: center; align-items: center; min-height: 200px; background: #f5f7fa; border-radius: 4px; padding: 8px; }
|
||||
.cert-preview-error { color: #909399; padding: 40px 0; }
|
||||
|
||||
/* 批量导入 dialog (RuoYi 风格) */
|
||||
.upload-result {
|
||||
margin-top: 12px; padding: 10px 12px;
|
||||
background: #f5f7fa; border-radius: 4px;
|
||||
font-size: 13px; line-height: 1.8;
|
||||
max-height: 200px; overflow-y: auto;
|
||||
}
|
||||
:deep(.el-upload__tip--inline) {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
gap: 8px; margin-top: 8px;
|
||||
}
|
||||
|
||||
/* stage-* CSS 已删除: 改用 AuditStatusTag 组件 (el-tag 自带颜色) */
|
||||
</style>
|
||||
|
||||
@@ -1,45 +1,43 @@
|
||||
<template>
|
||||
<div class="admin-meetings">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看系统中所有会议 (跨角色全量)</div>
|
||||
<div class="page-card admin-meetings">
|
||||
<div class="breadcrumb">首页 / 会议全览</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="会议主题:">
|
||||
<el-input v-model="q.title" placeholder="输入会议主题" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="会议主题"><el-input v-model="q.title" placeholder="输入会议主题" clearable /></el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="title" label="会议主题" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="STATUS_TAG[row.status] || 'info'">{{ STATUS_LABEL[row.status] || row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间" width="170" />
|
||||
<el-table-column prop="endTime" label="结束时间" width="170" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="title" label="会议主题" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="STATUS_TAG[row.status] || 'info'">{{ STATUS_LABEL[row.status] || row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间" width="170" />
|
||||
<el-table-column prop="endTime" label="结束时间" width="170" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
class="pager"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
v-model:current-page="q.pageNum"
|
||||
v-model:page-size="q.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:page-size="q.pageSize"
|
||||
:current-page="q.pageNum"
|
||||
@current-change="(p) => { q.pageNum = p; load() }"
|
||||
@size-change="(s) => { q.pageSize = s; q.pageNum = 1; load() }"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,8 +64,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await bizList('meeting', { ...q })
|
||||
rows.value = r.rows || r.data?.rows || []
|
||||
total.value = r.total || r.data?.total || 0
|
||||
rows.value = r.rows || (r.data && r.data.rows) || []
|
||||
total.value = r.total || (r.data && r.data.total) || 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -82,9 +80,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-meetings { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 16px; }
|
||||
.pager { margin-top: 16px; text-align: right; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -7,77 +7,78 @@
|
||||
biz_service_org 整体删除
|
||||
接口: bizList('org', q) / bizUpdate
|
||||
-->
|
||||
<div class="admin-orgs">
|
||||
<div class="page-card">
|
||||
<div class="desc">管理公司档案 (赞助方 + 执行方共用一张表, 通过 org_type 区分)</div>
|
||||
<div class="page-card admin-orgs">
|
||||
<div class="breadcrumb">首页 / 公司管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="公司类型">
|
||||
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:140px">
|
||||
<el-option label="赞助方" value="sponsor" />
|
||||
<el-option label="执行方" value="executor" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="正常" value="正常" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
<el-option label="0正常" value="0" />
|
||||
<el-option label="1禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司类型">
|
||||
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:140px">
|
||||
<el-option label="赞助方" value="sponsor" />
|
||||
<el-option label="执行方" value="executor" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="正常" value="正常" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
<el-option label="0正常" value="0" />
|
||||
<el-option label="1禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 工具栏 (与 People.vue batch-bar 风格一致) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="openAdd">新增公司</el-button>
|
||||
</div>
|
||||
|
||||
<div class="page-card">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="openAdd">新增公司</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="公司类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
|
||||
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatDate(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="公司类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
|
||||
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatDate(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
@@ -86,10 +87,7 @@
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
style="margin-top: 12px; text-align: right;"
|
||||
/>
|
||||
|
||||
<p class="hint-bottom">*点击禁用后, 该公司下所有账号均不可登录</p>
|
||||
</div>
|
||||
|
||||
<!-- 查看 / 编辑 dialog -->
|
||||
@@ -160,8 +158,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []
|
||||
page.total = data?.total || 0
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
@@ -253,10 +251,10 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-orgs { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 0; }
|
||||
.toolbar { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||
.hint-bottom { font-size: 12px; color: #ff4d4f; margin-top: 12px; text-align: right; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
|
||||
@@ -1,59 +1,54 @@
|
||||
<template>
|
||||
<div class="admin-projects">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看系统中所有项目 (跨角色全量)</div>
|
||||
<div class="page-card admin-projects">
|
||||
<div class="breadcrumb">首页 / 项目全览</div>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="项目编号:">
|
||||
<el-input v-model="q.projectNo" placeholder="输入项目编号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称:">
|
||||
<el-input v-model="q.projectName" placeholder="输入项目名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题:">
|
||||
<el-select v-model="q.isFinished" placeholder="全部" clearable>
|
||||
<el-option label="未结题" value="0" />
|
||||
<el-option label="已结题" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="q.projectName" placeholder="输入项目名称" clearable /></el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题">
|
||||
<el-select v-model="q.isFinished" placeholder="全部" clearable>
|
||||
<el-option label="未结题" value="0" />
|
||||
<el-option label="已结题" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="projectNo" label="项目编号" width="140" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="STATUS_TAG[row.status] || 'info'">{{ STATUS_LABEL[row.status] || row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isFinished" label="结题" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isFinished === '1' ? 'success' : 'info'">{{ row.isFinished === '1' ? '已结题' : '未结题' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="projectNo" label="项目编号" width="140" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="STATUS_TAG[row.status] || 'info'">{{ STATUS_LABEL[row.status] || row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isFinished" label="结题" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isFinished === '1' ? 'success' : 'info'">{{ row.isFinished === '1' ? '已结题' : '未结题' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
class="pager"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
v-model:current-page="q.pageNum"
|
||||
v-model:page-size="q.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:page-size="q.pageSize"
|
||||
:current-page="q.pageNum"
|
||||
@current-change="(p) => { q.pageNum = p; load() }"
|
||||
@size-change="(s) => { q.pageSize = s; q.pageNum = 1; load() }"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,8 +75,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await bizList('project', { ...q })
|
||||
rows.value = r.rows || r.data?.rows || []
|
||||
total.value = r.total || r.data?.total || 0
|
||||
rows.value = r.rows || (r.data && r.data.rows) || []
|
||||
total.value = r.total || (r.data && r.data.total) || 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -96,9 +91,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-projects { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 16px; }
|
||||
.pager { margin-top: 16px; text-align: right; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,23 +1,21 @@
|
||||
<template>
|
||||
<div class="admin-roles">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看 6 种角色的用户分布与人员明细</div>
|
||||
<div class="page-card admin-roles">
|
||||
<div class="breadcrumb">首页 / 角色管理</div>
|
||||
|
||||
<el-row :gutter="16" class="role-cards">
|
||||
<el-col v-for="r in roleStats" :key="r.code" :xs="12" :sm="8" :md="4">
|
||||
<div class="role-card" :class="{ active: r.code === selected }" @click="selectRole(r.code)">
|
||||
<div class="role-label">{{ r.label }}</div>
|
||||
<div class="role-count">{{ r.count }}</div>
|
||||
<div class="role-hint">点击查看详情</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-row :gutter="16" class="role-cards">
|
||||
<el-col v-for="r in roleStats" :key="r.code" :xs="12" :sm="8" :md="4">
|
||||
<div class="role-card" :class="{ active: r.code === selected }" @click="selectRole(r.code)">
|
||||
<div class="role-label">{{ r.label }}</div>
|
||||
<div class="role-count">{{ r.count }}</div>
|
||||
<div class="role-hint">点击查看详情</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 选中角色详情 -->
|
||||
<div v-if="selected" class="page-card" style="margin-top: 16px">
|
||||
<!-- 选中角色详情 (同 page-card 内, 用 sub-title 分割) -->
|
||||
<template v-if="selected">
|
||||
<h3 class="sub-title">{{ ROLE_LABEL[selected] }} - 成员列表</h3>
|
||||
<el-table :data="rows" border stripe v-loading="loading" style="margin-top: 12px">
|
||||
<el-table :data="rows" border stripe v-loading="loading" class="role-table">
|
||||
<el-table-column prop="userId" label="用户ID" width="80" />
|
||||
<el-table-column prop="userName" label="账号" width="140" />
|
||||
<el-table-column prop="nickName" label="姓名" width="140" />
|
||||
@@ -29,19 +27,30 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { listUser } from '@/api/system'
|
||||
import request from '@/utils/request'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const ROLE_LABEL = {
|
||||
admin: '后台管理员', leader: '项目负责人', manager: '合规人员',
|
||||
doctor: '评审专家', executor: '执行人', sponsor: '赞助方'
|
||||
}
|
||||
// 卡片展示的角色 (隐藏 admin / leader)
|
||||
const VISIBLE_ROLES = ['manager', 'doctor', 'executor', 'sponsor']
|
||||
|
||||
const roleStats = ref([])
|
||||
const selected = ref('')
|
||||
@@ -49,17 +58,20 @@ const rows = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
async function loadStats() {
|
||||
const codes = Object.keys(ROLE_LABEL)
|
||||
const arr = []
|
||||
for (const code of codes) {
|
||||
for (const code of VISIBLE_ROLES) {
|
||||
try {
|
||||
const r = await listUser({ pageNum: 1, pageSize: 1, roleType: code })
|
||||
arr.push({ code, label: ROLE_LABEL[code], count: r.total || 0 })
|
||||
arr.push({ code, label: ROLE_LABEL[code], count: (r.data && r.data.total) || r.total || 0 })
|
||||
} catch (e) {
|
||||
arr.push({ code, label: ROLE_LABEL[code], count: 0 })
|
||||
}
|
||||
}
|
||||
roleStats.value = arr
|
||||
// 默认选中第一个角色并加载列表
|
||||
if (arr.length && !selected.value) {
|
||||
await selectRole(arr[0].code)
|
||||
}
|
||||
}
|
||||
|
||||
async function selectRole(code) {
|
||||
@@ -67,24 +79,72 @@ async function selectRole(code) {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await listUser({ pageNum: 1, pageSize: 200, roleType: code })
|
||||
rows.value = r.rows || []
|
||||
rows.value = (r.data && r.data.rows) || r.rows || []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleStatus(row) {
|
||||
const newStatus = row.status === '0' ? '1' : '0'
|
||||
try {
|
||||
await request({
|
||||
url: '/system/user/changeStatus',
|
||||
method: 'put',
|
||||
data: { userId: row.userId, status: newStatus }
|
||||
})
|
||||
ElMessage.success('已' + (newStatus === '0' ? '启用' : '停用'))
|
||||
// 刷新当前角色列表 + 顶部卡片计数
|
||||
await selectRole(selected.value)
|
||||
await loadStats()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadStats)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-roles { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
|
||||
.role-cards { margin-top: 8px; }
|
||||
.role-card { background: #fafbfc; border: 1px solid #ebeef5; border-radius: 4px; padding: 16px; text-align: center; cursor: pointer; transition: all .2s; }
|
||||
.role-card:hover, .role-card.active { border-color: var(--brand-primary); background: #fff; box-shadow: 0 2px 8px rgba(24,144,255,.12); }
|
||||
.role-label { font-size: 14px; color: #606266; margin-bottom: 8px; }
|
||||
.role-count { font-size: 28px; font-weight: 600; color: var(--brand-primary); margin-bottom: 4px; }
|
||||
.role-card {
|
||||
background: #fafbfc;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.role-card:hover, .role-card.active {
|
||||
border-color: var(--brand-primary);
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(30,58,138,0.12);
|
||||
}
|
||||
.role-label { font-size: 14px; color: #8c8c8c; margin-bottom: 8px; }
|
||||
.role-count {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.role-hint { font-size: 12px; color: #c0c4cc; }
|
||||
.sub-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin: 0; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
|
||||
.sub-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 24px 0 12px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
.role-table { margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟦 后台 支持单位管理 (admin/sponsor-orgs)
|
||||
说明: 原 admin/Orgs.vue 拆出 sponsor 一支, 硬编码 orgType='sponsor'
|
||||
接口: bizList('org', q) / bizAdd / bizUpdate
|
||||
-->
|
||||
<div class="page-card admin-sponsor-orgs">
|
||||
<div class="breadcrumb">首页 / 支持单位管理</div>
|
||||
|
||||
<!-- ========== 筛选区 ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="正常" value="正常" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
<el-option label="0正常" value="0" />
|
||||
<el-option label="1禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="openAdd">新增支持单位</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatDate(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<el-button link type="primary" @click="onPeople(row)">人员管理</el-button>
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑支持单位' : '新增支持单位'" width="640px">
|
||||
<el-form :model="form" label-width="120px" :rules="rules" ref="formRef">
|
||||
<el-form-item label="公司名称" prop="orgName">
|
||||
<el-input v-model="form.orgName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司地址">
|
||||
<el-input v-model="form.address" />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="form.taxNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="form.contactName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="form.contactPhone" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isEdit" label="状态">
|
||||
<el-select v-model="form.status" style="width:160px">
|
||||
<el-option label="正常" value="正常" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList, bizAdd, bizUpdate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = reactive({ orgType: 'sponsor', orgName: '', taxNo: '', status: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '正常' || s === '0' || s === '合作中') return 'success'
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '禁用' || row.status === '1'
|
||||
}
|
||||
function formatDate(d) {
|
||||
if (!d) return ''
|
||||
const str = String(d)
|
||||
return str.length >= 10 ? str.slice(0, 10) : str
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(q, { orgType: 'sponsor', orgName: '', taxNo: '', status: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '正常' : '禁用'
|
||||
const label = disabled ? '恢复' : '禁用'
|
||||
if (!disabled) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.orgName}」吗? ${label}后该企业下所有账号均不可登录`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
}
|
||||
try {
|
||||
await bizUpdate('org', { orgId: row.orgId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const saving = ref(false)
|
||||
const formRef = ref(null)
|
||||
const form = reactive({
|
||||
orgId: null, orgType: 'sponsor', orgName: '', address: '', taxNo: '',
|
||||
contactName: '', contactPhone: '', status: '正常'
|
||||
})
|
||||
const rules = {
|
||||
orgName: [{ required: true, message: '请输入公司名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
isEdit.value = false
|
||||
Object.assign(form, {
|
||||
orgId: null, orgType: 'sponsor', orgName: '', address: '', taxNo: '',
|
||||
contactName: '', contactPhone: '', status: '正常'
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function onEdit(row) {
|
||||
isEdit.value = true
|
||||
Object.assign(form, { ...row, orgType: 'sponsor' })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function onPeople(row) {
|
||||
router.push({ path: '/admin/sponsor-people', query: { orgId: row.orgId, orgName: row.orgName } })
|
||||
}
|
||||
function onView(row) {
|
||||
ElMessageBox.alert(
|
||||
`类型: 赞助方\n` +
|
||||
`名称: ${row.orgName}\n` +
|
||||
`地址: ${row.address || '-'}\n` +
|
||||
`税号: ${row.taxNo || '-'}\n` +
|
||||
`联系人: ${row.contactName || '-'} (${row.contactPhone || '-'})\n` +
|
||||
`状态: ${row.status}\n` +
|
||||
`创建日期: ${formatDate(row.createTime)}`,
|
||||
'支持单位详情',
|
||||
{ confirmButtonText: '关闭' }
|
||||
).catch(() => {})
|
||||
}
|
||||
async function onSave() {
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await bizUpdate('org', { ...form })
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await bizAdd('org', { ...form })
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '保存失败') }
|
||||
finally { saving.value = false }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-sponsor-orgs { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟦 后台 支持单位下人员 (admin/sponsor-people)
|
||||
入口: org 表格行内「人员管理」按钮 (router.push 带 orgId/orgName)
|
||||
接口: bizList('person', q) / bizUpdate / bizAdd / sponsorImport / sponsorImportTemplate
|
||||
-->
|
||||
<div class="page-card admin-sponsor-people">
|
||||
<div class="breadcrumb">
|
||||
首页 / 支持单位管理 / 人员管理{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="姓名" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="手机号" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称">
|
||||
<el-input v-model="q.orgName" placeholder="企业名称" clearable style="width:180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="q.department" placeholder="部门" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="q.position" placeholder="职务" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建人员</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<span class="filter-tip">*批量导入模板与列表表项一致</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||||
<el-table-column prop="orgName" label="企业名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" min-width="100" />
|
||||
<el-table-column prop="position" label="职务" min-width="100" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status || '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '启用' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 批量导入 -->
|
||||
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择文件">
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
|
||||
<el-button>选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
|
||||
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '禁用' || row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...q,
|
||||
orgType: 'sponsor',
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
pageNum: page.pageNum,
|
||||
pageSize: page.pageSize,
|
||||
}
|
||||
const { data } = await bizList('person', params)
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(q, { name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '正常' : '禁用'
|
||||
const label = disabled ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.name}」吗?`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizUpdate('person', { personId: row.personId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
// ========== 新建人员 ==========
|
||||
function goNew() {
|
||||
router.push({
|
||||
path: '/admin/sponsor-people/new',
|
||||
query: {
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
...(orgName.value ? { orgName: orgName.value } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 批量导入 ==========
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importFile = ref(null)
|
||||
|
||||
function openImport() { importFile.value = null; importOpen.value = true }
|
||||
function onFileChange(file) { importFile.value = file.raw }
|
||||
function onFileRemove() { importFile.value = null }
|
||||
async function downloadTemplate() {
|
||||
try {
|
||||
const res = await downloadImportTemplate('sponsor')
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '支持单位人员导入模板.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch { ElMessage.error('模板下载失败') }
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
||||
importing.value = true
|
||||
try {
|
||||
const res = await importPerson('sponsor', importFile.value)
|
||||
const payload = res.data || res
|
||||
const ok = payload.ok ?? 0
|
||||
const total = payload.total ?? 0
|
||||
const failed = (payload.results || []).filter(r => !r.ok)
|
||||
if (failed.length === 0) {
|
||||
ElMessage.success(`导入成功 ${ok}/${total}`)
|
||||
} else {
|
||||
ElMessageBox.alert(
|
||||
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
|
||||
failed.map(f => ` 第${f.rowNo}行 ${f.name || ''}: ${f.message}`).join('\n'),
|
||||
'导入结果', { type: 'warning' }
|
||||
)
|
||||
}
|
||||
importOpen.value = false
|
||||
load()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '导入失败')
|
||||
} finally {
|
||||
importing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-sponsor-people { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,56 +1,54 @@
|
||||
<template>
|
||||
<div class="admin-title">
|
||||
<div class="page-card">
|
||||
<div class="desc">维护医生可用的职称字典。停用的职称不会出现在医生选择下拉中。</div>
|
||||
<div class="page-card admin-title">
|
||||
<div class="breadcrumb">首页 / 职称管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="职称名称:">
|
||||
<el-input v-model="q.name" placeholder="输入名称模糊搜索" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
<el-button type="success" @click="openAdd">新增职称</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="职称名称"><el-input v-model="q.name" placeholder="输入名称模糊搜索" clearable /></el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
<el-button type="success" @click="openAdd">新增职称</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="titleId" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="职称名称" min-width="200" />
|
||||
<el-table-column prop="sort" label="排序" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="titleId" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="职称名称" min-width="200" />
|
||||
<el-table-column prop="sort" label="排序" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
class="pager"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
v-model:current-page="q.pageNum"
|
||||
v-model:page-size="q.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:page-size="q.pageSize"
|
||||
:current-page="q.pageNum"
|
||||
@current-change="(p) => { q.pageNum = p; load() }"
|
||||
@size-change="(s) => { q.pageSize = s; q.pageNum = 1; load() }"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -92,8 +90,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await listTitles({ ...q })
|
||||
rows.value = r.rows || []
|
||||
total.value = r.total || 0
|
||||
rows.value = (r.data && r.data.rows) || r.rows || []
|
||||
total.value = (r.data && r.data.total) || r.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
@@ -164,9 +162,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-title { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 16px; }
|
||||
.pager { margin-top: 16px; text-align: right; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,70 +1,63 @@
|
||||
<template>
|
||||
<div class="admin-users">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看系统中所有用户、修改角色与状态</div>
|
||||
<div class="page-card admin-users">
|
||||
<div class="breadcrumb">首页 / 用户管理</div>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<el-form inline :model="q" class="filter-bar">
|
||||
<el-form-item label="用户账号:">
|
||||
<el-input v-model="q.userName" placeholder="输入用户账号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名:">
|
||||
<el-input v-model="q.nickName" placeholder="输入姓名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色:">
|
||||
<el-select v-model="q.roleType" placeholder="全部角色" clearable>
|
||||
<el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="用户账号"><el-input v-model="q.userName" placeholder="输入用户账号" clearable /></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="q.nickName" placeholder="输入姓名" clearable /></el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<el-select v-model="q.roleType" placeholder="全部角色" clearable>
|
||||
<el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="全部状态" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="userId" label="用户ID" width="80" />
|
||||
<el-table-column prop="userName" label="账号" width="120" />
|
||||
<el-table-column prop="nickName" label="姓名" width="140" />
|
||||
<el-table-column prop="roleType" label="角色" width="100">
|
||||
<template #default="{ row }">{{ ROLE_LABEL[row.roleType] || row.roleType }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phonenumber" label="手机号" width="130" />
|
||||
<el-table-column prop="email" label="邮箱" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'danger'">{{ row.status === '0' ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" link :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" border stripe v-loading="loading">
|
||||
<el-table-column prop="userId" label="用户ID" width="80" />
|
||||
<el-table-column prop="userName" label="账号" width="120" />
|
||||
<el-table-column prop="nickName" label="姓名" width="140" />
|
||||
<el-table-column prop="roleType" label="角色" width="100">
|
||||
<template #default="{ row }">{{ ROLE_LABEL[row.roleType] || row.roleType }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phonenumber" label="手机号" width="130" />
|
||||
<el-table-column prop="email" label="邮箱" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'danger'">{{ row.status === '0' ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" link :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
|
||||
{{ row.status === '0' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
class="pager"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
v-model:current-page="q.pageNum"
|
||||
v-model:page-size="q.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:page-size="q.pageSize"
|
||||
:current-page="q.pageNum"
|
||||
@current-change="(p) => { q.pageNum = p; load() }"
|
||||
@size-change="(s) => { q.pageSize = s; q.pageNum = 1; load() }"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -118,8 +111,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await listUser({ ...q })
|
||||
rows.value = r.rows || []
|
||||
total.value = r.total || 0
|
||||
rows.value = (r.data && r.data.rows) || r.rows || []
|
||||
total.value = (r.data && r.data.total) || r.total || 0
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
@@ -146,7 +139,8 @@ function openEdit(row) {
|
||||
|
||||
async function saveEdit() {
|
||||
try {
|
||||
await request({ url: '/system/user/edit', method: 'put', data: { ...editForm } })
|
||||
// 2026-08-16: 角色统一存 sys_user.role_type, 单次 PUT 即可 (mapper updateUser 已包含 role_type 字段)
|
||||
await request({ url: '/system/user', method: 'put', data: { ...editForm } })
|
||||
ElMessage.success('保存成功')
|
||||
editVisible.value = false
|
||||
load()
|
||||
@@ -174,9 +168,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-users { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.desc { font-size: 13px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #ebeef5; }
|
||||
.filter-bar { margin-bottom: 16px; }
|
||||
.pager { margin-top: 16px; text-align: right; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div class="admin-workbench">
|
||||
|
||||
<!-- 标题 -->
|
||||
<h2 class="page-title">系统总览</h2>
|
||||
<div class="page-card admin-workbench">
|
||||
<div class="breadcrumb">首页</div>
|
||||
|
||||
<!-- KPI 卡: 用户/项目/会议/已结题 -->
|
||||
<div class="stats-grid">
|
||||
@@ -70,7 +68,7 @@ async function load() {
|
||||
for (const code of codes) {
|
||||
try {
|
||||
const r = await listUser({ pageNum: 1, pageSize: 1, roleType: code })
|
||||
all.push({ code, label: ROLE_LABEL[code], count: r.total || 0 })
|
||||
all.push({ code, label: ROLE_LABEL[code], count: (r.data && r.data.total) || r.total || 0 })
|
||||
} catch (e) {
|
||||
all.push({ code, label: ROLE_LABEL[code], count: 0 })
|
||||
}
|
||||
@@ -79,13 +77,13 @@ async function load() {
|
||||
|
||||
// 项目数
|
||||
const ps = await bizList('project', { pageNum: 1, pageSize: 1 })
|
||||
stats.value.totalProjects = ps.total || ps.data?.total || 0
|
||||
stats.value.totalProjects = ps.total || (ps.data && ps.data.total) || 0
|
||||
// 已结题
|
||||
const fp = await bizList('project', { pageNum: 1, pageSize: 1, isFinished: '1' })
|
||||
stats.value.finishedProjects = fp.total || fp.data?.total || 0
|
||||
stats.value.finishedProjects = fp.total || (fp.data && fp.data.total) || 0
|
||||
// 会议数
|
||||
const ms = await bizList('meeting', { pageNum: 1, pageSize: 1 })
|
||||
stats.value.totalMeetings = ms.total || ms.data?.total || 0
|
||||
stats.value.totalMeetings = ms.total || (ms.data && ms.data.total) || 0
|
||||
} catch (e) {
|
||||
console.warn('admin workbench load failed', e)
|
||||
}
|
||||
@@ -95,18 +93,72 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.admin-workbench { padding: 16px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; color: #1a1a1a; margin-bottom: 16px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 12px; }
|
||||
.stat-card { background: #fff; border-radius: 4px; padding: 24px; border: 1px solid #ebeef5; cursor: pointer; text-decoration: none; color: inherit; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: opacity .15s, border-color .15s; min-height: 120px; }
|
||||
.stat-card:hover { border-color: var(--brand-primary); opacity: .9; }
|
||||
.stat-label { font-size: 14px; color: #606266; margin-bottom: 16px; }
|
||||
.stat-value { font-size: 32px; font-weight: 600; color: var(--brand-primary); line-height: 1.2; }
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
min-height: 120px;
|
||||
}
|
||||
.stat-card:hover { border-color: var(--brand-primary); box-shadow: 0 2px 8px rgba(30,58,138,0.15); }
|
||||
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0 20px; text-align: center; }
|
||||
.section { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; margin-top: 16px; }
|
||||
.section-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 16px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.role-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; }
|
||||
.role-card { background: #fafbfc; border: 1px solid #ebeef5; border-radius: 4px; padding: 16px; text-align: center; }
|
||||
.role-name { font-size: 13px; color: #606266; margin-bottom: 8px; }
|
||||
.role-count { font-size: 24px; font-weight: 600; color: var(--brand-primary); }
|
||||
.role-card {
|
||||
background: #fafbfc;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.role-name { font-size: 13px; color: #8c8c8c; margin-bottom: 8px; }
|
||||
.role-count {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
@@ -37,7 +37,7 @@ const detailOpen = ref(false)
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const r = await listMyMessages({ limit: 50 }); const list = r.rows
|
||||
const r = await listMyMessages({ limit: 50 }); const list = (r.data && r.data.rows) || r.rows || []
|
||||
rows.value = list.map(a => ({
|
||||
id: a.id,
|
||||
category: a.category,
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<div class="executor-account">
|
||||
<div class="page-card executor-account">
|
||||
<div class="breadcrumb">首页 / 账号信息</div>
|
||||
<div class="page-card">
|
||||
<el-form :model="form" label-width="120px" style="max-width:520px;">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="原密码">
|
||||
<el-input v-model="form.oldPwd" type="password" show-password placeholder="请输入原密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input v-model="form.newPwd" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码">
|
||||
<el-input v-model="form.confirmPwd" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
<el-button @click="onCancel">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<p class="page-sub">管理账号信息, 修改密码或手机号</p>
|
||||
|
||||
<el-form :model="form" label-width="120px" class="account-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="原密码">
|
||||
<el-input v-model="form.oldPwd" type="password" show-password placeholder="请输入原密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input v-model="form.newPwd" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码">
|
||||
<el-input v-model="form.confirmPwd" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
<el-button @click="onCancel">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -68,9 +68,9 @@ function onCancel() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.executor-account { padding: 0; }
|
||||
.breadcrumb { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.page-card { background: #fff; border-radius: 4px; padding: 24px; border: 1px solid #ebeef5; }
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep); border-color: var(--brand-primary-deep); }
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.executor-account { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
|
||||
.account-form { max-width: 520px; }
|
||||
</style>
|
||||
@@ -1,54 +1,52 @@
|
||||
<template>
|
||||
<div class="executor-labor">
|
||||
<div class="page-card executor-labor">
|
||||
<div class="breadcrumb">首页 / 劳务凭证</div>
|
||||
<p class="page-sub">管理会务执行产生的劳务凭证, 上传付款凭证 PDF</p>
|
||||
|
||||
<div class="filter-card">
|
||||
<el-form :inline="true" :model="q" size="default">
|
||||
<el-form-item label="会议名称:">
|
||||
<el-input v-model="q.meetingName" clearable placeholder="输入会议名称" style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="专家姓名:">
|
||||
<el-input v-model="q.expertName" clearable placeholder="输入专家姓名" style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 红字提示 (与 People.vue 一致) -->
|
||||
<div class="hint-list">
|
||||
<p>*结算前, 执行方和支持方无法看到凭证材料菜单, 结算后才能看到</p>
|
||||
<p>*结算后, 已完结状态, 执行方不能再编辑</p>
|
||||
</div>
|
||||
|
||||
<div class="page-card">
|
||||
<p class="tip">*结算前,执行方和支持方无法看到凭证材料菜单,结算后,才能看到结算材料</p>
|
||||
<p class="tip">*结算后,已完结状态 ,执行方不能再进行编辑</p>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="会议名称"><el-input v-model="q.meetingName" clearable placeholder="输入会议名称" style="width:200px" /></el-form-item>
|
||||
<el-form-item label="专家姓名"><el-input v-model="q.expertName" clearable placeholder="输入专家姓名" style="width:160px" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="voucherNo" label="凭证编号" width="140" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="expertName" label="专家姓名" width="110" />
|
||||
<el-table-column prop="idCard" label="身份证号" width="180" />
|
||||
<el-table-column prop="bankAccount" label="银行账号" width="220" />
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="submitDate" label="提交日期" width="160" />
|
||||
<el-table-column label="付款凭证.pdf" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="row.paymentVoucherUrl" type="primary" @click="previewVoucher(row.paymentVoucherUrl)">查看</el-link>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="success" @click="onUploadVoucher(row)">上传付款凭证</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
|
||||
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load" @size-change="load" />
|
||||
</div>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="voucherNo" label="凭证编号" width="140" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="expertName" label="专家姓名" width="110" />
|
||||
<el-table-column prop="idCard" label="身份证号" width="180" />
|
||||
<el-table-column prop="bankAccount" label="银行账号" width="220" />
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="submitDate" label="提交日期" width="160" />
|
||||
<el-table-column label="付款凭证.pdf" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="row.paymentVoucherUrl" type="primary" @click="previewVoucher(row.paymentVoucherUrl)">查看</el-link>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="success" @click="onUploadVoucher(row)">上传付款凭证</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
|
||||
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load" @size-change="load" />
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="detailOpen" title="劳务凭证详情" width="640px">
|
||||
@@ -198,18 +196,24 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.executor-labor { padding: 0; }
|
||||
.breadcrumb { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.filter-card, .page-card { background: #fff; border-radius: 4px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.tip { font-size: 12px; color: #f56c6c; margin: 4px 0; }
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.executor-labor { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep); border-color: var(--brand-primary-deep); }
|
||||
:deep(.el-table .el-button) { border-radius: 4px; }
|
||||
:deep(.el-table .el-button.is-link) { color: var(--brand-primary); background: transparent; border: none; }
|
||||
:deep(.el-table .el-button.is-link:hover) { color: var(--brand-primary-deep, var(--brand-primary)); background: transparent; }
|
||||
:deep(.el-table .el-button.is-link.is-disabled) { color: #c0c4cc; background: transparent; }
|
||||
|
||||
:deep(.el-button--warning) { background: #e6a23c; border-color: #e6a23c; border-radius: 4px; }
|
||||
:deep(.el-table .el-button) { border-radius: 4px; }
|
||||
/* 红字提示 (与 People.vue 一致) */
|
||||
.hint-list {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
color: #ff4d4f;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.hint-list p { margin-bottom: 4px; }
|
||||
.hint-list p:last-child { margin-bottom: 0; }
|
||||
</style>
|
||||
@@ -1,80 +1,70 @@
|
||||
<template>
|
||||
<div class="executor-meetings">
|
||||
<div class="page-card executor-meetings">
|
||||
<div class="breadcrumb">首页 / 会议列表</div>
|
||||
<p class="page-sub">查看执行方参与的会议, 提交监督意见</p>
|
||||
|
||||
<div class="filter-card">
|
||||
<el-form :inline="true" :model="q" size="default">
|
||||
<el-form-item label="项目编号:">
|
||||
<el-input v-model="q.projectNo" clearable placeholder="输入项目编号" style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="会议名称:">
|
||||
<el-input v-model="q.meetingName" clearable placeholder="输入会议名称" style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否系列会:">
|
||||
<el-select v-model="q.isSeries" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="是" value="1" />
|
||||
<el-option label="否" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题:">
|
||||
<el-select v-model="q.isFinished" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="已结题" value="1" />
|
||||
<el-option label="未结题" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="期数:">
|
||||
<el-input v-model="q.sessionNo" clearable placeholder="输入期数" style="width:100px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" clearable placeholder="输入项目编号" style="width:160px" /></el-form-item>
|
||||
<el-form-item label="会议名称"><el-input v-model="q.meetingName" clearable placeholder="输入会议名称" style="width:200px" /></el-form-item>
|
||||
<el-form-item label="是否系列会">
|
||||
<el-select v-model="q.isSeries" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="是" value="1" />
|
||||
<el-option label="否" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题">
|
||||
<el-select v-model="q.isFinished" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="已结题" value="1" />
|
||||
<el-option label="未结题" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="期数"><el-input v-model="q.sessionNo" clearable placeholder="输入期数" style="width:100px" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="page-card">
|
||||
<div class="toolbar">
|
||||
<span class="hint">已选 {{ selected.length }} 条</span>
|
||||
</div>
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelect">
|
||||
<el-table-column type="selection" width="48" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="120" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="isSeries" label="是否系列会" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isSeries === '1' || row.isSeries === '是' ? 'success' : 'info'" size="small">
|
||||
{{ row.isSeries === '1' || row.isSeries === '是' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="seriesTopic" label="系列会主题" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="期数/总期数" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.periodNo || 0 }}/{{ row.totalPeriods || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentStage" label="当前阶段" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.currentStage === '执行中' ? 'success' : 'info'" size="small">{{ row.currentStage || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isFinished" label="是否结题" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isFinished === '1' || row.isFinished === '已结题' ? 'warning' : 'info'" size="small">
|
||||
{{ row.isFinished === '1' || row.isFinished === '已结题' ? '已结题' : '未结题' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="warning" @click="onSupervision(row)">查看监督意见</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
|
||||
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load" @size-change="load" />
|
||||
</div>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelect">
|
||||
<el-table-column type="selection" width="48" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="120" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="isSeries" label="是否系列会" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isSeries === '1' || row.isSeries === '是' ? 'success' : 'info'" size="small">
|
||||
{{ row.isSeries === '1' || row.isSeries === '是' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="seriesTopic" label="系列会主题" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="期数/总期数" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.periodNo || 0 }}/{{ row.totalPeriods || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentStage" label="当前阶段" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.currentStage === '执行中' ? 'success' : 'info'" size="small">{{ row.currentStage || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isFinished" label="是否结题" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isFinished === '1' || row.isFinished === '已结题' ? 'warning' : 'info'" size="small">
|
||||
{{ row.isFinished === '1' || row.isFinished === '已结题' ? '已结题' : '未结题' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="warning" @click="onSupervision(row)">查看监督意见</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
|
||||
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load" @size-change="load" />
|
||||
</div>
|
||||
|
||||
<!-- 监督意见 弹窗 -->
|
||||
@@ -182,16 +172,10 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.executor-meetings { padding: 0; }
|
||||
.breadcrumb { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.filter-card, .page-card { background: #fff; border-radius: 4px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
||||
.hint { font-size: 13px; color: #909399; }
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.executor-meetings { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep); border-color: var(--brand-primary-deep); }
|
||||
:deep(.el-table .el-button) { border-radius: 4px; }
|
||||
:deep(.el-table .el-button.is-link) { color: var(--brand-primary); background: transparent; border: none; }
|
||||
:deep(.el-table .el-button.is-link:hover) { color: var(--brand-primary-deep); background: transparent; }
|
||||
:deep(.el-table .el-button.is-link.is-disabled) { color: #c0c4cc; background: transparent; }
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="executor-overview">
|
||||
<h1 class="page-title">首页</h1>
|
||||
<p class="hint">*点击数字,跳转到相应的列表页</p>
|
||||
<div class="page-card executor-overview">
|
||||
<div class="breadcrumb">首页</div>
|
||||
<h1 class="page-title" style="display:none"></h1>
|
||||
|
||||
<div class="stats-grid">
|
||||
<router-link class="stat-card" to="/executor/projects">
|
||||
@@ -62,16 +62,52 @@ onMounted(loadStats)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.executor-overview { padding: 0; }
|
||||
.page-title { font-size: 22px; font-weight: 600; color: #1a1a1a; margin: 0 0 8px; }
|
||||
.hint { font-size: 12px; color: #f56c6c; margin: 0 0 16px; }
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.executor-overview { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-title { display: none; }
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; }
|
||||
.stat-card { background: #fff; border: 1px solid #ebeef5; border-radius: 4px; padding: 32px 24px; text-decoration: none; color: inherit; display: block; text-align: center; }
|
||||
.stat-card:hover { border-color: var(--brand-primary); }
|
||||
.stat-label { font-size: 14px; color: #909399; margin-bottom: 16px; }
|
||||
.stat-value { font-size: 40px; font-weight: 700; color: var(--brand-primary); line-height: 1; }
|
||||
.section { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.section-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin: 0 0 16px; }
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 32px 24px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
text-align: center;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.stat-card:hover { border-color: var(--brand-primary); box-shadow: 0 2px 8px rgba(30,58,138,0.15); }
|
||||
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.stat-value {
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* 消息通知 */
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 16px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
.notice-list { list-style: none; padding: 0; margin: 0; }
|
||||
.notice-item { padding: 12px 0; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; gap: 12px; }
|
||||
.notice-item:last-child { border-bottom: none; }
|
||||
|
||||
@@ -1,87 +1,82 @@
|
||||
<template>
|
||||
<div class="executor-projects">
|
||||
<div class="page-card executor-projects">
|
||||
<div class="breadcrumb">首页 / 项目列表</div>
|
||||
<p class="page-sub">查看执行方承接的项目, 进行项目分配与评价</p>
|
||||
|
||||
<div class="filter-card">
|
||||
<el-form :inline="true" :model="query" size="default">
|
||||
<el-form-item label="项目编号:">
|
||||
<el-input v-model="query.projectNo" clearable placeholder="项目编号" style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称:">
|
||||
<el-input v-model="query.projectName" clearable placeholder="项目名称" style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目时间:">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" style="width:240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题:">
|
||||
<el-select v-model="query.isFinished" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="已结题" value="1" />
|
||||
<el-option label="未结题" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadList">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="query" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="query.projectNo" clearable placeholder="项目编号" style="width:160px" /></el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="query.projectName" clearable placeholder="项目名称" style="width:200px" /></el-form-item>
|
||||
<el-form-item label="项目时间">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" style="width:240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题">
|
||||
<el-select v-model="query.isFinished" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="已结题" value="1" />
|
||||
<el-option label="未结题" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadList">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量按钮 (与 People.vue 风格一致) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" :disabled="!selected.length" @click="onAssign">项目分配</el-button>
|
||||
<el-button type="primary" :disabled="!selected.length" @click="onBatchRate">批量评价</el-button>
|
||||
<span class="filter-tip">已选 {{ selected.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<div class="page-card">
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" :disabled="!selected.length" @click="onAssign">项目分配</el-button>
|
||||
<el-button type="primary" :disabled="!selected.length" @click="onBatchRate">批量评价</el-button>
|
||||
<span class="hint">已选 {{ selected.length }} 条</span>
|
||||
<span class="tip">*支持方管理员在分配项目的时候,只能分配监察员</span>
|
||||
</div>
|
||||
<el-table :data="list" border stripe v-loading="loading" @selection-change="sel=>selected=sel">
|
||||
<el-table-column type="selection" width="48" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="120" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="总场次/总期数" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.totalSessions || 0 }}/{{ row.totalPeriods || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="doneSessions" label="已执行" width="80" align="center" />
|
||||
<el-table-column prop="todoSessions" label="未执行" width="80" align="center" />
|
||||
<el-table-column label="总金额" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.totalAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可用金额" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.availableAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已支付劳务费" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.paidLaborAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已支付会务费" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.paidMeetingAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行单位评价(支持方)" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-rate v-model="row.ratingScore" disabled show-score :max="5" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isFinished" label="是否结题" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isFinished === '1' || row.isFinished === '已结题' ? 'warning' : 'info'" size="small">
|
||||
{{ row.isFinished === '1' || row.isFinished === '已结题' ? '已结题' : '未结题' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="项目开始时间" width="150" />
|
||||
<el-table-column prop="endTime" label="项目结束时间" width="150" />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="viewDetail(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onAssign(row)">分配</el-button>
|
||||
<el-button link type="primary" @click="onRate(row)">项目评价</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
|
||||
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadList" @size-change="loadList" />
|
||||
</div>
|
||||
<p class="footnote">*点击后可查看具体评价维度,评价标准与供应商系统保持一致</p>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致) ========== -->
|
||||
<el-table :data="list" border stripe v-loading="loading" @selection-change="sel=>selected=sel">
|
||||
<el-table-column type="selection" width="48" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="120" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="总场次/总期数" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.totalSessions || 0 }}/{{ row.totalPeriods || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="doneSessions" label="已执行" width="80" align="center" />
|
||||
<el-table-column prop="todoSessions" label="未执行" width="80" align="center" />
|
||||
<el-table-column label="总金额" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.totalAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可用金额" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.availableAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已支付劳务费" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.paidLaborAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已支付会务费" width="120" align="right">
|
||||
<template #default="{ row }">¥ {{ formatNum(row.paidMeetingAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行单位评价(支持方)" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-rate v-model="row.ratingScore" disabled show-score :max="5" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isFinished" label="是否结题" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isFinished === '1' || row.isFinished === '已结题' ? 'warning' : 'info'" size="small">
|
||||
{{ row.isFinished === '1' || row.isFinished === '已结题' ? '已结题' : '未结题' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="项目开始时间" width="150" />
|
||||
<el-table-column prop="endTime" label="项目结束时间" width="150" />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="viewDetail(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onAssign(row)">分配</el-button>
|
||||
<el-button link type="primary" @click="onRate(row)">项目评价</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
|
||||
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadList" @size-change="loadList" />
|
||||
</div>
|
||||
|
||||
<!-- 项目分配 弹窗 -->
|
||||
@@ -182,8 +177,8 @@ async function loadList() {
|
||||
params.endTime = dateRange.value[1]
|
||||
}
|
||||
const r = await bizList('project', params)
|
||||
list.value = r.rows || []
|
||||
page.total = r.total || 0
|
||||
list.value = (r.data && r.data.rows) || r.rows || []
|
||||
page.total = (r.data && r.data.total) || r.total || 0
|
||||
} catch (e) { list.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
@@ -200,7 +195,7 @@ function reset() {
|
||||
function viewDetail(row) { detail.value = row; detailOpen.value = true }
|
||||
|
||||
async function loadPeople() {
|
||||
try { const r = await bizList('person', { pageNum: 1, pageSize: 200 }); people.value = (r.rows || []).map(p => ({ userId: p.personId, name: p.name })) }
|
||||
try { const r = await bizList('person', { pageNum: 1, pageSize: 200 }); people.value = ((r.data && r.data.rows) || r.rows || []).map(p => ({ userId: p.personId, name: p.name })) }
|
||||
catch { people.value = [] }
|
||||
}
|
||||
|
||||
@@ -259,17 +254,12 @@ onMounted(() => { loadList(); loadPeople() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.executor-projects { padding: 0; }
|
||||
.breadcrumb { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.filter-card, .page-card { background: #fff; border-radius: 4px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||
.hint { font-size: 13px; color: #909399; }
|
||||
.tip { font-size: 12px; color: #f56c6c; margin-left: 12px; }
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.executor-projects { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
|
||||
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
.footnote { font-size: 12px; color: #f56c6c; margin-top: 8px; }
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep); border-color: var(--brand-primary-deep); }
|
||||
:deep(.el-table .el-button) { border-radius: 4px; }
|
||||
:deep(.el-table .el-button.is-link) { color: var(--brand-primary); }
|
||||
:deep(.el-table .el-button.is-link:hover) { color: var(--brand-primary-deep); }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
</style>
|
||||
@@ -81,7 +81,7 @@ async function loadStats() {
|
||||
|
||||
async function loadNotices() {
|
||||
try {
|
||||
const r = await listMyMessages({ limit: 50 }); const list = r.rows
|
||||
const r = await listMyMessages({ limit: 50 }); const list = (r.data && r.data.rows) || r.rows || []
|
||||
notices.value = list.map(n => ({
|
||||
id: n.id,
|
||||
title: n.title,
|
||||
|
||||
@@ -149,8 +149,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await bizList('meeting', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = r.rows || []
|
||||
page.total = r.total || 0
|
||||
rows.value = (r.data && r.data.rows) || r.rows || []
|
||||
page.total = (r.data && r.data.total) || r.total || 0
|
||||
} catch (e) {
|
||||
rows.value = []
|
||||
page.total = 0
|
||||
|
||||
@@ -219,8 +219,8 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await bizList('project', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = r.rows || []
|
||||
page.total = r.total || 0
|
||||
rows.value = (r.data && r.data.rows) || r.rows || []
|
||||
page.total = (r.data && r.data.total) || r.total || 0
|
||||
} catch (e) { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
@@ -228,7 +228,7 @@ async function load() {
|
||||
async function loadPersons() {
|
||||
try {
|
||||
const r = await bizList('person', { pageNum: 1, pageSize: 100 })
|
||||
personList.value = r.rows || []
|
||||
personList.value = (r.data && r.data.rows) || r.rows || []
|
||||
} catch (e) { personList.value = [] }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<template>
|
||||
<div class="manager-accounts">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看当前登录账号、修改个人信息与密码</div>
|
||||
<el-descriptions :column="2" border style="margin-bottom: 24px">
|
||||
<el-descriptions-item label="账号">{{ profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ profile.nickName || profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ (profile.roleGroup || '').split(',')[0] || '合规人员' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ profile.status === '0' ? '正常' : '停用' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ profile.phonenumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ profile.email || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">{{ profile.createTime || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="page-card manager-accounts">
|
||||
<div class="breadcrumb">首页 / 账号管理</div>
|
||||
<p class="page-sub">查看当前登录账号、修改个人信息与密码</p>
|
||||
|
||||
<el-form :model="form" label-width="120px" style="max-width:600px" :rules="rules" ref="formRef">
|
||||
<div class="section-title">修改个人信息</div>
|
||||
<el-form-item label="姓名" prop="nickName"><el-input v-model="form.nickName" placeholder="请输入姓名" /></el-form-item>
|
||||
<el-form-item label="手机号" prop="phonenumber"><el-input v-model="form.phonenumber" placeholder="请输入手机号" /></el-form-item>
|
||||
<el-form-item label="邮箱" prop="email"><el-input v-model="form.email" placeholder="请输入邮箱" /></el-form-item>
|
||||
<el-descriptions :column="2" border style="margin-bottom: 24px">
|
||||
<el-descriptions-item label="账号">{{ profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ profile.nickName || profile.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ (profile.roleGroup || '').split(',')[0] || '合规人员' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ profile.status === '0' ? '正常' : '停用' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ profile.phonenumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ profile.email || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">{{ profile.createTime || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="section-title">修改密码</div>
|
||||
<el-form-item label="原密码" prop="oldPassword"><el-input v-model="form.oldPassword" type="password" show-password placeholder="请输入原密码" /></el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword"><el-input v-model="form.newPassword" type="password" show-password placeholder="请输入新密码" /></el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword"><el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入新密码" /></el-form-item>
|
||||
<div class="form-tip">* 密码长度 6-20 位,支持数字、字母、特殊字符;留空表示不修改密码</div>
|
||||
<el-form-item><el-button type="primary" :loading="saving" @click="onSave">保存</el-button><el-button @click="onCancel">取消</el-button></el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-form :model="form" label-width="120px" style="max-width:600px" :rules="rules" ref="formRef">
|
||||
<div class="section-title">修改个人信息</div>
|
||||
<el-form-item label="姓名" prop="nickName"><el-input v-model="form.nickName" placeholder="请输入姓名" /></el-form-item>
|
||||
<el-form-item label="手机号" prop="phonenumber"><el-input v-model="form.phonenumber" placeholder="请输入手机号" /></el-form-item>
|
||||
<el-form-item label="邮箱" prop="email"><el-input v-model="form.email" placeholder="请输入邮箱" /></el-form-item>
|
||||
|
||||
<div class="section-title">修改密码</div>
|
||||
<el-form-item label="原密码" prop="oldPassword"><el-input v-model="form.oldPassword" type="password" show-password placeholder="请输入原密码" /></el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword"><el-input v-model="form.newPassword" type="password" show-password placeholder="请输入新密码" /></el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword"><el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入新密码" /></el-form-item>
|
||||
<div class="filter-tip">* 密码长度 6-20 位,支持数字、字母、特殊字符;留空表示不修改密码</div>
|
||||
<el-form-item><el-button type="primary" :loading="saving" @click="onSave">保存</el-button><el-button @click="onCancel">取消</el-button></el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -92,9 +92,15 @@ onMounted(loadProfile)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-accounts { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 6px; padding: 24px; border: 1px solid #ebeef5; }
|
||||
.desc { color: #909399; font-size: 13px; margin-bottom: 16px; }
|
||||
.section-title { font-weight: 600; margin: 16px 0 12px; padding-left: 8px; border-left: 3px solid var(--brand-primary); }
|
||||
.form-tip { color: #909399; font-size: 12px; margin-bottom: 12px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
|
||||
.filter-tip { color: #909399; font-size: 12px; margin-bottom: 12px; }
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin: 16px 0 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,39 +1,41 @@
|
||||
<template>
|
||||
<div class="manager-exec-intent">
|
||||
<div class="page-card">
|
||||
<div class="desc">查看专家对项目的执行意向(含待签约/已签约/已拒绝/已退出)</div>
|
||||
<el-form inline :model="q">
|
||||
<el-form-item label="项目编号:"><el-input v-model="q.projectNo" clearable /></el-form-item>
|
||||
<el-form-item label="项目名称:"><el-input v-model="q.projectName" clearable /></el-form-item>
|
||||
<el-form-item label="姓名:"><el-input v-model="q.name" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位:"><el-input v-model="q.workUnit" clearable /></el-form-item>
|
||||
<el-form-item label="手机号:"><el-input v-model="q.phone" clearable /></el-form-item>
|
||||
<el-form-item label="签约状态:">
|
||||
<el-select v-model="q.onboardStatus" clearable>
|
||||
<el-option label="待签约" value="待签约" /><el-option label="已签约" value="已签约" />
|
||||
<el-option label="已拒绝" value="已拒绝" /><el-option label="已退出" value="已退出" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="load">查找</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="page-card">
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="projectNo" label="意向项目编号" width="140" />
|
||||
<el-table-column prop="projectName" label="意向项目名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="name" label="姓名" width="100" />
|
||||
<el-table-column prop="workUnit" label="工作单位" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" width="120" />
|
||||
<el-table-column prop="position" label="职务" width="100" />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
<el-table-column label="签约状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.onboardStatus==='已签约' ? 'success' : row.onboardStatus==='已拒绝' ? 'danger' : row.onboardStatus==='已退出' ? 'info' : 'warning'">{{ row.onboardStatus }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
</el-table>
|
||||
<div class="page-card manager-exec-intent">
|
||||
<div class="breadcrumb">首页 / 执行意向</div>
|
||||
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" clearable /></el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="q.projectName" clearable /></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="q.name" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" clearable /></el-form-item>
|
||||
<el-form-item label="手机号"><el-input v-model="q.phone" clearable /></el-form-item>
|
||||
<el-form-item label="签约状态">
|
||||
<el-select v-model="q.onboardStatus" clearable>
|
||||
<el-option label="待签约" value="待签约" /><el-option label="已签约" value="已签约" />
|
||||
<el-option label="已拒绝" value="已拒绝" /><el-option label="已退出" value="已退出" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="load">查找</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="projectNo" label="意向项目编号" width="140" />
|
||||
<el-table-column prop="projectName" label="意向项目名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="name" label="姓名" width="100" />
|
||||
<el-table-column prop="workUnit" label="工作单位" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" width="120" />
|
||||
<el-table-column prop="position" label="职务" width="100" />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
<el-table-column label="签约状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.onboardStatus==='已签约' ? 'success' : row.onboardStatus==='已拒绝' ? 'danger' : row.onboardStatus==='已退出' ? 'info' : 'warning'">{{ row.onboardStatus }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,8 +64,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-exec-intent { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 6px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
|
||||
.desc { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 服务机构查看 (manager/executor-orgs)
|
||||
说明: 原 manager/Orgs.vue 拆出 executor 一支, 仅查看
|
||||
接口: bizList('org', q)
|
||||
-->
|
||||
<div class="page-card manager-executor-orgs">
|
||||
<div class="breadcrumb">首页 / 服务机构管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable>
|
||||
<el-option label="0正常" value="0" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="1禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.createTime ? String(row.createTime).slice(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="view(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="goPeople(row)">人员管理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="detailOpen" title="服务机构详情" width="640px" v-if="currentRow">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="公司类型">
|
||||
<el-tag type="primary">执行方</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司名称">{{ currentRow.orgName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="公司地址">{{ currentRow.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="税号/统一社会信用代码">{{ currentRow.taxNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合作状态">
|
||||
<el-tag :type="statusTagType(currentRow.status)">{{ currentRow.status }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建日期">{{ currentRow.createTime ? String(currentRow.createTime).slice(0,10) : '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ currentRow.contactName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ currentRow.contactPhone || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailOpen=false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList } from '@/api/public'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = ref({ orgType: 'executor', orgName: '', taxNo: '', status: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
const currentRow = ref(null)
|
||||
const detailOpen = ref(false)
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '正常' || s === '0' || s === '合作中') return 'success'
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []; page.total = data?.total || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() { q.value = { orgType: 'executor', orgName: '', taxNo: '', status: '' }; page.pageNum = 1; load() }
|
||||
function view(row) { currentRow.value = row; detailOpen.value = true }
|
||||
function goPeople(row) {
|
||||
router.push({ path: '/manager/executor-people', query: { orgId: row.orgId, orgName: row.orgName } })
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-executor-orgs { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 服务机构下人员 (manager/executor-people)
|
||||
入口: org 表格行内「人员管理」按钮 (router.push 带 orgId/orgName)
|
||||
接口: bizList('person', q) / bizUpdate / bizAdd / executorImport / executorImportTemplate
|
||||
-->
|
||||
<div class="page-card manager-executor-people">
|
||||
<div class="breadcrumb">
|
||||
首页 / 服务机构管理 / 人员管理{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="姓名" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="手机号" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称">
|
||||
<el-input v-model="q.orgName" placeholder="企业名称" clearable style="width:180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="q.department" placeholder="部门" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="q.position" placeholder="职务" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建人员</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<span class="filter-tip">*批量导入模板与列表表项一致</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||||
<el-table-column prop="orgName" label="企业名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" min-width="100" />
|
||||
<el-table-column prop="position" label="职务" min-width="100" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status || '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '启用' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择文件">
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
|
||||
<el-button>选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
|
||||
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '禁用' || row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...q,
|
||||
orgType: 'executor',
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
pageNum: page.pageNum,
|
||||
pageSize: page.pageSize,
|
||||
}
|
||||
const { data } = await bizList('person', params)
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(q, { name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '正常' : '禁用'
|
||||
const label = disabled ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.name}」吗?`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizUpdate('person', { personId: row.personId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
// ========== 新建人员 ==========
|
||||
function goNew() {
|
||||
router.push({
|
||||
path: '/manager/executor-people/new',
|
||||
query: {
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
...(orgName.value ? { orgName: orgName.value } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 批量导入 ==========
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importFile = ref(null)
|
||||
|
||||
function openImport() { importFile.value = null; importOpen.value = true }
|
||||
function onFileChange(file) { importFile.value = file.raw }
|
||||
function onFileRemove() { importFile.value = null }
|
||||
async function downloadTemplate() {
|
||||
try {
|
||||
const res = await downloadImportTemplate('executor')
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '服务机构人员导入模板.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch { ElMessage.error('模板下载失败') }
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
||||
importing.value = true
|
||||
try {
|
||||
const res = await importPerson('executor', importFile.value)
|
||||
const payload = res.data || res
|
||||
const ok = payload.ok ?? 0
|
||||
const total = payload.total ?? 0
|
||||
const failed = (payload.results || []).filter(r => !r.ok)
|
||||
if (failed.length === 0) {
|
||||
ElMessage.success(`导入成功 ${ok}/${total}`)
|
||||
} else {
|
||||
ElMessageBox.alert(
|
||||
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
|
||||
failed.map(f => ` 第${f.rowNo}行 ${f.name || ''}: ${f.message}`).join('\n'),
|
||||
'导入结果', { type: 'warning' }
|
||||
)
|
||||
}
|
||||
importOpen.value = false
|
||||
load()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '导入失败')
|
||||
} finally {
|
||||
importing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-executor-people { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="page-card">
|
||||
<div class="desc">审核专家注册信息</div>
|
||||
<div class="page-card manager-experts">
|
||||
<div class="breadcrumb">首页 / 专家审核</div>
|
||||
|
||||
<!-- ========== 筛选区 (按原型 top=174: 姓名/手机号/工作单位/审核状态) ========== -->
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="" clearable style="width:160px" />
|
||||
@@ -15,9 +15,10 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="审核状态">
|
||||
<el-select v-model="q.auditStatus" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="待审核" value="0" />
|
||||
<el-option label="通过" value="1" />
|
||||
<el-option label="退回" value="2" />
|
||||
<el-option label="未提交" value="0" />
|
||||
<el-option label="待审核" value="1" />
|
||||
<el-option label="通过" value="2" />
|
||||
<el-option label="拒绝" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -25,10 +26,8 @@
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- ========== 批量按钮区 (按原型 top=226: 新建专家/批量导入/批量审核/批量导入执业证书/批量导入职称证明) ========== -->
|
||||
<div class="page-card">
|
||||
<!-- ========== 批量按钮区 (与 People.vue 风格一致) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="openAdd">新建专家</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
@@ -65,7 +64,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="['stage-tag', auditStatusClass(row.auditStatus)]">{{ auditStatusText(row.auditStatus) }}</span>
|
||||
<audit-status-tag :status="row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="auditTime" label="审批时间" width="160" align="center">
|
||||
@@ -77,16 +76,18 @@
|
||||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link :type="row.status === '1' ? 'danger' : 'success'" @click="row.status === '1' ? onDisable(row) : onEnable(row)">
|
||||
{{ row.status === '1' ? '禁用' : '恢复' }}
|
||||
<el-button link :type="row.status === 'N' ? 'success' : 'danger'" @click="row.status === 'N' ? onEnable(row) : onDisable(row)">
|
||||
{{ row.status === 'N' ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
<el-button link type="info" v-if="row.auditStatus === '2'" @click="onViewAuditOpinion(row)">已退回</el-button>
|
||||
<el-button link type="info" v-if="row.auditStatus === '3'" @click="onViewAuditOpinion(row)">已拒绝</el-button>
|
||||
<el-button link type="primary" v-if="row.practiceCertUrl || row.titleCertUrl || row.resumeUrl" @click="onDownloadAll(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== Dialog: 新建/编辑专家 (按原型 top=239 确定/取消) ========== -->
|
||||
@@ -121,8 +122,8 @@
|
||||
<el-form :model="auditForm" label-width="100px">
|
||||
<el-form-item label="审核结果">
|
||||
<el-radio-group v-model="auditForm.result">
|
||||
<el-radio value="1">通过</el-radio>
|
||||
<el-radio value="2">退回</el-radio>
|
||||
<el-radio value="2">通过</el-radio>
|
||||
<el-radio value="3">拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见">
|
||||
@@ -144,8 +145,8 @@
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="审核结果">
|
||||
<el-radio-group v-model="importAuditResult">
|
||||
<el-radio value="1">通过</el-radio>
|
||||
<el-radio value="2">退回</el-radio>
|
||||
<el-radio value="2">通过</el-radio>
|
||||
<el-radio value="3">拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见">
|
||||
@@ -190,7 +191,7 @@
|
||||
<el-descriptions-item label="工作单位">{{ currentRow.workUnit }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科室">{{ currentRow.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职称">{{ currentRow.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">{{ auditStatusText(currentRow.auditStatus) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态"><audit-status-tag :status="currentRow.auditStatus" /></el-descriptions-item>
|
||||
<el-descriptions-item label="执业证书">
|
||||
<el-link v-if="currentRow.practiceCertUrl" type="primary" @click="onViewCert(currentRow.practiceCertUrl)">查看证书</el-link>
|
||||
<span v-else>-</span>
|
||||
@@ -208,6 +209,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { bizList, bizAdd, bizUpdate } from '@/api/public'
|
||||
import AuditStatusTag from '@/components/AuditStatusTag.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const q = ref({ name: '', phone: '', workUnit: '', auditStatus: '' })
|
||||
@@ -233,17 +235,7 @@ function reset() {
|
||||
}
|
||||
function onSel(rs) { selection.value = rs }
|
||||
|
||||
// ========== 工具: 审核状态显示/颜色 ==========
|
||||
function auditStatusText(s) {
|
||||
if (s === '1') return '通过'
|
||||
if (s === '2') return '退回'
|
||||
return '待审核'
|
||||
}
|
||||
function auditStatusClass(s) {
|
||||
if (s === '1') return 'stage-done'
|
||||
if (s === '2') return 'stage-reject'
|
||||
return 'stage-reviewing'
|
||||
}
|
||||
// auditStatusText / auditStatusClass 已删除: 改用 AuditStatusTag 组件
|
||||
function fmtTime(d) {
|
||||
if (!d) return ''
|
||||
const dt = new Date(d)
|
||||
@@ -285,7 +277,7 @@ async function submitEdit() {
|
||||
await bizUpdate('expert', { ...editForm })
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await bizAdd('expert', { ...editForm, auditStatus: '1', auditBy: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ') })
|
||||
await bizAdd('expert', { ...editForm, auditStatus: '2', auditBy: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ') })
|
||||
ElMessage.success('新建成功')
|
||||
}
|
||||
editOpen.value = false
|
||||
@@ -296,12 +288,13 @@ async function submitEdit() {
|
||||
|
||||
// ========== 审核 dialog ==========
|
||||
const auditOpen = ref(false)
|
||||
const auditForm = reactive({ expertId: '', name: '', phone: '', result: '1', opinion: '' })
|
||||
// result: '2'=通过, '3'=拒绝 (BizAuditStatusEnum: 0未提交 1待审核 2通过 3拒绝)
|
||||
const auditForm = reactive({ expertId: '', name: '', phone: '', result: '2', opinion: '' })
|
||||
function onAudit(row) {
|
||||
auditForm.expertId = row.expertId
|
||||
auditForm.name = row.name
|
||||
auditForm.phone = row.phone
|
||||
auditForm.result = '1'
|
||||
auditForm.result = '2'
|
||||
auditForm.opinion = ''
|
||||
auditOpen.value = true
|
||||
}
|
||||
@@ -315,7 +308,7 @@ async function submitAudit() {
|
||||
auditBy: 'admin', // 后端应取登录用户
|
||||
auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
})
|
||||
ElMessage.success(auditForm.result === '1' ? '审核通过' : '已退回')
|
||||
ElMessage.success(auditForm.result === '2' ? '审核通过' : '已拒绝')
|
||||
auditOpen.value = false
|
||||
load()
|
||||
} catch { ElMessage.error('审核失败') }
|
||||
@@ -329,7 +322,7 @@ async function onBatchAudit() {
|
||||
submitting.value = true
|
||||
let ok = 0
|
||||
for (const r of selection.value) {
|
||||
try { await bizUpdate('expert', { expertId: r.expertId, auditStatus: '1', auditOpinion: '批量通过', auditBy: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ') }); ok++ } catch {}
|
||||
try { await bizUpdate('expert', { expertId: r.expertId, auditStatus: '2', auditOpinion: '批量通过', auditBy: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ') }); ok++ } catch {}
|
||||
}
|
||||
submitting.value = false
|
||||
ElMessage.success(`批量审核通过 ${ok}/${selection.value.length} 条`)
|
||||
@@ -339,10 +332,10 @@ async function onBatchAudit() {
|
||||
|
||||
// ========== 禁用/恢复 ==========
|
||||
async function onDisable(row) {
|
||||
try { await bizUpdate('expert', { expertId: row.expertId, status: '0' }); ElMessage.success('已禁用'); load() } catch { ElMessage.error('禁用失败') }
|
||||
try { await bizUpdate('expert', { expertId: row.expertId, status: 'N' }); ElMessage.success('已禁用'); load() } catch { ElMessage.error('禁用失败') }
|
||||
}
|
||||
async function onEnable(row) {
|
||||
try { await bizUpdate('expert', { expertId: row.expertId, status: '1' }); ElMessage.success('已恢复'); load() } catch { ElMessage.error('恢复失败') }
|
||||
try { await bizUpdate('expert', { expertId: row.expertId, status: 'Y' }); ElMessage.success('已恢复'); load() } catch { ElMessage.error('恢复失败') }
|
||||
}
|
||||
|
||||
// ========== 查看详情 ==========
|
||||
@@ -350,11 +343,11 @@ const viewOpen = ref(false)
|
||||
const currentRow = ref(null)
|
||||
function onView(row) { currentRow.value = row; viewOpen.value = true }
|
||||
|
||||
// ========== 已退回: 查看退回意见 ==========
|
||||
// ========== 已拒绝: 查看拒绝意见 ==========
|
||||
function onViewAuditOpinion(row) {
|
||||
ElMessageBox.alert(
|
||||
`退回时间: ${fmtTime(row.auditTime) || '-'}\n审批人: ${row.auditBy || '-'}\n退回意见: ${row.auditOpinion || '(无)'}`,
|
||||
`专家 ${row.name} 审核已退回`,
|
||||
`拒绝时间: ${fmtTime(row.auditTime) || '-'}\n审批人: ${row.auditBy || '-'}\n拒绝意见: ${row.auditOpinion || '(无)'}`,
|
||||
`专家 ${row.name} 审核已拒绝`,
|
||||
{ type: 'warning', confirmButtonText: '知道了' }
|
||||
).catch(() => {})
|
||||
}
|
||||
@@ -382,12 +375,12 @@ function onDownloadAll(row) {
|
||||
|
||||
// ========== 批量导入 dialog ==========
|
||||
const importOpen = ref(false)
|
||||
const importAuditResult = ref('1')
|
||||
const importAuditResult = ref('2')
|
||||
const importAuditOpinion = ref('')
|
||||
const importFileUrl = ref('')
|
||||
const importing = ref(false)
|
||||
function openImport() {
|
||||
importAuditResult.value = '1'
|
||||
importAuditResult.value = '2'
|
||||
importAuditOpinion.value = ''
|
||||
importFileUrl.value = ''
|
||||
importOpen.value = true
|
||||
@@ -400,7 +393,7 @@ async function submitImport() {
|
||||
importing.value = true
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
importing.value = false
|
||||
ElMessage.info(`已提交批量导入任务 (审核结果: ${importAuditResult.value === '1' ? '通过' : '退回'}) — 待后端实现`)
|
||||
ElMessage.info(`已提交批量导入任务 (审核结果: ${importAuditResult.value === '2' ? '通过' : '拒绝'}) — 待后端实现`)
|
||||
importOpen.value = false
|
||||
}
|
||||
|
||||
@@ -434,13 +427,12 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-card { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 16px; margin-bottom: 12px; }
|
||||
.desc { color: #606266; font-size: 13px; margin-bottom: 12px; }
|
||||
.filter-form .el-form-item { margin-right: 12px; margin-bottom: 8px; }
|
||||
.batch-bar { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-experts { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
|
||||
.stage-tag { display: inline-block; padding: 2px 10px; border-radius: 3px; font-size: 12px; border: 1px solid; }
|
||||
.stage-done { color: #67c23a; border-color: #c2e7b0; background: #f0f9eb; }
|
||||
.stage-reviewing { color: #e6a23c; border-color: #f5dab1; background: #fdf6ec; }
|
||||
.stage-reject { color: #f56c6c; border-color: #fbc4c4; background: #fef0f0; }
|
||||
/* stage-* CSS 已删除: 改用 AuditStatusTag 组件 (el-tag 自带颜色) */
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="page-card manager-meeting-new">
|
||||
<h2 style="margin-bottom:16px">新建会议</h2>
|
||||
<div class="breadcrumb">首页 / 会议管理 / 新建会议</div>
|
||||
<p class="page-sub" v-if="preselectProjectId">已选择项目: {{ preselectProjectId }}</p>
|
||||
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" style="max-width:700px">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="meeting-form">
|
||||
<el-form-item label="项目编号" prop="projectNo">
|
||||
<el-input v-model="form.projectNo" :disabled="!!preselectProjectId" placeholder="项目编号" />
|
||||
</el-form-item>
|
||||
@@ -100,13 +100,16 @@ onMounted(async () => {
|
||||
if (preselectProjectId) {
|
||||
try {
|
||||
const r = await bizGet('project', preselectProjectId)
|
||||
form.projectNo = r.data?.projectNo || ''
|
||||
form.projectNo = (r.data && r.data.projectNo) || ''
|
||||
} catch (e) {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-meeting-new { padding: 16px; }
|
||||
.page-sub { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-sub { color: #909399; font-size: 13px; margin-bottom: 16px; }
|
||||
.meeting-form { max-width: 700px; }
|
||||
</style>
|
||||
@@ -1,89 +1,80 @@
|
||||
<template>
|
||||
<div class="manager-meetings">
|
||||
<!-- ========== 筛选区 (严格按 manager 角色原型 会议管理_1.html — 第一行 6 项 + 第二行 3 项) ========== -->
|
||||
<div class="page-card">
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号">
|
||||
<el-input v-model="q.projectNo" placeholder="" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="会议ID">
|
||||
<el-input v-model="q.meetingId" placeholder="" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="会议名称">
|
||||
<el-input v-model="q.meetingName" placeholder="" clearable style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="第?期">
|
||||
<el-input v-model="q.periodNo" placeholder="" clearable style="width:80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="会议时间">
|
||||
<el-date-picker v-model="q.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:170px" />
|
||||
<span style="margin:0 4px">至</span>
|
||||
<el-date-picker v-model="q.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:170px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目形式">
|
||||
<el-select v-model="q.projectForm" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="线上" value="线上" />
|
||||
<el-option label="线下" value="线下" />
|
||||
<el-option label="线上+线下" value="线上+线下" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前阶段">
|
||||
<el-select v-model="q.currentStage" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="未执行" value="未执行" />
|
||||
<el-option label="执行中" value="执行中" />
|
||||
<el-option label="已执行" value="已执行" />
|
||||
<el-option label="已完结" value="已完结" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="q.remark" placeholder="" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="page-card manager-meetings">
|
||||
<div class="breadcrumb">首页 / 会议管理</div>
|
||||
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="" clearable style="width:140px" /></el-form-item>
|
||||
<el-form-item label="会议ID"><el-input v-model="q.meetingId" placeholder="" clearable style="width:140px" /></el-form-item>
|
||||
<el-form-item label="会议名称"><el-input v-model="q.meetingName" placeholder="" clearable style="width:160px" /></el-form-item>
|
||||
<el-form-item label="第?期"><el-input v-model="q.periodNo" placeholder="" clearable style="width:80px" /></el-form-item>
|
||||
<el-form-item label="会议时间">
|
||||
<el-date-picker v-model="q.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:170px" />
|
||||
<span style="margin:0 4px">至</span>
|
||||
<el-date-picker v-model="q.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:170px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目形式">
|
||||
<el-select v-model="q.projectForm" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="线上" value="线上" />
|
||||
<el-option label="线下" value="线下" />
|
||||
<el-option label="线上+线下" value="线上+线下" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前阶段">
|
||||
<el-select v-model="q.currentStage" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="未执行" value="未执行" />
|
||||
<el-option label="执行中" value="执行中" />
|
||||
<el-option label="已执行" value="已执行" />
|
||||
<el-option label="已完结" value="已完结" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="q.remark" placeholder="" clearable style="width:140px" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量提交按钮 (manager 角色专有) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" plain :disabled="!selectedIds.length" @click="onBatchSubmit">批量提交</el-button>
|
||||
<span v-if="selectedIds.length" class="filter-tip">已选 {{ selectedIds.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<!-- ========== 表格区 (按 manager 角色原型 会议管理_1.html: 操作列 4 按钮 — 查看/修改/提交/复制) ========== -->
|
||||
<div class="page-card">
|
||||
<!-- 批量提交按钮 (manager 角色才有) -->
|
||||
<div class="batch-bar" v-if="selectedIds.length">
|
||||
<el-button type="primary" plain @click="onBatchSubmit" :disabled="!selectedIds.length">批量提交</el-button>
|
||||
<span class="selected-tip">已选 {{ selectedIds.length }} 条</span>
|
||||
</div>
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelectionChange">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="130" fixed />
|
||||
<el-table-column prop="meetingId" label="会议ID" width="120" align="center" />
|
||||
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="会议开始时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.startTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="会议结束时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.endTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalPeriods" label="总期数" width="80" align="center" />
|
||||
<el-table-column label="期数" width="80" align="center">
|
||||
<template #default="{ row }">第 {{ row.periodNo || 0 }} 期</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentStage" label="当前阶段" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="['stage-tag', 'stage-' + stageClass(row.currentStage)]">{{ row.currentStage }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onSubmit(row)">提交</el-button>
|
||||
<el-button link type="primary" @click="onCopy(row)">复制</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelectionChange">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="130" fixed />
|
||||
<el-table-column prop="meetingId" label="会议ID" width="120" align="center" />
|
||||
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="会议开始时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.startTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="会议结束时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.endTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalPeriods" label="总期数" width="80" align="center" />
|
||||
<el-table-column label="期数" width="80" align="center">
|
||||
<template #default="{ row }">第 {{ row.periodNo || 0 }} 期</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentStage" label="当前阶段" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="['stage-tag', 'stage-' + stageClass(row.currentStage)]">{{ row.currentStage }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onSubmit(row)">提交</el-button>
|
||||
<el-button link type="primary" @click="onCopy(row)">复制</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
@@ -289,19 +280,15 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-meetings { padding: 16px; }
|
||||
.page-card {
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.filter-form .el-form-item { margin-right: 12px; margin-bottom: 12px; }
|
||||
.batch-bar { margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
|
||||
.batch-bar .selected-tip { color: #909399; font-size: 13px; margin-left: 8px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
|
||||
/* 当前阶段 tag 颜色 */
|
||||
/* 当前阶段 tag 颜色 (业务专用, 其它页没有) */
|
||||
.stage-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
@@ -309,11 +296,11 @@ onMounted(load)
|
||||
font-size: 12px;
|
||||
border: 1px solid;
|
||||
}
|
||||
.stage-pending { color: #909399; border-color: #dcdfe6; background: #f5f7fa; }
|
||||
.stage-running { color: #409eff; border-color: #b3d8ff; background: #ecf5ff; }
|
||||
.stage-done { color: #67c23a; border-color: #c2e7b0; background: #f0f9eb; }
|
||||
.stage-pending { color: #909399; border-color: #dcdfe6; background: #f5f7fa; }
|
||||
.stage-running { color: var(--brand-primary); border-color: #b3c5e0; background: #eef1f8; }
|
||||
.stage-done { color: #67c23a; border-color: #c2e7b0; background: #f0f9eb; }
|
||||
.stage-reviewing { color: #e6a23c; border-color: #f5dab1; background: #fdf6ec; }
|
||||
.stage-waiting { color: #f56c6c; border-color: #fbc4c4; background: #fef0f0; }
|
||||
.stage-frozen { color: #909399; border-color: #dcdfe6; background: #f5f7fa; opacity: 0.6; }
|
||||
.stage-default { color: #606266; border-color: #dcdfe6; background: #fafafa; }
|
||||
.stage-waiting { color: #ff4d4f; border-color: #fbc4c4; background: #fef0f0; }
|
||||
.stage-frozen { color: #909399; border-color: #dcdfe6; background: #f5f7fa; opacity: 0.6; }
|
||||
.stage-default { color: #606266; border-color: #dcdfe6; background: #fafafa; }
|
||||
</style>
|
||||
@@ -4,69 +4,66 @@
|
||||
重构说明: 原 manager/SupportUnits.vue (仅看支持单位) 改为可看所有公司
|
||||
接口: bizList('org', q) / bizUpdate
|
||||
-->
|
||||
<div class="manager-orgs">
|
||||
<div class="page-card">
|
||||
<div class="page-title">公司查看</div>
|
||||
<div class="desc">公司管理 > 公司查看 (赞助方 + 执行方)</div>
|
||||
<el-form inline :model="q">
|
||||
<el-form-item label="公司类型:">
|
||||
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:140px">
|
||||
<el-option label="赞助方" value="sponsor" />
|
||||
<el-option label="执行方" value="executor" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称:">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码:">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态:">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable>
|
||||
<el-option label="正常/合作中" value="0" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="action-btn" @click="load">查找</el-button>
|
||||
<el-button class="action-btn secondary" @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="page-card manager-orgs">
|
||||
<div class="breadcrumb">首页 / 公司管理</div>
|
||||
|
||||
<div class="page-card">
|
||||
<div class="page-toolbar">
|
||||
<el-button class="action-btn secondary" @click="goBack">返回</el-button>
|
||||
</div>
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
|
||||
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.createTime ? String(row.createTime).slice(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="view(row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司类型">
|
||||
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:140px">
|
||||
<el-option label="赞助方" value="sponsor" />
|
||||
<el-option label="执行方" value="executor" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable>
|
||||
<el-option label="正常/合作中" value="0" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
|
||||
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.createTime ? String(row.createTime).slice(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="view(row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
|
||||
@@ -129,20 +126,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-orgs { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 6px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; margin-bottom: 4px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
.desc { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.page-toolbar { display: flex; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
|
||||
.page-toolbar :deep(.action-btn) {
|
||||
height: 36px; padding: 0 20px; font-size: 14px; border-radius: 4px;
|
||||
background: var(--brand-primary); color: #fff; border: 1px solid var(--brand-primary);
|
||||
display: inline-flex; align-items: center; justify-content: center; white-space: nowrap;
|
||||
transition: opacity .15s; margin-left: 0;
|
||||
}
|
||||
.page-toolbar :deep(.action-btn:hover) { opacity: .85; }
|
||||
.page-toolbar :deep(.action-btn.secondary) {
|
||||
background: #fff; color: var(--brand-primary); border: 1px solid var(--brand-primary);
|
||||
}
|
||||
.page-toolbar :deep(.action-btn.secondary:hover) { background: rgba(30,58,138,.06); }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
|
||||
+112
-112
@@ -1,95 +1,92 @@
|
||||
<template>
|
||||
<div class="manager-plans">
|
||||
<div class="page-card">
|
||||
<div class="page-title">项目策划方案管理</div>
|
||||
<div class="desc">审核 / 修改 / 结算 项目策划方案</div>
|
||||
<el-form inline :model="q">
|
||||
<!-- 按原型 top=171-178 的筛选区: 项目编号(52) / 项目编号(141) / 策划方案名称(254) / 项目方向(519) / 策划方案名称(591) / 状态(939) / 备注(1138) / 备注(1177) -->
|
||||
<el-form-item label="项目编号:"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
<el-form-item label="策划方案名称:"><el-input v-model="q.planName" placeholder="输入策划方案名称" clearable /></el-form-item>
|
||||
<el-form-item label="项目方向:">
|
||||
<el-select v-model="q.planDirection" placeholder="请选择" clearable>
|
||||
<el-option label="专项计划一" value="专项计划一" /><el-option label="专项计划二" value="专项计划二" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目类别:">
|
||||
<el-select v-model="q.planCategory" placeholder="请选择" clearable>
|
||||
<el-option label="共识" value="共识" /><el-option label="对外交流" value="对外交流" />
|
||||
<el-option label="学术项目" value="学术项目" /><el-option label="科研项目" value="科研项目" />
|
||||
<el-option label="指南" value="指南" /><el-option label="会议项目" value="会议项目" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable>
|
||||
<el-option label="待审核" value="0" /><el-option label="审核通过" value="1" /><el-option label="已退回" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结算:">
|
||||
<el-select v-model="q.isSettled" placeholder="请选择" clearable>
|
||||
<el-option label="已结算" value="1" /><el-option label="未结算" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注:"><el-input v-model="q.remark" placeholder="输入备注" clearable /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="action-btn" @click="load">查找</el-button>
|
||||
<el-button class="action-btn secondary" @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="filter-tip">*批量审核:策划案名称,投稿ID,审核结果,审核意见,只能审核不通过</div>
|
||||
<div class="page-card manager-plans">
|
||||
<div class="breadcrumb">首页 / 项目策划方案</div>
|
||||
<div class="hint-list">
|
||||
<p>*批量审核:策划案名称,投稿ID,审核结果,审核意见,只能审核不通过</p>
|
||||
</div>
|
||||
|
||||
<div class="page-card">
|
||||
<!-- 批量按钮 (按原型 left: 批量审核 266 / 批量结算 432) -->
|
||||
<div class="page-toolbar">
|
||||
<el-button class="action-btn" :disabled="!selection.length" @click="batchAudit">
|
||||
批量审核<span v-if="selection.length" class="badge">{{ selection.length }}</span>
|
||||
</el-button>
|
||||
<el-button class="action-btn secondary" :disabled="!selection.length" @click="batchSettle">
|
||||
批量结算<span v-if="selection.length" class="badge">{{ selection.length }}</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列头顺序严格按原型 top=294-300 的 left: 策划方案名称 342 / 项目方向 569 / 项目类别 739 / 项目形式 864 / 设计文件 999 / 状态 1133 / 是否结算 1246 / 项目编号 1348 / 备注 1492 / 操作 1640 -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="planName" label="策划方案名称" min-width="200" fixed show-overflow-tooltip />
|
||||
<el-table-column prop="planDirection" label="项目方向" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="planCategory" label="项目类别" width="100" align="center" />
|
||||
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
|
||||
<el-table-column label="设计文件" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.designFileUrl">
|
||||
<el-button link type="primary" @click="onFile(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="downloadFile(row)">下载</el-button>
|
||||
</template>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
<el-form-item label="策划方案名称"><el-input v-model="q.planName" placeholder="输入策划方案名称" clearable /></el-form-item>
|
||||
<el-form-item label="项目方向">
|
||||
<el-select v-model="q.planDirection" placeholder="请选择" clearable>
|
||||
<el-option label="专项计划一" value="专项计划一" /><el-option label="专项计划二" value="专项计划二" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目类别">
|
||||
<el-select v-model="q.planCategory" placeholder="请选择" clearable>
|
||||
<el-option label="共识" value="共识" /><el-option label="对外交流" value="对外交流" />
|
||||
<el-option label="学术项目" value="学术项目" /><el-option label="科研项目" value="科研项目" />
|
||||
<el-option label="指南" value="指南" /><el-option label="会议项目" value="会议项目" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable>
|
||||
<el-option label="待审核" value="0" /><el-option label="审核通过" value="1" /><el-option label="已退回" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结算">
|
||||
<el-select v-model="q.isSettled" placeholder="请选择" clearable>
|
||||
<el-option label="已结算" value="1" /><el-option label="未结算" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="q.remark" placeholder="输入备注" clearable /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量按钮 (与 People.vue 风格一致) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" :disabled="!selection.length" @click="batchAudit">批量审核</el-button>
|
||||
<el-button type="primary" :disabled="!selection.length" @click="batchSettle">批量结算</el-button>
|
||||
<span v-if="selection.length" class="filter-tip">已选 {{ selection.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="planName" label="策划方案名称" min-width="200" fixed show-overflow-tooltip />
|
||||
<el-table-column prop="planDirection" label="项目方向" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="planCategory" label="项目类别" width="100" align="center" />
|
||||
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
|
||||
<el-table-column label="设计文件" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.designFileUrl">
|
||||
<el-button link type="primary" @click="onFile(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="downloadFile(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.status==='1' ? 'success' : row.status==='2' ? 'danger' : 'warning'">
|
||||
{{ row.status==='1' ? '审核通过' : row.status==='2' ? '已退回' : '待审核' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否结算" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.isSettled==='1' ? 'success' : 'warning'">
|
||||
{{ row.isSettled==='1' ? '已结算' : '未结算' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="projectNo" label="项目编号" width="140" align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||||
<!-- 操作列按原型 left 顺序: 审核 1586 / 修改 1640 / 结算 1693 -->
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onSettle(row)" v-if="row.isSettled!=='1'">结算</el-button>
|
||||
<el-button link type="primary" v-if="row.designFileUrl" @click="downloadFile(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.status==='1' ? 'success' : row.status==='2' ? 'danger' : 'warning'">
|
||||
{{ row.status==='1' ? '审核通过' : row.status==='2' ? '已退回' : '待审核' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否结算" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.isSettled==='1' ? 'success' : 'warning'">
|
||||
{{ row.isSettled==='1' ? '已结算' : '未结算' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="projectNo" label="项目编号" width="140" align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||||
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onSettle(row)" v-if="row.isSettled!=='1'">结算</el-button>
|
||||
<el-button link type="primary" v-if="row.designFileUrl" @click="downloadFile(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
|
||||
@@ -282,31 +279,34 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-plans { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 6px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; margin-bottom: 4px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
.desc { color: #909399; font-size: 13px; margin-bottom: 12px; }
|
||||
.filter-tip { color: #ff4d4f; font-size: 12px; margin-top: 8px; }
|
||||
.page-toolbar { display: flex; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
|
||||
.page-toolbar :deep(.action-btn) {
|
||||
height: 36px; padding: 0 20px; font-size: 14px; border-radius: 4px;
|
||||
background: var(--brand-primary); color: #fff; border: 1px solid var(--brand-primary);
|
||||
display: inline-flex; align-items: center; justify-content: center; white-space: nowrap;
|
||||
transition: opacity .15s; margin-left: 0;
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.page-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 6px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
}
|
||||
.page-toolbar :deep(.action-btn:hover) { opacity: .85; }
|
||||
.page-toolbar :deep(.action-btn.secondary) {
|
||||
background: #fff; color: var(--brand-primary); border: 1px solid var(--brand-primary);
|
||||
.page-sub { font-size: 13px; color: #909399; margin: 0 0 16px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
|
||||
/* 红字提示 (与 People.vue 一致) */
|
||||
.hint-list {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
color: #ff4d4f;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.page-toolbar :deep(.action-btn.secondary:hover) { background: rgba(30,58,138,.06); }
|
||||
.page-toolbar :deep(.action-btn.is-disabled),
|
||||
.page-toolbar :deep(.action-btn:disabled) { background: #f5f7fa; color: #c0c4cc; border-color: #e4e7ed; opacity: 1; cursor: not-allowed; }
|
||||
.page-toolbar :deep(.action-btn.secondary.is-disabled),
|
||||
.page-toolbar :deep(.action-btn.secondary:disabled) { background: #fff; color: #c0c4cc; border-color: #e4e7ed; }
|
||||
.page-toolbar :deep(.action-btn .badge) {
|
||||
display: inline-block; margin-left: 6px; padding: 0 6px; min-width: 18px; height: 18px;
|
||||
line-height: 18px; font-size: 11px; text-align: center;
|
||||
background: #fff; color: var(--brand-primary); border-radius: 9px;
|
||||
}
|
||||
.page-toolbar :deep(.action-btn.secondary .badge) { background: var(--brand-primary); color: #fff; }
|
||||
.hint-list p { margin-bottom: 4px; }
|
||||
.hint-list p:last-child { margin-bottom: 0; }
|
||||
</style>
|
||||
|
||||
@@ -1,89 +1,84 @@
|
||||
<template>
|
||||
<div class="page-card">
|
||||
<!-- 面包屑 (与 sponsor 端一致: 首页 / XXX / 当前页) -->
|
||||
<!-- 面包屑 -->
|
||||
<div class="breadcrumb">
|
||||
首页 / 项目管理 / <span class="current">{{ isEdit ? '编辑项目' : '新建项目' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 第一区块:项目信息 -->
|
||||
<el-card shadow="never" class="new-card">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="project-form">
|
||||
<!-- ========== 第一区块:项目信息 ========== -->
|
||||
<div class="new-card-title">项目信息</div>
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编号" prop="projectNo">
|
||||
<el-input v-model="form.projectNo" placeholder="请输入项目编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目形式" prop="projectForm">
|
||||
<el-select v-model="form.projectForm" placeholder="请选择" style="width:100%">
|
||||
<el-option label="线上" value="线上" />
|
||||
<el-option label="线下" value="线下" />
|
||||
<el-option label="线上+线下" value="线上+线下" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="总场次/总期数" prop="totalSessions">
|
||||
<el-input-number v-model="form.totalSessions" :min="1" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="总金额(元)" prop="totalAmount">
|
||||
<el-input-number v-model="form.totalAmount" :min="0" :precision="2" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="管理费(元)">
|
||||
<el-input-number v-model="form.manageFee" :min="0" :precision="2" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 支持单位 -->
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="提交截止(天)">
|
||||
<el-input-number v-model="form.submitDeadlineDays" :min="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="支持单位">
|
||||
<el-select v-model="form.orgId" placeholder="请选择赞助公司" filterable clearable style="width:100%" @change="onSupporterPick">
|
||||
<el-option v-for="u in supporterOptions" :key="u.userId"
|
||||
:label="`${u.userName}${u.nickName ? ' (' + u.nickName + ')' : ''}`"
|
||||
:value="u.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编号" prop="projectNo">
|
||||
<el-input v-model="form.projectNo" placeholder="请输入项目编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目形式" prop="projectForm">
|
||||
<el-select v-model="form.projectForm" placeholder="请选择" style="width:100%">
|
||||
<el-option label="线上" value="线上" />
|
||||
<el-option label="线下" value="线下" />
|
||||
<el-option label="线上+线下" value="线上+线下" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="总场次/总期数" prop="totalSessions">
|
||||
<el-input-number v-model="form.totalSessions" :min="1" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="总金额(元)" prop="totalAmount">
|
||||
<el-input-number v-model="form.totalAmount" :min="0" :precision="2" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="管理费(元)">
|
||||
<el-input-number v-model="form.manageFee" :min="0" :precision="2" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="提交截止(天)">
|
||||
<el-input-number v-model="form.submitDeadlineDays" :min="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="支持单位">
|
||||
<el-select v-model="form.orgId" placeholder="请选择赞助公司" filterable clearable style="width:100%" @change="onSupporterPick">
|
||||
<el-option v-for="u in supporterOptions" :key="u.userId"
|
||||
:label="`${u.userName}${u.nickName ? ' (' + u.nickName + ')' : ''}`"
|
||||
:value="u.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二区块:角色劳务设置 -->
|
||||
<el-card shadow="never" class="new-card">
|
||||
<!-- ========== 第二区块:角色劳务设置 ========== -->
|
||||
<div class="new-card-title">角色劳务设置</div>
|
||||
<div class="hint-text">
|
||||
<p>*角色劳务设置针对该项目所有会议生效</p>
|
||||
@@ -100,17 +95,14 @@
|
||||
</el-select>
|
||||
<el-input v-if="r.role === '其他'" v-model="r.customName" placeholder="角色名" class="role-custom" />
|
||||
<el-input-number v-model="r.amount" :min="0" :precision="2" placeholder="劳务金额" class="role-amount" />
|
||||
<!-- 行尾按钮: 每行末尾 [增加角色] + [删除本行] (不靠右, 紧贴字段) -->
|
||||
<div class="row-actions">
|
||||
<el-button link type="primary" @click="addRoleRow">增加角色</el-button>
|
||||
<el-button link type="danger" :disabled="form.roleRows.length<=1" @click="removeRoleRowAt(idx)">删除角色</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint-text">*当选择其他时,需手动填写角色名称,显示填写框</p>
|
||||
</el-card>
|
||||
|
||||
<!-- 第三区块:公告文件 -->
|
||||
<el-card shadow="never" class="new-card">
|
||||
<!-- ========== 第三区块:公告文件 ========== -->
|
||||
<div class="new-card-title">公告文件</div>
|
||||
<p class="hint-text">*勾选公告类型后,上传对应文件;发布公告时可同时发布多个类型</p>
|
||||
<div class="notice-list">
|
||||
@@ -143,9 +135,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint-text">*名称规则:公告文件类型+取项目编号的后两段+项目名称</p>
|
||||
</el-card>
|
||||
</el-form>
|
||||
|
||||
<!-- 底部按钮 (与 sponsor 工具栏风格一致) -->
|
||||
<!-- 底部按钮 -->
|
||||
<div class="form-actions">
|
||||
<el-button @click="confirmCancel">取消</el-button>
|
||||
<el-button type="warning" :loading="submitting" @click="submit('publish')">保存并发布公告</el-button>
|
||||
@@ -183,7 +175,7 @@ const form = reactive({
|
||||
projectName: '', projectNo: '', projectForm: '',
|
||||
totalSessions: 0, totalAmount: 0, manageFee: 0,
|
||||
startTime: '', endTime: '',
|
||||
// 赞助公司 (TODO: 下拉源应改为 biz_org,orgType='sponsor', 当前是 sys_user)
|
||||
// 赞助公司 (TODO 后续: 下拉源应改为 biz_org,orgType='sponsor' 列表, 当前是 sys_user)
|
||||
orgId: null,
|
||||
orgName: '',
|
||||
// 提交截止(天)
|
||||
@@ -286,7 +278,7 @@ async function loadSupporters(query = '') {
|
||||
const params = { roleType: 'sponsor', pageNum: 1, pageSize: 50 }
|
||||
if (query) params.userName = query
|
||||
const r = await listSupporters(params)
|
||||
supporterOptions.value = r.rows || r.data || []
|
||||
supporterOptions.value = (r.data && r.data.rows) || r.rows || []
|
||||
} catch (e) { supporterOptions.value = [] }
|
||||
}
|
||||
|
||||
@@ -454,18 +446,25 @@ async function submit(mode = 'save') {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1:1 抄 sponsor/SponsorProjects.vue */
|
||||
/* 整页面板 (与 People.vue 风格一致, 1 层) */
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.breadcrumb .current { color: #262626; font-weight: 500; }
|
||||
|
||||
/* 卡片分块 (与 sponsor 一致: 左边竖线标识 + 标题) */
|
||||
.new-card { margin-bottom: 14px; border: 1px solid #f0f0f0; border-radius: 4px; }
|
||||
.new-card :deep(.el-card__body) { padding: 16px 20px; }
|
||||
/* 表单 (整页一个 form) */
|
||||
.project-form { max-width: 100%; }
|
||||
|
||||
/* 章节标题 (替换原 .new-card 容器, 用分隔线分割 3 块) */
|
||||
.new-card-title {
|
||||
font-size: 14px; font-weight: 600; color: #262626; margin-bottom: 12px;
|
||||
padding-left: 8px; border-left: 3px solid var(--brand-primary, #1890ff); line-height: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 16px 0 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
.new-card-title:first-child { margin-top: 0; }
|
||||
|
||||
.hint-text { font-size: 12px; color: #909399; margin: 8px 0; line-height: 1.6; }
|
||||
.hint-text p { margin-bottom: 4px; }
|
||||
@@ -499,6 +498,6 @@ async function submit(mode = 'save') {
|
||||
.notice-actions .el-button { padding: 2px 6px; font-size: 12px; }
|
||||
.notice-disabled { flex: 1; color: #c0c4cc; font-size: 13px; padding: 8px 0; }
|
||||
|
||||
/* 底部按钮 (居中, 与 sponsor 风格一致) */
|
||||
/* 底部按钮 */
|
||||
.form-actions { display: flex; justify-content: center; gap: 16px; padding: 20px 0 4px; border-top: 1px solid #f0f0f0; margin-top: 8px; }
|
||||
</style>
|
||||
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 支持单位查看 (manager/sponsor-orgs)
|
||||
说明: 原 manager/Orgs.vue 拆出 sponsor 一支, 仅查看
|
||||
接口: bizList('org', q)
|
||||
-->
|
||||
<div class="page-card manager-sponsor-orgs">
|
||||
<div class="breadcrumb">首页 / 支持单位管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable>
|
||||
<el-option label="正常/合作中" value="0" />
|
||||
<el-option label="合作中" value="合作中" />
|
||||
<el-option label="禁用" value="禁用" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.createTime ? String(row.createTime).slice(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="view(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="goPeople(row)">人员管理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="detailOpen" title="支持单位详情" width="640px" v-if="currentRow">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="公司类型">
|
||||
<el-tag type="success">赞助方</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司名称">{{ currentRow.orgName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="公司地址">{{ currentRow.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="税号/统一社会信用代码">{{ currentRow.taxNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合作状态">
|
||||
<el-tag :type="statusTagType(currentRow.status)">{{ currentRow.status }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建日期">{{ currentRow.createTime ? String(currentRow.createTime).slice(0,10) : '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ currentRow.contactName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ currentRow.contactPhone || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailOpen=false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList } from '@/api/public'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = ref({ orgType: 'sponsor', orgName: '', taxNo: '', status: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
const currentRow = ref(null)
|
||||
const detailOpen = ref(false)
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '正常' || s === '0' || s === '合作中') return 'success'
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []; page.total = data?.total || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() { q.value = { orgType: 'sponsor', orgName: '', taxNo: '', status: '' }; page.pageNum = 1; load() }
|
||||
function view(row) { currentRow.value = row; detailOpen.value = true }
|
||||
function goPeople(row) {
|
||||
router.push({ path: '/manager/sponsor-people', query: { orgId: row.orgId, orgName: row.orgName } })
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-sponsor-orgs { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 支持单位下人员 (manager/sponsor-people)
|
||||
入口: org 表格行内「人员管理」按钮 (router.push 带 orgId/orgName)
|
||||
接口: bizList('person', q) / bizUpdate / bizAdd / sponsorImport / sponsorImportTemplate
|
||||
-->
|
||||
<div class="page-card manager-sponsor-people">
|
||||
<div class="breadcrumb">
|
||||
首页 / 支持单位管理 / 人员管理{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="姓名" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="手机号" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称">
|
||||
<el-input v-model="q.orgName" placeholder="企业名称" clearable style="width:180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="q.department" placeholder="部门" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="q.position" placeholder="职务" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建人员</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<span class="filter-tip">*批量导入模板与列表表项一致</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||||
<el-table-column prop="orgName" label="企业名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" min-width="100" />
|
||||
<el-table-column prop="position" label="职务" min-width="100" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status || '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(row) ? '启用' : '禁用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNum"
|
||||
v-model:page-size="page.pageSize"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择文件">
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
|
||||
<el-button>选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
|
||||
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '禁用' || s === '1') return 'danger'
|
||||
return 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '禁用' || row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...q,
|
||||
orgType: 'sponsor',
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
pageNum: page.pageNum,
|
||||
pageSize: page.pageSize,
|
||||
}
|
||||
const { data } = await bizList('person', params)
|
||||
rows.value = (data && data.rows) || []
|
||||
page.total = (data && data.total) || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(q, { name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '正常' : '禁用'
|
||||
const label = disabled ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.name}」吗?`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizUpdate('person', { personId: row.personId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
// ========== 新建人员 ==========
|
||||
function goNew() {
|
||||
router.push({
|
||||
path: '/manager/sponsor-people/new',
|
||||
query: {
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
...(orgName.value ? { orgName: orgName.value } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 批量导入 ==========
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importFile = ref(null)
|
||||
|
||||
function openImport() { importFile.value = null; importOpen.value = true }
|
||||
function onFileChange(file) { importFile.value = file.raw }
|
||||
function onFileRemove() { importFile.value = null }
|
||||
async function downloadTemplate() {
|
||||
try {
|
||||
const res = await downloadImportTemplate('sponsor')
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '支持单位人员导入模板.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch { ElMessage.error('模板下载失败') }
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
||||
importing.value = true
|
||||
try {
|
||||
const res = await importPerson('sponsor', importFile.value)
|
||||
const payload = res.data || res
|
||||
const ok = payload.ok ?? 0
|
||||
const total = payload.total ?? 0
|
||||
const failed = (payload.results || []).filter(r => !r.ok)
|
||||
if (failed.length === 0) {
|
||||
ElMessage.success(`导入成功 ${ok}/${total}`)
|
||||
} else {
|
||||
ElMessageBox.alert(
|
||||
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
|
||||
failed.map(f => ` 第${f.rowNo}行 ${f.name || ''}: ${f.message}`).join('\n'),
|
||||
'导入结果', { type: 'warning' }
|
||||
)
|
||||
}
|
||||
importOpen.value = false
|
||||
load()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '导入失败')
|
||||
} finally {
|
||||
importing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-sponsor-people { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,41 +1,43 @@
|
||||
<template>
|
||||
<div class="manager-support-intent">
|
||||
<div class="page-card">
|
||||
<div class="page-title">支持意向</div>
|
||||
<!-- 筛选区严格按原型 top=169-217 的 left 顺序: 意向项目名称(276) / ZH-2026-658(371) / 姓名(541) / 工作单位(580) / 工作单位(750) / 工作单位(817) / 项目编号(996) / 提示(1113) -->
|
||||
<el-form inline :model="q">
|
||||
<el-form-item label="项目编号:"><el-input v-model="q.projectNo" placeholder="如 ZH-2026-658" clearable /></el-form-item>
|
||||
<el-form-item label="意向项目名称:"><el-input v-model="q.projectName" placeholder="输入意向项目名称" clearable /></el-form-item>
|
||||
<el-form-item label="姓名:"><el-input v-model="q.name" placeholder="输入姓名" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位:"><el-input v-model="q.workUnit" placeholder="输入工作单位" clearable /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="action-btn" @click="load">查找</el-button>
|
||||
<el-button class="action-btn secondary" @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 提示语严格按原型 top=217-243 -->
|
||||
<div class="filter-tip">*项目公示中点击支持意向后,显示在此</div>
|
||||
<div class="filter-tip">*支持意向不需要注册,但是如果已经注册账号,则显示账号状态</div>
|
||||
<div class="filter-tip">*根据手机号与系统账号进行匹配</div>
|
||||
<div class="page-card manager-support-intent">
|
||||
<div class="breadcrumb">首页 / 支持意向</div>
|
||||
|
||||
<!-- 提示语 (按原型 top=217-243) -->
|
||||
<div class="hint-list">
|
||||
<p>*项目公示中点击支持意向后,显示在此</p>
|
||||
<p>*支持意向不需要注册,但是如果已经注册账号,则显示账号状态</p>
|
||||
<p>*根据手机号与系统账号进行匹配</p>
|
||||
</div>
|
||||
|
||||
<div class="page-card">
|
||||
<!-- 列头严格按原型 top=296 left 顺序: 支持意向项目编号 272 / 支持意向项目名称 437 / 姓名 610 / 工作单位名称 685 / 部门 815 / 职务 925 / 手机号 1054 / 账号状态 1164 -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="projectNo" label="支持意向项目编号" width="140" align="center" />
|
||||
<el-table-column prop="projectName" label="支持意向项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="name" label="姓名" width="80" align="center" />
|
||||
<el-table-column prop="workUnit" label="工作单位名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" width="120" align="center" />
|
||||
<el-table-column prop="position" label="职务" width="120" align="center" />
|
||||
<el-table-column prop="phone" label="手机号" width="130" align="center" />
|
||||
<el-table-column label="账号状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.accountStatus==='存在' ? 'success' : 'warning'">{{ row.accountStatus || '不存在' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="如 ZH-2026-658" clearable /></el-form-item>
|
||||
<el-form-item label="意向项目名称"><el-input v-model="q.projectName" placeholder="输入意向项目名称" clearable /></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="q.name" placeholder="输入姓名" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" placeholder="输入工作单位" clearable /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 表格 (与 People.vue 风格一致, 无 page-card 包裹) ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="projectNo" label="支持意向项目编号" width="140" align="center" />
|
||||
<el-table-column prop="projectName" label="支持意向项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="name" label="姓名" width="80" align="center" />
|
||||
<el-table-column prop="workUnit" label="工作单位名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" width="120" align="center" />
|
||||
<el-table-column prop="position" label="职务" width="120" align="center" />
|
||||
<el-table-column prop="phone" label="手机号" width="130" align="center" />
|
||||
<el-table-column label="账号状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.accountStatus==='存在' ? 'success' : 'warning'">{{ row.accountStatus || '不存在' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,20 +66,23 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-support-intent { padding: 16px; }
|
||||
.page-card { background: #fff; border-radius: 6px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; margin-bottom: 4px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
.filter-tip { color: #909399; font-size: 12px; line-height: 1.8; }
|
||||
.page-toolbar { display: flex; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
|
||||
.page-toolbar :deep(.action-btn) {
|
||||
height: 36px; padding: 0 20px; font-size: 14px; border-radius: 4px;
|
||||
background: var(--brand-primary); color: #fff; border: 1px solid var(--brand-primary);
|
||||
display: inline-flex; align-items: center; justify-content: center; white-space: nowrap;
|
||||
transition: opacity .15s; margin-left: 0;
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
|
||||
/* 红字提示 (与 People.vue 一致) */
|
||||
.hint-list {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
color: #ff4d4f;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.page-toolbar :deep(.action-btn:hover) { opacity: .85; }
|
||||
.page-toolbar :deep(.action-btn.secondary) {
|
||||
background: #fff; color: var(--brand-primary); border: 1px solid var(--brand-primary);
|
||||
}
|
||||
.page-toolbar :deep(.action-btn.secondary:hover) { background: rgba(30,58,138,.06); }
|
||||
.hint-list p { margin-bottom: 4px; }
|
||||
.hint-list p:last-child { margin-bottom: 0; }
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<template>
|
||||
<div class="manager-workbench">
|
||||
<div class="page-card manager-workbench">
|
||||
<div class="breadcrumb">首页 / 工作台</div>
|
||||
|
||||
<!-- 我的项目策划方案 标题 (原 top=142) -->
|
||||
<h2 class="page-title">我的项目策划方案</h2>
|
||||
|
||||
<!-- 5 个 KPI 卡 (原 top=165 已结算/已结题 + top=458 已执行/未执行) -->
|
||||
<!-- 5 个 KPI 卡 -->
|
||||
<div class="stats-grid">
|
||||
<router-link class="stat-card" to="/manager/projects">
|
||||
<div class="stat-label">项目总数量</div>
|
||||
@@ -30,7 +28,7 @@
|
||||
|
||||
<p class="hint-text">*点击数字,跳转到相应的列表页</p>
|
||||
|
||||
<!-- 消息通知 (新增) -->
|
||||
<!-- 消息通知 -->
|
||||
<section class="section">
|
||||
<h2 class="section-title">消息通知<a class="more" href="javascript:void(0)">更多 →</a></h2>
|
||||
<ul class="notice-list">
|
||||
@@ -100,23 +98,61 @@ onMounted(() => { loadStats(); loadNotices() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-workbench { padding: 16px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; color: #1a1a1a; margin-bottom: 16px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
|
||||
/* 5 个 KPI 卡 (跟 executor/Overview 一致) */
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 12px; }
|
||||
.stat-card {
|
||||
background: #fff; border-radius: 4px; padding: 24px;
|
||||
border: 1px solid #ebeef5; cursor: pointer; text-decoration: none; color: inherit;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity .15s, border-color .15s;
|
||||
min-height: 120px;
|
||||
}
|
||||
.stat-card:hover { border-color: var(--brand-primary); opacity: .9; }
|
||||
.stat-label { font-size: 14px; color: #606266; margin-bottom: 16px; }
|
||||
.stat-value { font-size: 32px; font-weight: 600; color: var(--brand-primary); line-height: 1.2; }
|
||||
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0 20px; text-align: center; }
|
||||
.section { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.section-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 16px; padding-left: 10px; border-left: 3px solid var(--brand-primary); display: flex; justify-content: space-between; }
|
||||
|
||||
/* 消息通知 */
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.more { font-size: 12px; color: var(--brand-primary); text-decoration: none; }
|
||||
.more:hover { text-decoration: underline; }
|
||||
.notice-list { list-style: none; border-top: 1px solid #f0f0f0; }
|
||||
.notice-item { padding: 12px 0; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; gap: 12px; }
|
||||
.notice-item:last-child { border-bottom: none; }
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<el-input
|
||||
v-if="myPersonId"
|
||||
v-model="form.orgName"
|
||||
placeholder="请填写所属公司 (TODO: 改为 biz_org 下拉)"
|
||||
placeholder="请填写所属公司"
|
||||
/>
|
||||
<el-input
|
||||
v-else
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
筛选区 (top 165-205, 按 left): 项目策划方案管理 / 项目总数量 / 已结算的会议 / 已结题的项目
|
||||
列头 (top 285-335, 按 left): 工作台 / 会议管理
|
||||
-->
|
||||
<div class="sponsor-home">
|
||||
<div class="page-card sponsor-home">
|
||||
<div class="breadcrumb">首页</div>
|
||||
|
||||
<!-- KPI: 5 个数字面板, 1 行展示 -->
|
||||
<div class="stats-grid stats-grid-5">
|
||||
@@ -100,7 +101,9 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.sponsor-home { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
|
||||
.stats-grid { display: grid; gap: 16px; margin-bottom: 16px; }
|
||||
.stats-grid-5 { grid-template-columns: repeat(5, 1fr); }
|
||||
@@ -109,28 +112,44 @@ onMounted(() => {
|
||||
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.stat-card:hover { border-color: var(--brand-primary); box-shadow: 0 2px 8px rgba(30,58,138,0.15); }
|
||||
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.stat-value { font-size: 32px; font-weight: 700; color: var(--brand-primary); line-height: 1; margin-bottom: 8px; }
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.stat-desc { font-size: 12px; color: #bfbfbf; }
|
||||
|
||||
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0; text-align: center; }
|
||||
|
||||
.section { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #f0f0f0; }
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px; font-weight: 600; color: #1a1a1a;
|
||||
margin-bottom: 16px; padding-left: 10px;
|
||||
border-left: 3px solid var(--brand-primary);
|
||||
line-height: 1;
|
||||
display: flex; justify-content: space-between;
|
||||
}
|
||||
.more { font-size: 12px; color: var(--brand-primary); text-decoration: none; }
|
||||
.more:hover { text-decoration: underline; }
|
||||
.notice-list { list-style: none; border-top: 1px solid #f0f0f0; }
|
||||
.notice-item {
|
||||
padding: 12px 0; border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
@@ -196,8 +196,8 @@ async function loadList() {
|
||||
}
|
||||
delete params.startTimeRange
|
||||
const r = await bizList('meeting', params)
|
||||
list.value = r.rows || []
|
||||
page.total = r.total || 0
|
||||
list.value = (r.data && r.data.rows) || r.rows || []
|
||||
page.total = (r.data && r.data.total) || r.total || 0
|
||||
} catch (e) { list.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: { '@': path.resolve(__dirname, 'src') }
|
||||
},
|
||||
build: {
|
||||
// 老浏览器兼容: 把 ?. / ?? / 箭头函数等 ES2020+ 语法降级到 es2015
|
||||
target: 'es2015'
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
host: '0.0.0.0',
|
||||
|
||||
+32
-39
@@ -60,17 +60,19 @@ SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京
|
||||
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '其他费用', 1000.00, 1, 1000.00, '茶水等', '已审核', 'labor' FROM biz_meeting WHERE business_id='5643145673';
|
||||
|
||||
-- 专家
|
||||
-- audit_status: 0=未提交 1=待审核 2=通过 3=拒绝 (BizAuditStatusEnum)
|
||||
-- status: Y=正常 N=禁用
|
||||
INSERT INTO biz_expert(name, phone, work_unit, department, title, region, id_card, audit_status, audit_by, audit_time, status, create_by, create_time)
|
||||
VALUES
|
||||
('张三', '13534621147', '北京XXXXXXX医院', '呼吸科', '主任医师', '北京', '110101198501011234', '待审核', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('李四', '13534621148', '北京XXXXXXX医院', '血液科', '副主任医师', '北京', '110101198801012345', '审核通过', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('王五', '13534621149', '北京XXXXXXX医院', '心血管科', '主治医师', '上海', '310101198901013456', '待审核', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('赵六', '13534621150', '北京XXXXXXX医院', '神经科', '副主任医师', '广东', '440101198701014567', '已退回', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('钱七', '13534621151', '北京XXXXXXX医院', '内分泌科', '主任医师', '北京', '110101199001015678', '审核通过', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('陈教授', '13534621152', '北京XXXXXXX医院', '消化科', '副主任医师', '北京', '110101198601016789', '审核通过', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('王教授', '13534621153', '北京XXXXXXX医院', '心血管科', '主任医师', '上海', '310101198401017890', '审核通过', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('李教授', '13534621154', '北京XXXXXXX医院', '神经科', '主治医师', '北京', '110101199201018901', '审核通过', 'admin', NOW(), '0', 'admin', NOW()),
|
||||
('张教授', '13534621155', '北京XXXXXXX医院', '骨科', '主任医师', '北京', '110101198501019012', '已驳回', 'admin', NOW(), '0', 'admin', NOW());
|
||||
('张三', '13534621147', '北京XXXXXXX医院', '呼吸科', '主任医师', '北京', '110101198501011234', '1', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('李四', '13534621148', '北京XXXXXXX医院', '血液科', '副主任医师', '北京', '110101198801012345', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('王五', '13534621149', '北京XXXXXXX医院', '心血管科', '主治医师', '上海', '310101198901013456', '1', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('赵六', '13534621150', '北京XXXXXXX医院', '神经科', '副主任医师', '广东', '440101198701014567', '3', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('钱七', '13534621151', '北京XXXXXXX医院', '内分泌科', '主任医师', '北京', '110101199001015678', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('陈教授', '13534621152', '北京XXXXXXX医院', '消化科', '副主任医师', '北京', '110101198601016789', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('王教授', '13534621153', '北京XXXXXXX医院', '心血管科', '主任医师', '上海', '310101198401017890', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('李教授', '13534621154', '北京XXXXXXX医院', '神经科', '主治医师', '北京', '110101199201018901', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
|
||||
('张教授', '13534621155', '北京XXXXXXX医院', '骨科', '主任医师', '北京', '110101198501019012', '3', 'admin', NOW(), 'Y', 'admin', NOW());
|
||||
|
||||
-- 公司表 (赞助方/执行方共用)
|
||||
INSERT INTO biz_org(org_name, org_type, address, tax_no, contact_name, contact_phone, intent_count, status, create_by, create_time)
|
||||
@@ -97,23 +99,23 @@ VALUES
|
||||
-- 人员(执行方会务执行, org_id 对应 biz_execution_unit 那 8 条, 取前 5 个 org_id)
|
||||
INSERT INTO biz_person(name, phone, org_id, department, position, role, unit_type, status, create_by, create_time)
|
||||
VALUES
|
||||
('张三', '13534621147', 1, '会务部', '经理', '会务执行', 'execution', '0', 'admin', NOW()),
|
||||
('李四', '13534621148', 1, '会务部', '专员', '会务执行', 'execution', '0', 'admin', NOW()),
|
||||
('王五', '13534621149', 2, '会务部', '主管', '会务执行', 'execution', '0', 'admin', NOW()),
|
||||
('赵六', '13534621150', 2, '会务部', '专员', '会务执行', 'execution', '0', 'admin', NOW()),
|
||||
('钱七', '13534621151', 3, '会务部', '经理', '会务执行', 'execution', '0', 'admin', NOW()),
|
||||
('孙八', '13534621152', 3, '会务部', '专员', '会务执行', 'execution', '0', 'admin', NOW()),
|
||||
('周九', '13534621153', 4, '会务部', '主管', '会务执行', 'execution', '1', 'admin', NOW()),
|
||||
('吴十', '13534621154', 4, '会务部', '专员', '会务执行', 'execution', '0', 'admin', NOW());
|
||||
('张三', '13534621147', 1, '会务部', '经理', '会务执行', 'executor', '0', 'admin', NOW()),
|
||||
('李四', '13534621148', 1, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW()),
|
||||
('王五', '13534621149', 2, '会务部', '主管', '会务执行', 'executor', '0', 'admin', NOW()),
|
||||
('赵六', '13534621150', 2, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW()),
|
||||
('钱七', '13534621151', 3, '会务部', '经理', '会务执行', 'executor', '0', 'admin', NOW()),
|
||||
('孙八', '13534621152', 3, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW()),
|
||||
('周九', '13534621153', 4, '会务部', '主管', '会务执行', 'executor', '1', 'admin', NOW()),
|
||||
('吴十', '13534621154', 4, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW());
|
||||
|
||||
-- 人员(合规方项目负责人/会议执行/监察员, org_id 对应 sponsor 类型 biz_org 前 5 个)
|
||||
INSERT INTO biz_person(name, phone, org_id, department, position, role, unit_type, status, create_by, create_time)
|
||||
VALUES
|
||||
('张三', '13800138001', 1, '合规部', '经理', '项目负责人', 'execution', '0', 'admin', NOW()),
|
||||
('李四', '13800138002', 2, '合规部', '专员', '会议执行', 'execution', '0', 'admin', NOW()),
|
||||
('王五', '13800138003', 3, '监察组', '组长', '监察员', 'execution', '0', 'admin', NOW()),
|
||||
('赵六', '13800138004', 4, '监察组', '专员', '监察员', 'execution', '0', 'admin', NOW()),
|
||||
('钱七', '13800138005', 5, '合规部', '主管', '项目负责人', 'execution', '0', 'admin', NOW());
|
||||
('张三', '13800138001', 1, '合规部', '经理', '项目负责人', 'executor', '0', 'admin', NOW()),
|
||||
('李四', '13800138002', 2, '合规部', '专员', '会议执行', 'executor', '0', 'admin', NOW()),
|
||||
('王五', '13800138003', 3, '监察组', '组长', '监察员', 'executor', '0', 'admin', NOW()),
|
||||
('赵六', '13800138004', 4, '监察组', '专员', '监察员', 'executor', '0', 'admin', NOW()),
|
||||
('钱七', '13800138005', 5, '合规部', '主管', '项目负责人', 'executor', '0', 'admin', NOW());
|
||||
|
||||
-- 支持意向
|
||||
INSERT INTO biz_support_intent(project_no, project_name, name, work_unit, department, position, phone, account_status, create_time)
|
||||
@@ -192,27 +194,18 @@ VALUES
|
||||
('LV-2026-003', 3, '智慧医院建设项目方案论证', '李教授', '440101********9012', '6222****3456', 4000.00, '2026-03-16 10:00:00', '已审核'),
|
||||
('LV-2026-004', 4, '基层医疗改革试点中期评估', '张教授', '320101********3456', '6222****7890', 3500.00, '2026-02-02 10:00:00', '已驳回');
|
||||
|
||||
-- 业务角色绑定(为 sys_user 建立业务角色映射)
|
||||
-- 业务角色绑定 (2026-08-16 重构: 统一存 sys_user.role_type, 不再使用 biz_user_role_bind)
|
||||
-- admin: 1=后台管理员
|
||||
INSERT INTO biz_user_role_bind(user_id, role_type, unit_type, create_time)
|
||||
SELECT user_id, 'admin', NULL, NOW() FROM sys_user WHERE user_name='admin';
|
||||
UPDATE sys_user SET role_type='admin' WHERE user_name='admin';
|
||||
|
||||
-- 创建 5 个业务角色用户 (密码 123456, RuoYi 加密后)
|
||||
INSERT INTO sys_user(user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, create_by, create_time, remark)
|
||||
INSERT INTO sys_user(user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, create_by, create_time, remark, role_type)
|
||||
VALUES
|
||||
('leader01', '项目负责人张三', '00', 'leader01@bahim.org.cn', '13900139001', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '项目负责人'),
|
||||
('manager01', '合规员李四', '00', 'manager01@bahim.org.cn', '13900139002', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '合规人员'),
|
||||
('doctor01', '专家王五', '00', 'doctor01@bahim.org.cn', '13900139003', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '专家'),
|
||||
('executor01', '执行负责人赵六', '00', 'executor01@bahim.org.cn', '13900139004', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '执行方'),
|
||||
('sponsor01', '赞助方钱七', '00', 'sponsor01@bahim.org.cn', '13900139005', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '赞助方');
|
||||
|
||||
-- 绑定业务角色
|
||||
INSERT INTO biz_user_role_bind(user_id, role_type, unit_type, create_time)
|
||||
SELECT user_id, 'leader', NULL, NOW() FROM sys_user WHERE user_name='leader01' UNION ALL
|
||||
SELECT user_id, 'manager', NULL, NOW() FROM sys_user WHERE user_name='manager01' UNION ALL
|
||||
SELECT user_id, 'doctor', NULL, NOW() FROM sys_user WHERE user_name='doctor01' UNION ALL
|
||||
SELECT user_id, 'executor', 'execution', NOW() FROM sys_user WHERE user_name='executor01' UNION ALL
|
||||
SELECT user_id, 'sponsor', 'sponsor', NOW() FROM sys_user WHERE user_name='sponsor01';
|
||||
('leader01', '项目负责人张三', '00', 'leader01@bahim.org.cn', '13900139001', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '项目负责人', 'leader'),
|
||||
('manager01', '合规员李四', '00', 'manager01@bahim.org.cn', '13900139002', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '合规人员', 'manager'),
|
||||
('doctor01', '专家王五', '00', 'doctor01@bahim.org.cn', '13900139003', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '专家', 'doctor'),
|
||||
('executor01', '执行负责人赵六', '00', 'executor01@bahim.org.cn', '13900139004', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '执行方', 'executor'),
|
||||
('sponsor01', '赞助方钱七', '00', 'sponsor01@bahim.org.cn', '13900139005', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '赞助方', 'sponsor');
|
||||
|
||||
-- 给所有业务用户配项目负责人角色(ruoyi role_id=2)
|
||||
INSERT INTO sys_user_role(user_id, role_id)
|
||||
|
||||
+4
-13
@@ -174,11 +174,11 @@ CREATE TABLE biz_expert (
|
||||
id_card_back_url VARCHAR(500) DEFAULT NULL COMMENT '身份证反面URL',
|
||||
practice_cert_url VARCHAR(500) DEFAULT NULL COMMENT '执业证书URL',
|
||||
title_cert_url VARCHAR(500) DEFAULT NULL COMMENT '职称证明URL',
|
||||
audit_status VARCHAR(20) DEFAULT '0' COMMENT '审核状态 待审核/审核通过/已退回',
|
||||
audit_status VARCHAR(20) DEFAULT '1' COMMENT '审核状态 0未提交 1待审核 2通过 3拒绝',
|
||||
audit_opinion VARCHAR(500) DEFAULT NULL COMMENT '审核意见',
|
||||
audit_by VARCHAR(64) DEFAULT NULL COMMENT '审核人',
|
||||
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
|
||||
status CHAR(1) DEFAULT '0' COMMENT '状态 0正常 1禁用',
|
||||
status CHAR(1) DEFAULT 'Y' COMMENT '状态 Y正常 N禁用',
|
||||
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
@@ -392,17 +392,8 @@ CREATE TABLE biz_labor_voucher (
|
||||
UNIQUE KEY uk_voucher_no (voucher_no)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='劳务凭证表';
|
||||
|
||||
DROP TABLE IF EXISTS biz_user_role_bind;
|
||||
CREATE TABLE biz_user_role_bind (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
user_id BIGINT NOT NULL COMMENT '用户ID',
|
||||
role_type VARCHAR(20) NOT NULL COMMENT '业务角色 admin/leader/manager/doctor/executor/sponsor (注: 原 supplier 角色已统一为 executor, 见 2026-08-15 重构)',
|
||||
unit_id BIGINT DEFAULT NULL COMMENT '所属单位ID',
|
||||
unit_type VARCHAR(20) DEFAULT NULL COMMENT '所属单位类型',
|
||||
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_urb_user (user_id, role_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户业务角色绑定';
|
||||
-- 注: 2026-08-16 重构, 删 biz_user_role_bind 表, 业务角色统一存 sys_user.role_type
|
||||
-- sys_user 表在 RuoYi 标准 schema 里已含 role_type 列 (admin/leader/manager/doctor/executor/sponsor)
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
-- ============================================================
|
||||
-- Migration: biz_expert.audit_status 0/1/2/中文 → 0/1/2/3 (语义扩展)
|
||||
-- Date: 2026-08-16
|
||||
-- Author: Claude
|
||||
-- 影响范围: biz_expert 表的 audit_status 字段
|
||||
-- 旧语义: '0' = 待审核, '1' = 通过, '2' = 退回 (还有中文脏数据: '审核通过'/'待审核'/'已退回'/'已驳回')
|
||||
-- 新语义: '0' = 未提交, '1' = 待审核, '2' = 通过, '3' = 拒绝
|
||||
-- 注意: status 字段已改成 Y/N (见 migration_status_YN_2026_08_16.sql)
|
||||
-- ============================================================
|
||||
|
||||
-- 0. 备份 (必须做, 用于回滚)
|
||||
CREATE TABLE biz_expert_audit_status_bak_20260816 AS
|
||||
SELECT expert_id, name, audit_status FROM biz_expert;
|
||||
|
||||
-- 1. 单条 CASE UPDATE, 一次搞定 (避免 cascading: '0'→'1' 后被 '1'→'2' 误改)
|
||||
UPDATE biz_expert
|
||||
SET audit_status = CASE audit_status
|
||||
WHEN '0' THEN '1' -- 待审核 (旧) → 待审核 (新)
|
||||
WHEN '1' THEN '2' -- 通过 (旧) → 通过 (新)
|
||||
WHEN '2' THEN '3' -- 退回 (旧) → 拒绝 (新)
|
||||
WHEN '审核通过' THEN '2'
|
||||
WHEN '待审核' THEN '1'
|
||||
WHEN '已退回' THEN '3'
|
||||
WHEN '已驳回' THEN '3'
|
||||
ELSE audit_status -- 未识别的值不动, 人工排查
|
||||
END
|
||||
WHERE audit_status IN ('0','1','2','审核通过','待审核','已退回','已驳回');
|
||||
|
||||
-- 2. ALTER 默认值 + comment
|
||||
ALTER TABLE biz_expert
|
||||
MODIFY COLUMN audit_status VARCHAR(20) DEFAULT '1' COMMENT '审核状态 0未提交 1待审核 2通过 3拒绝';
|
||||
|
||||
-- 3. 验证 (执行后应全部为 1/2/3)
|
||||
-- SELECT audit_status, COUNT(*) FROM biz_expert GROUP BY audit_status ORDER BY audit_status;
|
||||
-- 预期结果 (取决于生产数据):
|
||||
-- 1 | 待审核
|
||||
-- 2 | 通过
|
||||
-- 3 | 拒绝
|
||||
|
||||
-- 4. 若有数据未转换 (上面 ELSE 分支), 用 SELECT 排查:
|
||||
-- SELECT expert_id, name, audit_status FROM biz_expert
|
||||
-- WHERE audit_status NOT IN ('0','1','2','3') OR audit_status IS NULL;
|
||||
|
||||
-- 5. 确认无误后, 可删除备份表:
|
||||
-- DROP TABLE biz_expert_audit_status_bak_20260816;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- ============================================================
|
||||
-- Migration: biz_expert.status 0/1 → Y/N
|
||||
-- Date: 2026-08-16
|
||||
-- Author: Claude
|
||||
-- 影响范围: biz_expert 表的 status 字段
|
||||
-- 旧语义: '0' = 正常, '1' = 禁用
|
||||
-- 新语义: 'Y' = 正常, 'N' = 禁用
|
||||
-- 注意: audit_status (0/1/2) 不变
|
||||
-- ============================================================
|
||||
|
||||
-- 0. 备份 (可选, 已上生产前一定要做)
|
||||
-- CREATE TABLE biz_expert_status_bak_20260816 AS
|
||||
-- SELECT expert_id, status FROM biz_expert;
|
||||
|
||||
-- 1. 数据迁移 (先转换旧值, 避免默认值变化后插入冲突)
|
||||
UPDATE biz_expert SET status = 'Y' WHERE status = '0';
|
||||
UPDATE biz_expert SET status = 'N' WHERE status = '1';
|
||||
|
||||
-- 2. 表结构变更 (默认值 + 注释)
|
||||
ALTER TABLE biz_expert
|
||||
MODIFY COLUMN status CHAR(1) DEFAULT 'Y' COMMENT '状态 Y正常 N禁用';
|
||||
|
||||
-- 3. 验证 (执行后应全部为 Y/N)
|
||||
-- SELECT status, COUNT(*) FROM biz_expert GROUP BY status;
|
||||
-- 预期结果:
|
||||
-- Y | 10
|
||||
-- N | 0
|
||||
Reference in New Issue
Block a user