Files
guoju0808/proto/html/components/project-assign-1.html
T
郭庆泰 cf5790229a refactor: 合并 biz_support_unit/biz_execution_unit/biz_service_org 为 biz_org, 删除 2 张孤儿表, 改 biz_person.work_unit → org_id FK
主要改动:
- SQL: biz_support_unit → biz_org, 加 org_type, 加 business_nature; 删 biz_execution_unit, biz_service_org
- SQL: biz_project.support_unit_id/name + service_org_id/name → org_id/name/type
- SQL: biz_meeting.support_unit_name → org_name
- SQL: biz_person.work_unit → org_id (FK)
- 后端: 新 BizOrg entity/mapper/service/controller
- 后端: BizProject/BizMeeting 字段重命名
- 后端: 删 12 个 BizSupportUnit/BizExecutionUnit/BizServiceOrg Java 文件
- 后端: BizPersonImportVO.workUnit → orgName (导入时查 biz_org 取 org_id)
- 后端: BizAuthController.registerExecutor 完整实现 (原 registerSupplier stub)
- 前端: 新 admin/Orgs.vue + manager/Orgs.vue (原 SupportUnits)
- 前端: RegisterExecutor.vue (原 RegisterSupplier, 单页 2 步)
- 前端: sponsor/executor/manager 多个文件 workUnit → orgId/orgName 重命名
- 前端: 统一 supplier → executor, 业务命名 sponsor(赞助方) / executor(执行方=供应商)
- 前端: 全工程 execution → executor (company type / role / person unit_type)
2026-08-15 12:41:10 +08:00

602 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目分配 v1 - 项目管理系统</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
color: #333;
background: #fafafa;
min-height: 100vh;
}
.content {
padding: 24px 32px;
}
/* ============= 面包屑 ============= */
.breadcrumb {
font-size: 13px;
color: #8c8c8c;
margin-bottom: 12px;
}
.breadcrumb a {
color: #1890ff;
text-decoration: none;
}
.breadcrumb a:hover { text-decoration: underline; }
/* ============= 页头 ============= */
.page-header {
text-align: center;
margin-bottom: 24px;
}
.page-title {
font-size: 22px;
font-weight: 700;
color: #1a1a1a;
}
/* ============= 卡片 ============= */
.card {
background: #fff;
border-radius: 8px;
padding: 20px 24px;
margin-bottom: 16px;
border: 1px solid #f0f0f0;
}
.card-title {
font-size: 15px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 16px;
padding-left: 10px;
border-left: 3px solid #1890ff;
line-height: 1;
}
/* ============= 红色提示 ============= */
.hint-text {
font-size: 12px;
color: #ff4d4f;
line-height: 1.6;
margin: 8px 0;
}
.hint-text p { margin-bottom: 4px; }
.hint-text p:last-child { margin-bottom: 0; }
/* ============= 信息展示(label + value ============= */
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px 24px;
}
.info-item {
display: flex;
align-items: center;
gap: 8px;
}
.info-label {
font-size: 14px;
color: #595959;
white-space: nowrap;
flex-shrink: 0;
min-width: 110px;
text-align: right;
}
.info-value {
font-size: 13px;
color: #1a1a1a;
}
/* ============= 表单行 ============= */
.filter-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 14px;
}
.filter-label {
font-size: 14px;
color: #595959;
white-space: nowrap;
flex-shrink: 0;
min-width: 110px;
text-align: right;
position: relative;
}
.filter-label .required {
color: #ff4d4f;
margin-right: 4px;
}
.filter-input,
.filter-select {
flex: 1;
height: 32px;
padding: 0 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 13px;
color: #333;
background: #fff;
outline: none;
}
.filter-input:focus,
.filter-select:focus {
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24,144,255,0.1);
}
.filter-select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpolyline name='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 14px;
padding-right: 24px;
}
/* ============= 执行方列表 ============= */
.exec-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 12px;
}
.exec-row {
display: flex;
align-items: center;
gap: 12px;
}
.exec-row .exec-num {
font-size: 13px;
color: #595959;
width: 24px;
flex-shrink: 0;
}
.exec-row .exec-select {
width: 200px;
height: 32px;
padding: 0 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 13px;
color: #333;
background: #fff;
outline: none;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpolyline name='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 14px;
padding-right: 24px;
}
.exec-row .field-group {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
}
.exec-row .field-group .small-label {
font-size: 13px;
color: #595959;
white-space: nowrap;
flex-shrink: 0;
}
.exec-row .field-group .small-input {
width: 140px;
height: 32px;
padding: 0 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 13px;
color: #333;
background: #fff;
outline: none;
}
.exec-row .field-group .small-input:focus,
.exec-row .exec-select:focus {
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24,144,255,0.1);
}
.exec-actions {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.op-link {
color: #1890ff;
background: none;
border: none;
padding: 0;
cursor: pointer;
font-size: 13px;
}
.op-link:hover { text-decoration: underline; }
.exec-add {
margin-top: 4px;
}
/* ============= 截止时间 ============= */
.deadline-row {
display: flex;
align-items: center;
gap: 8px;
}
.deadline-row .filter-label {
min-width: 130px;
}
.deadline-row .filter-input {
flex: 1;
max-width: 380px;
}
.deadline-row .unit {
font-size: 13px;
color: #595959;
margin-left: -4px;
}
/* ============= 合同 ============= */
.contract-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.contract-row {
display: flex;
align-items: center;
gap: 12px;
}
.contract-row .filter-label {
min-width: 110px;
}
.file-link {
color: #1890ff;
text-decoration: none;
font-size: 13px;
cursor: pointer;
}
.file-link:hover { text-decoration: underline; }
/* ============= 底部按钮 ============= */
.form-actions {
display: flex;
justify-content: center;
gap: 16px;
padding: 24px 0;
}
.filter-btn {
height: 32px;
padding: 0 24px;
background: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
white-space: nowrap;
}
.filter-btn:hover { background: #096dd9; }
.filter-btn.default {
background: #fff;
color: #333;
border: 1px solid #d9d9d9;
}
.filter-btn.default:hover {
border-color: #1890ff;
color: #1890ff;
}
.form-actions .filter-btn {
height: 40px;
padding: 0 32px;
font-size: 14px;
}
/* ============= 响应式 ============= */
@media (max-width: 768px) {
.content { padding: 16px; }
.info-grid { grid-template-columns: 1fr; }
.filter-row,
.deadline-row { flex-direction: column; align-items: stretch; }
.filter-label { width: auto; text-align: left; }
.exec-row { flex-wrap: wrap; }
.exec-row .exec-select,
.exec-row .field-group .small-input { width: 100%; }
.form-actions { flex-wrap: wrap; }
}
</style>
</head>
<body>
<main class="content">
<!-- ============= 面包屑 ============= -->
<div class="breadcrumb">
<a href="javascript:void(0)" onclick="history.back()">项目管理</a> &gt; 项目分配
</div>
<!-- ============= 页头 ============= -->
<div class="page-header">
<h1 class="page-title">项目分配</h1>
</div>
<!-- ============= 第一区块:项目基本信息(只读) ============= -->
<div class="card">
<div class="card-title">项目信息</div>
<div class="info-grid">
<div class="info-item">
<span class="info-label">项目编号:</span>
<span class="info-value">ZH-2026-658</span>
</div>
<div class="info-item">
<span class="info-label">项目名称:</span>
<span class="info-value">小牛血清创新应用研讨会</span>
</div>
<div class="info-item">
<span class="info-label">项目形式:</span>
<span class="info-value">线上</span>
</div>
<div class="info-item">
<span class="info-label">总场次/总期数:</span>
<span class="info-value">26</span>
</div>
<div class="info-item">
<span class="info-label">总金额:</span>
<span class="info-value">3,112,000</span>
</div>
<div class="info-item">
<span class="info-label">管理费及税金:</span>
<span class="info-value">1,112,000</span>
</div>
</div>
</div>
<!-- ============= 第二区块:支持单位 ============= -->
<div class="card">
<div class="filter-row">
<span class="filter-label"><span class="required">*</span>支持单位</span>
<input type="text" class="filter-input" placeholder="请输入支持单位(支持模糊查找)">
</div>
<p class="hint-text">*支持单位只有一个,支持模糊查找,选择项</p>
</div>
<!-- ============= 第三区块:执行单位 ============= -->
<div class="card">
<div class="filter-row">
<span class="filter-label"><span class="required">*</span>执行单位</span>
<span style="flex: 1;"></span>
</div>
<div class="exec-list" id="execList">
<div class="exec-row">
<span class="exec-num">1.</span>
<select class="exec-select">
<option value="">请选择执行单位</option>
<option value="1">北京XXXX公司</option>
<option value="2">上海XXXX公司</option>
</select>
<div class="field-group">
<span class="small-label">场数:</span>
<input type="number" class="small-input" placeholder="0">
<span class="small-label">总金额:</span>
<input type="text" class="small-input" placeholder="0.00">
</div>
<div class="exec-actions">
<button type="button" class="op-link js-add-exec">增加</button>
<button type="button" class="op-link js-del-exec" disabled>删除</button>
</div>
</div>
<div class="exec-row">
<span class="exec-num">2.</span>
<select class="exec-select">
<option value="">请选择执行单位</option>
<option value="1">北京XXXX公司</option>
<option value="2">上海XXXX公司</option>
</select>
<div class="field-group">
<span class="small-label">场数:</span>
<input type="number" class="small-input" placeholder="0">
<span class="small-label">总金额:</span>
<input type="text" class="small-input" placeholder="0.00">
</div>
<div class="exec-actions">
<button type="button" class="op-link js-add-exec">增加</button>
<button type="button" class="op-link js-del-exec">删除</button>
</div>
</div>
<div class="exec-row">
<span class="exec-num">3.</span>
<select class="exec-select">
<option value="">请选择执行单位</option>
<option value="1">北京XXXX公司</option>
<option value="2">上海XXXX公司</option>
</select>
<div class="field-group">
<span class="small-label">场数:</span>
<input type="number" class="small-input" placeholder="0">
<span class="small-label">总金额:</span>
<input type="text" class="small-input" placeholder="0.00">
</div>
<div class="exec-actions">
<button type="button" class="op-link js-add-exec">增加</button>
<button type="button" class="op-link js-del-exec">删除</button>
</div>
</div>
</div>
<button type="button" class="filter-btn default exec-add" id="addExecBtn">+ 增加执行方</button>
<p class="hint-text" style="margin-top: 12px;">
*支持多个执行方,给每个执行方分配场次和总金额;<br>
提交时验证多个执行方的总金额加起来是否超过项目总金额;<br>
执行方总金额显示在执行方的项目列表中对应项目的总金额
</p>
<p class="hint-text">
*如未选择执行单位,则场次和总金额为非必填项,如选择了执行单位,则为必填项<br>
*可用金额=总金额×(1-管理费税费)-累计劳务-累计会务(总发票的金额)<br>
*项目列表中的为该项目的总数,执行方项目列表中显示的为该执行方的总金额,计算公式保持一致<br>
*选择后,该项目则出现在支持单位管理员的项目列表中
</p>
</div>
<!-- ============= 第四区块:提交材料截止时间 ============= -->
<div class="card">
<div class="filter-row deadline-row">
<span class="filter-label">提交材料截止时间</span>
<input type="number" class="filter-input" placeholder="请输入天数">
<span class="unit"></span>
</div>
<p class="hint-text">*按照会议截止时间,自然日,往后推对应的天数。如果提交后,被退回了,则时间重新开始计算。</p>
<p class="hint-text">*天数最大不能超过100,超过后报错提示</p>
<p class="hint-text">*每天0点取一次,合同可能会有多个,显示多个</p>
<p style="margin-top: 12px;">
<a class="file-link" href="javascript:void(0)">合同文件2</a>
</p>
</div>
<!-- ============= 第五区块:合同文件 ============= -->
<div class="card">
<div class="contract-list">
<div class="contract-row">
<span class="filter-label">支持合同:</span>
<a class="file-link" href="javascript:void(0)">合同文件</a>
</div>
<div class="contract-row">
<span class="filter-label">执行合同:</span>
<a class="file-link" href="javascript:void(0)">合同文件</a>
<a class="file-link" href="javascript:void(0)">合同文件2</a>
</div>
</div>
<p class="hint-text" style="margin-top: 12px;">*根据项目编号,同步OA的合同文件,可预览</p>
</div>
<!-- ============= 底部按钮 ============= -->
<div class="form-actions">
<button type="button" class="filter-btn" id="btnSave">保存</button>
<button type="button" class="filter-btn default" id="btnCancel">取消</button>
</div>
</main>
<script>
// ============= 增加执行方 =============
function bindExecRow(row) {
const addBtn = row.querySelector('.js-add-exec');
const delBtn = row.querySelector('.js-del-exec');
addBtn.addEventListener('click', () => addExecRow());
delBtn.addEventListener('click', () => {
const list = document.getElementById('execList');
if (list.children.length > 1) {
row.remove();
renumberExec();
updateDelButtons();
} else {
alert('至少保留一行');
}
});
}
function renumberExec() {
document.querySelectorAll('#execList .exec-row').forEach((row, i) => {
row.querySelector('.exec-num').textContent = (i + 1) + '.';
});
}
function updateDelButtons() {
const rows = document.querySelectorAll('#execList .exec-row');
rows.forEach((row, i) => {
const btn = row.querySelector('.js-del-exec');
btn.disabled = rows.length === 1 && i === 0;
});
}
function addExecRow() {
const list = document.getElementById('execList');
const row = document.createElement('div');
row.className = 'exec-row';
row.innerHTML = `
<span class="exec-num"></span>
<select class="exec-select">
<option value="">请选择执行单位</option>
<option value="1">北京XXXX公司</option>
<option value="2">上海XXXX公司</option>
</select>
<div class="field-group">
<span class="small-label">场数:</span>
<input type="number" class="small-input" placeholder="0">
<span class="small-label">总金额:</span>
<input type="text" class="small-input" placeholder="0.00">
</div>
<div class="exec-actions">
<button type="button" class="op-link js-add-exec">增加</button>
<button type="button" class="op-link js-del-exec">删除</button>
</div>
`;
list.appendChild(row);
bindExecRow(row);
renumberExec();
updateDelButtons();
}
// 给现有 3 行绑定事件
document.querySelectorAll('#execList .exec-row').forEach(bindExecRow);
updateDelButtons();
// ============= 底部按钮 =============
document.getElementById('btnSave').addEventListener('click', () => {
alert('保存成功');
});
document.getElementById('btnCancel').addEventListener('click', () => {
if (confirm('确定取消项目分配?未保存的内容将丢失')) {
history.back();
}
});
</script>
</body>
</html>