Explorar o código

feat: 新增合同管理模块

xieyong hai 1 mes
pai
achega
b88b07dd98

+ 133 - 0
src/styles/views/contractManagement/index.scss

@@ -0,0 +1,133 @@
+
+.cm-page,.cm-page *{box-sizing:border-box}
+.cm-page{min-height:100%;padding:22px;color:#22334a;background:#f3f6fb;--primary:#1768e5}
+.cm-page .cm-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:14px;background:#fff;border:1px solid #e0e7f1;border-radius:13px;padding:14px 18px;box-shadow:0 7px 22px rgba(40,67,106,.05)}
+.cm-page .cm-toolbar .cm-tools-right{margin-left:auto;display:flex;gap:8px}
+.cm-page .cm-filter-bar{display:flex;align-items:center;gap:0;background:#fff;border:1px solid #e0e7f1;border-radius:13px;padding:14px 18px;margin-bottom:14px;box-shadow:0 7px 22px rgba(40,67,106,.05);flex-wrap:wrap}
+.cm-page .cm-filter-bar .cm-filter-cell{display:flex;align-items:center;gap:8px;padding:0 14px;border-right:1px dashed #e1e7ef;min-width:180px}
+.cm-page .cm-filter-bar .cm-filter-cell:last-child{border-right:0}
+.cm-page .cm-filter-bar .cm-filter-cell .cm-filter-label{color:#4a586d;font-size:12px;white-space:nowrap}
+.cm-page .cm-filter-bar .el-select,.cm-page .cm-filter-bar .el-input{flex:1;min-width:120px;width:auto}
+.cm-page .cm-filter-bar .el-input__inner,.cm-page .cm-filter-bar .el-select .el-input__inner{height:32px;line-height:32px;font-size:12px;padding:0 8px}
+.cm-page .cm-filter-bar .el-input__inner:focus,.cm-page .cm-filter-bar .el-select .el-input__inner:focus{border-color:#1768e5}
+.cm-page .cm-filter-bar .el-input__suffix{right:4px}
+.cm-page .cm-filter-bar .cm-filter-actions{border-right:0;min-width:auto;padding:0 0 0 12px;margin-left:auto;display:flex;gap:8px}
+.cm-page .cm-workspace{background:#fff;border:1px solid #e0e7f1;border-radius:13px;box-shadow:0 7px 22px rgba(40,67,106,.05);overflow:hidden}
+.cm-page .cm-workspace .ele-table-tool{min-height:60px;padding:14px 18px;border-bottom:1px solid #edf0f3}
+.cm-page .cm-workspace ::v-deep th.el-table__cell{background:#f6f8fb;color:#68778c;font-weight:600;padding:12px 8px;border:0;border-bottom:1px solid #edf0f3;text-align:center;white-space:nowrap}
+.cm-page .cm-workspace ::v-deep td.el-table__cell{padding:9px 0;border-bottom:1px solid #edf0f3;background:#fff;color:#2a3a51;font-size:12px}
+.cm-page .cm-workspace ::v-deep .el-table__row:hover>td.el-table__cell{background:#f2f7ff!important}
+.cm-page .cm-duty{display:block;padding:4px 12px;text-align:left;line-height:1.6;color:#44546b}
+.cm-page .cm-link{color:#1768e5;cursor:pointer;background:none;border:0;font-size:12px;padding:0}
+.cm-page .cm-link:hover{text-decoration:underline}
+.cm-page .cm-action{display:flex;align-items:center;justify-content:center;gap:0;flex-wrap:nowrap}
+.cm-page .cm-action .cm-link{padding:0 4px;white-space:nowrap}
+.cm-page .cm-action .cm-link+.cm-link{border-left:1px solid #e1e7ef}
+.cm-page .cm-empty{min-height:120px;display:grid;place-content:center;justify-items:center;color:#7e8b9d}
+.cm-page .cm-empty i{font-size:32px;color:#c4cdd9;display:block;margin-bottom:8px}
+.cm-page .cm-empty p{margin:6px 0 0;font-size:12px}
+.cm-page .cm-status{display:inline-block;padding:2px 10px;border-radius:11px;font-size:11px}
+.cm-page .cm-status.active{color:#168d69;background:#e7f7f1}
+.cm-page .cm-status.pending{color:#c67d23;background:#fff3e2}
+.cm-page .cm-status.trial{color:#1768e5;background:#eaf2ff}
+.cm-page .cm-status.expired{color:#d55752;background:#ffe9e8}
+.cm-page .cm-status.terminated{color:#7e8b9d;background:#eef0f4}
+.cm-page .cm-status.changed{color:#7553be;background:#f0ebfb}
+.cm-page .cm-status.archived{color:#5a6a7c;background:#e8edf3}
+.cm-page .cm-status.expiring{color:#db7a25;background:#fff1e5}
+.cm-page .cm-status.signing{color:#1768e5;background:#eaf2ff}
+
+.cm-change-page{padding:0}
+.cm-change-header{background:#fff;padding:18px 22px;border-bottom:1px solid #e1e7ef}
+.cm-change-header h2{margin:0 0 10px;font-size:18px;color:#243955;font-weight:600}
+.cm-change-header h2 i{color:#1768e5;margin-right:6px}
+.cm-change-employee{display:flex;align-items:center;gap:24px;flex-wrap:wrap;font-size:12px;color:#4a586d}
+.cm-change-employee span{display:flex;align-items:center;gap:6px}
+.cm-change-employee span b{color:#243955;font-weight:500;margin-left:2px}
+.cm-change-section{padding:18px 22px;background:#fff;border-bottom:1px solid #edf1f6}
+.cm-change-section-title{font-size:14px;color:#243955;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:6px}
+.cm-change-section-title::before{content:"";display:inline-block;width:3px;height:14px;background:#1768e5;border-radius:1px}
+.cm-change-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 24px}
+.cm-change-grid .el-form-item{margin-bottom:14px}
+.cm-change-grid .el-form-item__label{padding-bottom:4px;color:#4a586d;font-size:12px;line-height:18px}
+.cm-change-grid .el-select,.cm-change-grid .el-date-editor,.cm-change-grid .el-input{width:100%}
+.cm-change-grid .el-input__inner,.cm-change-grid .el-select .el-input__inner{height:32px;line-height:32px;font-size:13px}
+.cm-change-section .el-form-item.is-required .el-form-item__label::before{content:"*";color:#d55752;margin-right:3px}
+.cm-sign-method{display:flex;gap:8px}
+.cm-sign-method .cm-method-btn{flex:1;height:34px;border:1px solid #1768e5;border-radius:4px;background:#fff;color:#1768e5;font-size:13px;cursor:pointer;transition:all .15s}
+.cm-sign-method .cm-method-btn.is-active{background:#1768e5;color:#fff;box-shadow:0 2px 6px rgba(23,104,229,.3)}
+.cm-sign-method .cm-method-btn:hover:not(.is-active){background:#eaf2ff}
+.cm-change-notes{font-size:11px;color:#7e8b9d;line-height:1.8;padding:10px 12px;background:#fafbfd;border-radius:4px;margin-top:8px}
+.cm-change-notes p{margin:0}
+.cm-change-notes p+p{margin-top:3px}
+.cm-change-notes i{color:#1768e5;margin-right:3px}
+.cm-compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:18px 22px;background:#fafbfd}
+.cm-compare-card{background:#fff;border:1px solid #e1e7ef;border-radius:6px;padding:14px 18px;position:relative}
+.cm-compare-card .cm-compare-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #edf1f6}
+.cm-compare-card .cm-compare-card-title{font-size:13px;font-weight:600;color:#243955;display:flex;align-items:center;gap:6px}
+.cm-compare-card .cm-compare-card-title::before{content:"";display:inline-block;width:3px;height:12px;background:#1768e5;border-radius:1px}
+.cm-compare-card .cm-compare-card-action{cursor:pointer;color:#9aa5b3;font-size:12px;background:none;border:0}
+.cm-compare-card .cm-compare-card-action:hover{color:#1768e5}
+.cm-compare-list{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px}
+.cm-compare-item{display:flex;flex-direction:column;gap:2px}
+.cm-compare-item .cm-compare-label{color:#7b899b;font-size:10px}
+.cm-compare-item .cm-compare-value{color:#243955;font-size:12px;font-weight:500}
+.cm-compare-item .cm-compare-value.is-highlight{color:#1768e5;font-weight:600}
+.cm-attach-section{padding:18px 22px;background:#fff}
+.cm-attach-list{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
+.cm-attach-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:#fafbfd;border:1px solid #e1e7ef;border-radius:5px;cursor:pointer;transition:all .15s}
+.cm-attach-item:hover{background:#eaf2ff;border-color:#1768e5}
+.cm-attach-item i.cm-attach-icon{color:#1768e5;font-size:18px}
+.cm-attach-item .cm-attach-name{color:#243955;font-size:12px;font-weight:500}
+.cm-attach-item .cm-attach-meta{color:#9aa5b3;font-size:10px;margin-top:2px}
+.cm-attach-add{display:flex;align-items:center;justify-content:center;gap:6px;padding:18px;background:#fafbfd;border:1px dashed #d8dde5;border-radius:5px;color:#7e8b9d;cursor:pointer;font-size:12px;transition:all .15s}
+.cm-attach-add:hover{background:#eaf2ff;border-color:#1768e5;color:#1768e5}
+.cm-attach-add i{font-size:16px}
+.cm-change-footer{position:sticky;bottom:0;padding:14px 22px;display:flex;align-items:center;justify-content:space-between;background:rgba(255,255,255,.96);backdrop-filter:blur(4px);border-top:1px solid #edf1f6;z-index:5}
+.cm-change-footer .cm-footer-info{color:#7e8b9d;font-size:12px}
+.cm-change-footer .cm-footer-info i{color:#1768e5;margin-right:3px}
+.cm-change-footer .cm-footer-actions{display:flex;gap:8px}
+.cm-list-container{padding:18px 22px;background:#fafbfd;border-bottom:1px dashed #edf1f6}
+.cm-list-container .cm-list-info{font-size:11px;color:#7e8b9d;line-height:1.8}
+.cm-list-container .cm-list-info i{color:#1768e5;margin-right:3px}
+
+/* 发起合同弹窗(与合同变更风格一致) */
+.cm-create-page{padding:0}
+.cm-create-header{background:#fff;padding:18px 22px;border-bottom:1px solid #e1e7ef}
+.cm-create-header h2{margin:0 0 6px;font-size:18px;color:#243955;font-weight:600}
+.cm-create-header h2 i{color:#1768e5;margin-right:6px}
+.cm-create-header p{margin:0;color:#7e8b9d;font-size:12px}
+.cm-create-section{padding:18px 22px;background:#fff;border-bottom:1px solid #edf1f6}
+.cm-create-section-title{font-size:14px;color:#243955;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:6px}
+.cm-create-section-title::before{content:"";display:inline-block;width:3px;height:14px;background:#1768e5;border-radius:1px}
+.cm-create-employee-card{background:#f8fbff;border:1px solid #dbe7ff;border-radius:6px;padding:12px 16px;margin-bottom:14px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
+.cm-create-employee-card .cm-employee-avatar{width:42px;height:42px;display:grid;place-items:center;border-radius:50%;background:linear-gradient(135deg,#1768e5,#4c8be8);color:#fff;font-size:18px;font-weight:600}
+.cm-create-employee-card .cm-employee-info{display:flex;flex-direction:column;gap:2px}
+.cm-create-employee-card .cm-employee-info b{color:#243955;font-size:14px}
+.cm-create-employee-card .cm-employee-info small{color:#7e8b9d;font-size:11px}
+.cm-create-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 24px}
+.cm-create-grid .el-form-item{margin-bottom:14px}
+.cm-create-grid .el-form-item__label{padding-bottom:4px;color:#4a586d;font-size:12px;line-height:18px}
+.cm-create-grid .el-select,.cm-create-grid .el-date-editor,.cm-create-grid .el-input,.cm-create-grid .el-input-number{width:100%}
+.cm-create-grid .el-input__inner,.cm-create-grid .el-select .el-input__inner{height:32px;line-height:32px;font-size:13px}
+.cm-create-grid .el-input-number .el-input__inner{height:32px}
+.cm-create-sign-method{display:flex;gap:8px}
+.cm-create-sign-method .cm-method-btn{flex:1;height:34px;border:1px solid #1768e5;border-radius:4px;background:#fff;color:#1768e5;font-size:13px;cursor:pointer;transition:all .15s}
+.cm-create-sign-method .cm-method-btn.is-active{background:#1768e5;color:#fff;box-shadow:0 2px 6px rgba(23,104,229,.3)}
+.cm-create-sign-method .cm-method-btn:hover:not(.is-active){background:#eaf2ff}
+.cm-create-tips{font-size:11px;color:#7e8b9d;line-height:1.8;padding:10px 12px;background:#fafbfd;border-radius:4px;margin-top:8px}
+.cm-create-tips p{margin:0}
+.cm-create-tips p+p{margin-top:3px}
+.cm-create-tips i{color:#1768e5;margin-right:3px}
+.cm-create-tips p .cm-reason-highlight{color:#1768e5;font-weight:500}
+.cm-create-upload-area{padding:18px 22px;background:#fff}
+.cm-create-upload{display:flex;align-items:center;justify-content:center;gap:8px;padding:18px;background:#fafbfd;border:1px dashed #d8dde5;border-radius:5px;color:#7e8b9d;cursor:pointer;font-size:12px;transition:all .15s}
+.cm-create-upload:hover{background:#eaf2ff;border-color:#1768e5;color:#1768e5}
+.cm-create-upload i{font-size:18px}
+.cm-create-uploaded{display:flex;align-items:center;gap:10px;padding:10px 14px;background:#e7f7f1;border:1px solid #b5e1c3;border-radius:5px;color:#168d69;font-size:12px;margin-top:8px}
+.cm-create-uploaded i{font-size:16px}
+.cm-create-uploaded .cm-uploaded-close{margin-left:auto;cursor:pointer;color:#7e8b9d;background:none;border:0}
+.cm-create-footer{position:sticky;bottom:0;padding:14px 22px;display:flex;align-items:center;justify-content:space-between;background:rgba(255,255,255,.96);backdrop-filter:blur(4px);border-top:1px solid #edf1f6;z-index:5}
+.cm-create-footer .cm-footer-info{color:#7e8b9d;font-size:12px}
+.cm-create-footer .cm-footer-info i{color:#1768e5;margin-right:3px}
+.cm-create-footer .cm-footer-actions{display:flex;gap:8px}

+ 94 - 0
src/styles/views/contractTemplate/index.scss

@@ -0,0 +1,94 @@
+
+.ct-page,.ct-page *{box-sizing:border-box}
+.ct-page{min-height:100%;padding:22px;background:#f3f6fb;color:#22334a;--primary:#1768e5}
+.ct-filter{padding:15px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:11px;background:#fff;border:1px solid #e0e7f1;border-radius:13px;margin-bottom:14px;box-shadow:0 7px 22px rgba(40,67,106,.05)}
+.ct-filter .ct-filter-cell{display:flex;align-items:center;gap:8px;min-width:0}
+.ct-filter .ct-filter-label{color:#4a586d;font-size:12px;white-space:nowrap;flex:none;min-width:64px;text-align:right}
+.ct-filter .ct-filter-cell .el-input,.ct-filter .ct-filter-cell .el-select{flex:1;min-width:0}
+.ct-filter .ct-filter-cell .el-input__inner,.ct-filter .ct-filter-cell .el-select .el-input__inner{height:32px;line-height:32px;font-size:12px;border-radius:1px}
+.ct-filter .ct-filter-actions{display:flex;align-items:center;gap:8px;justify-content:flex-end;align-self:end}
+.ct-page .ct-workspace{background:#fff;border:1px solid #dfe6ef;border-radius:13px;box-shadow:0 7px 22px rgba(40,67,106,.05);overflow:hidden}
+.ct-page .ct-workspace .ct-add-btn{border-bottom:1px solid #edf0f3}
+.ct-page .ct-workspace .ele-table-tool{padding:0}
+.ct-page .ct-workspace ::v-deep th.el-table__cell{background:#f5f8fc;color:#4a586d;font-weight:600;padding:10px 6px;border-bottom:1px solid #e1e7ef;text-align:center;white-space:nowrap;font-size:12px}
+.ct-page .ct-workspace ::v-deep td.el-table__cell{padding:8px 6px;border-bottom:1px solid #eef1f6;background:#fff;vertical-align:middle}
+.ct-page .ct-workspace ::v-deep .el-table__row:hover>td.el-table__cell{background:#f9fbfe!important}
+.ct-page .ct-empty-row{padding:32px 12px;color:#7e8b9d;text-align:center;font-size:12px}
+.ct-cell-input{width:100%;border:0;outline:0;padding:5px 6px;font-size:12px;color:#24344b;background:transparent;text-align:center}
+.ct-cell-input:focus{background:#eaf2ff}
+.ct-cell-input.ct-readonly{background:#f5f8fc;color:#6e7d91;cursor:not-allowed}
+.ct-cell-readonly{display:block;padding:5px 6px;background:#f5f8fc;color:#6e7d91;font-size:12px;text-align:center;border-radius:3px}
+.ct-cell-select{width:100%}
+.ct-cell-select .el-input__inner{height:26px;line-height:26px;border:0;background:transparent;padding:0 6px;font-size:12px;text-align:center}
+.ct-cell-select .el-input__inner:focus{border-color:#1768e5}
+.ct-cell-select .el-input__suffix{display:none}
+.ct-link{color:#1768e5;background:none;border:0;cursor:pointer;font-size:12px;padding:2px 4px}
+.ct-link:hover{text-decoration:underline}
+.ct-status-btn{background:none;border:0;cursor:pointer;font-size:12px;padding:2px 8px}
+.ct-status-btn.active{color:#168d69}
+.ct-status-btn.active:hover{color:#0d6e4d}
+.ct-status-btn.inactive{color:#a4aebd}
+.ct-status-btn.inactive:hover{color:#7e8b9d}
+.ct-action-link{color:#1768e5;background:none;border:0;cursor:pointer;font-size:12px;padding:0 4px}
+.ct-action-link.danger{color:#d55752}
+.ct-action-link:hover{text-decoration:underline}
+.ct-required::before{content:'*';color:#d55752;margin-right:2px}
+.ct-attachment-name{font-size:10px;color:#168d69;margin-top:2px}
+.ct-notes{margin-top:14px;padding:14px 18px;background:#fff;border:1px solid #dfe6ef;border-radius:4px;font-size:12px;color:#4a586d;line-height:1.9}
+.ct-notes p{margin:0}
+.ct-notes p+p{margin-top:4px}
+/* 简单新增弹窗样式(顶部按钮 + 筛选 + 列表) */
+.ct-quick-dialog{width:900px !important}
+.ct-quick-dialog ::v-deep .el-dialog__header{padding:16px 20px;border-bottom:1px solid #edf1f6;margin:0}
+.ct-quick-dialog ::v-deep .el-dialog__title{font-size:15px;font-weight:600}
+.ct-quick-dialog ::v-deep .el-dialog__body{padding:18px 20px}
+.ct-quick-dialog .ct-quick-tip{padding:10px 12px;background:#f8fbff;border:1px solid #dbe7ff;border-radius:4px;color:#4a586d;font-size:12px;line-height:1.6;margin-bottom:14px}
+.ct-quick-dialog .ct-quick-tip i{color:#1768e5;margin-right:3px}
+.ct-quick-dialog .ct-quick-actions{display:flex;align-items:center;gap:10px;margin-bottom:14px}
+.ct-quick-dialog .ct-quick-section{margin-bottom:14px}
+.ct-quick-dialog .ct-quick-section-title{font-size:12px;color:#1768e5;font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:6px}
+.ct-quick-dialog .ct-quick-section-title::before{content:'';width:3px;height:12px;background:#1768e5;border-radius:1px}
+.ct-quick-dialog .ct-quick-filter-row{display:flex;align-items:center;gap:8px;background:#fff;border:1px solid #dfe6ef;border-radius:6px;padding:6px 12px;flex-wrap:wrap}
+.ct-quick-dialog .ct-quick-filter-cell{display:flex;align-items:center;gap:6px;padding:0 12px;border-right:1px dashed #e1e7ef;min-width:120px}
+.ct-quick-dialog .ct-quick-filter-cell:last-child{border-right:0}
+.ct-quick-dialog .ct-quick-filter-cell.ct-quick-flex{flex:1;min-width:200px}
+.ct-quick-dialog .ct-quick-filter-label{color:#4a586d;font-size:12px;white-space:nowrap}
+.ct-quick-dialog .ct-quick-filter-row .el-select,.ct-quick-dialog .ct-quick-filter-row .el-input{width:100%}
+.ct-quick-dialog .ct-quick-filter-row .el-input__inner,.ct-quick-dialog .ct-quick-filter-row .el-select .el-input__inner{height:30px;line-height:30px;border:0;font-size:12px;padding:0;background:transparent}
+.ct-quick-dialog .ct-quick-filter-row .el-input__inner:focus,.ct-quick-dialog .ct-quick-filter-row .el-select .el-input__inner:focus{border-color:#1768e5}
+.ct-quick-dialog .ct-quick-table{width:100%;border-collapse:collapse;font-size:12px;background:#fff;border:1px solid #dfe6ef;border-radius:4px;overflow:hidden}
+.ct-quick-dialog .ct-quick-table th{background:#f5f8fc;color:#4a586d;font-weight:600;padding:8px 6px;border:1px solid #e1e7ef;text-align:center;white-space:nowrap}
+.ct-quick-dialog .ct-quick-table td{padding:6px;border:1px solid #e1e7ef;text-align:center;vertical-align:middle;background:#fff;color:#2a3a51}
+.ct-quick-dialog .ct-quick-table tr:hover td{background:#f9fbfe}
+.ct-quick-dialog .ct-status-pill{display:inline-flex;align-items:center;padding:2px 10px;border-radius:11px;font-size:11px}
+.ct-quick-dialog .ct-status-pill.active{color:#168d69;background:#e7f7f1}
+.ct-quick-dialog .ct-status-pill.inactive{color:#a4aebd;background:#eef0f4}
+.ct-quick-dialog .ct-status-pill.draft{color:#7e8b9d;background:#eef0f4}
+.ct-quick-dialog .ct-status-pill.pending{color:#c67d23;background:#fff3e2}
+.ct-quick-dialog .ct-quick-foot{padding-top:12px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid #edf1f6;margin-top:14px}
+/* 完整编辑弹窗样式 */
+.ct-dialog ::v-deep .el-dialog__header{padding:14px 20px;border-bottom:1px solid #edf1f6;margin:0}
+.ct-dialog ::v-deep .el-dialog__title{font-size:15px;font-weight:600}
+.ct-dialog ::v-deep .el-dialog__body{padding:18px 20px}
+.ct-dialog ::v-deep .el-dialog__footer{padding:12px 20px;border-top:1px solid #edf1f6}
+.ct-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 18px;margin-bottom:14px}
+.ct-form-grid .el-form-item{margin-bottom:14px}
+.ct-form-grid .el-form-item__label{padding-bottom:4px;color:#4a586d;font-size:12px;line-height:18px}
+.ct-form-grid .el-select,.ct-form-grid .el-date-editor,.ct-form-grid .el-input{width:100%}
+.ct-form-grid .el-input__inner,.ct-form-grid .el-select .el-input__inner{height:30px;line-height:30px;font-size:12px}
+.ct-section-title{font-size:12px;color:#1768e5;font-weight:600;margin:8px 0 10px;display:flex;align-items:center;gap:6px}
+.ct-section-title::before{content:'';width:3px;height:12px;background:#1768e5;border-radius:1px}
+.ct-editor-wrap{border:1px solid #dfe6ef;border-radius:4px;background:#fff;overflow:hidden}
+.ct-editor-toolbar{display:flex;align-items:center;gap:2px;padding:6px 8px;background:#fafbfd;border-bottom:1px solid #edf1f6;flex-wrap:wrap;min-height:38px}
+.ct-editor-toolbar .el-button--mini{padding:4px 8px;font-size:11px;line-height:1.2}
+.ct-editor-toolbar .ct-tb-sep{width:1px;height:18px;background:#e1e7ef;margin:0 4px}
+.ct-editor-toolbar .ct-tb-spacer{flex:1}
+.ct-editor-body{min-height:200px;max-height:280px;padding:14px 18px;overflow-y:auto;font-size:13px;line-height:1.85;color:#2a3a51;outline:0;white-space:pre-wrap;word-break:break-word;background:#fff}
+.ct-editor-body:focus{background:#fffdf8}
+.ct-editor-foot{padding:8px 12px;border-top:1px solid #edf1f6;background:#fafbfd;display:flex;align-items:center;justify-content:space-between;font-size:11px;color:#7e8b9d}
+.ct-editor-foot i{color:#1768e5;margin-right:4px}
+.ct-variable-popover .ct-var-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;width:380px;padding:4px}
+.ct-variable-popover .ct-var-item{padding:6px 8px;border:1px solid #e1e7ef;border-radius:4px;background:#fafbfd;cursor:pointer;font-size:11px;color:#24344b;text-align:center;transition:all .15s}
+.ct-variable-popover .ct-var-item:hover{background:#eaf2ff;border-color:#1768e5;color:#1768e5}
+@media(max-width:900px){.ct-filter{grid-template-columns:1fr 1fr}.ct-filter .ct-filter-actions{grid-column:span 2}}
+

+ 720 - 0
src/views/contractManagement/index.vue

@@ -0,0 +1,720 @@
+<template>
+  <main class="cm-page" v-loading="loading">
+    <section class="cm-workspace">
+      <div class="cm-filter-bar">
+        <div class="cm-filter-cell">
+          <span class="cm-filter-label">合同编号:</span>
+          <el-input v-model.trim="filters.contractNo" clearable placeholder="请输入合同编号" size="small"
+            @keyup.enter.native="applyFilters" />
+        </div>
+        <div class="cm-filter-cell">
+          <span class="cm-filter-label">合同类型:</span>
+          <el-select v-model="filters.contractTypeShort" clearable placeholder="全职" size="small">
+            <el-option v-for="t in contractTypes" :key="t" :label="t" :value="t" />
+          </el-select>
+        </div>
+        <div class="cm-filter-cell">
+          <span class="cm-filter-label">合同状态:</span>
+          <el-select v-model="filters.status" clearable placeholder="启用" size="small">
+            <el-option v-for="s in contractStatus" :key="s.value" :label="s.label" :value="s.value" />
+          </el-select>
+        </div>
+        <div class="cm-filter-cell">
+          <span class="cm-filter-label">所属部门:</span>
+          <el-select v-model="filters.department" clearable placeholder="全部" size="small">
+            <el-option v-for="d in departments" :key="d" :label="d" :value="d" />
+          </el-select>
+        </div>
+        <div class="cm-filter-cell cm-filter-actions">
+          <el-button type="primary" size="small" @click="applyFilters">查询</el-button>
+          <el-button size="small" @click="resetFilters">重置</el-button>
+        </div>
+      </div>
+
+      <ele-pro-table ref="table" row-key="id" :columns="columns" :datasource="filteredRecords"
+        :need-page="true" :page-size="10" :page-sizes="[10, 20, 50, 100]"
+        :toolkit="['size', 'columns', 'fullscreen']" stripe class="cm-pro-table">
+        <template slot="toolbar">
+          <el-button type="primary" icon="el-icon-plus" size="small" @click="openCreate">发起合同</el-button>
+        <el-button size="small" icon="el-icon-download" @click="exportList">导出</el-button>
+        </template>
+        <template v-slot:positionDuty="{ row }">
+          <span class="cm-duty">{{ row.positionDuty }}</span>
+        </template>
+        <template v-slot:signDoc="{ row }">
+          <button class="cm-link" @click="viewFile(row.signDoc)">查看</button>
+        </template>
+        <template v-slot:duration="{ row }">{{ row.startDate }} ~ {{ row.endDate || '长期' }}</template>
+        <template v-slot:status="{ row }">
+          <span class="cm-status" :class="statusClass(row.status)">{{ row.status }}</span>
+        </template>
+        <template v-slot:attachment="{ row }">
+          <button class="cm-link" @click="viewFile(row.attachment)">查看</button>
+        </template>
+        <template v-slot:action="{ row }">
+          <div class="cm-action">
+            <button class="cm-link" @click="viewDetail(row)">详情查看</button>
+            <button class="cm-link" @click="openChange(row)">变更</button>
+            <button class="cm-link" @click="handleRenew(row)">续签</button>
+            <button class="cm-link" @click="handleTerminate(row)">解除</button>
+            <button class="cm-link" @click="handleSign(row)">签订</button>
+            <button class="cm-link" @click="handleArchive(row)">已归档</button>
+          </div>
+        </template>
+        <template slot="empty">
+          <div class="cm-empty">
+            <i class="el-icon-document"></i>
+            <strong>暂无合同记录</strong>
+            <p>调整筛选条件或点击「发起合同」新增</p>
+          </div>
+        </template>
+      </ele-pro-table>
+    </section>
+
+    <!-- 详情查看弹窗 -->
+    <ele-modal
+      :visible.sync="detailVisible"
+      width="900px"
+      title="合同详情"
+      append-to-body
+      custom-class="cm-detail-dialog"
+     :movable="true">
+      <div v-if="detailRow" class="cm-create-page">
+        <div class="cm-create-header">
+          <h2><i class="el-icon-document"></i>合同详情</h2>
+          <p>查看合同的完整信息,包括员工信息、合同条款及附件</p>
+        </div>
+
+        <div class="cm-create-employee-card" style="margin:14px 22px 0;">
+          <span class="cm-employee-avatar">{{ detailRow.employeeName ? detailRow.employeeName.slice(0, 1) : '?' }}</span>
+          <div class="cm-employee-info">
+            <b>员工姓名:{{ detailRow.employeeName }}<span style="margin-left:24px;">员工工号:{{ detailRow.employeeNo }}</span></b>
+            <small>所属法人:{{ detailRow.legalEntity }} · 合同编号:<span style="color:#1768e5;">{{ detailRow.contractNo }}</span> · 合同类型:{{ detailRow.contractType }}</small>
+          </div>
+          <div style="margin-left:auto;text-align:right;">
+            <small style="color:#7e8b9d;font-size:10px;display:block;">合同状态</small>
+            <span class="cm-status" :class="statusClass(detailRow.status)" style="margin-top:4px;display:inline-block;">{{ detailRow.status }}</span>
+          </div>
+        </div>
+
+        <div class="cm-create-section">
+          <div class="cm-create-section-title">合同基本信息</div>
+          <div class="cm-create-grid">
+            <div class="cm-compare-item"><span class="cm-compare-label">合同编号:</span><span class="cm-compare-value">{{ detailRow.contractNo }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">合同类型:</span><span class="cm-compare-value">{{ detailRow.contractType }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">所属法人:</span><span class="cm-compare-value">{{ detailRow.legalEntity }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">所属部门:</span><span class="cm-compare-value">{{ detailRow.department }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">岗位名称:</span><span class="cm-compare-value">{{ detailRow.positionName }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">职级:</span><span class="cm-compare-value">{{ detailRow.level || '-' }}</span></div>
+          </div>
+        </div>
+
+        <div class="cm-create-section">
+          <div class="cm-create-section-title">岗位职责与工作信息</div>
+          <div style="padding:4px 0 8px;color:#243955;font-size:13px;line-height:1.7;">{{ detailRow.positionDuty || '暂无岗位职责说明' }}</div>
+        </div>
+
+        <div class="cm-create-section">
+          <div class="cm-create-section-title">合同期限与薪资</div>
+          <div class="cm-create-grid">
+            <div class="cm-compare-item"><span class="cm-compare-label">合同起始:</span><span class="cm-compare-value">{{ detailRow.startDate || '-' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">合同终止:</span><span class="cm-compare-value">{{ detailRow.endDate || '长期' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">月薪资:</span><span class="cm-compare-value is-highlight">{{ detailRow.salary ? ('¥ ' + Number(detailRow.salary).toLocaleString() + ' 元/月') : '待定' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">试用期至:</span><span class="cm-compare-value">{{ detailRow.probationEnd || '无试用期' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">工作地点:</span><span class="cm-compare-value">{{ detailRow.workLocation || '-' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">直接主管:</span><span class="cm-compare-value">{{ detailRow.manager || '-' }}</span></div>
+          </div>
+        </div>
+
+        <div class="cm-create-section">
+          <div class="cm-create-section-title">签署信息</div>
+          <div class="cm-create-grid">
+            <div class="cm-compare-item"><span class="cm-compare-label">签署方式:</span><span class="cm-compare-value">{{ detailRow.signMethod || '-' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">签署文件:</span><span class="cm-compare-value">
+              <button class="cm-link" @click="viewFile(detailRow.signDoc)">{{ detailRow.signDoc || '查看' }}</button>
+            </span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">合同附件:</span><span class="cm-compare-value">
+              <button class="cm-link" @click="viewFile(detailRow.attachment)">{{ detailRow.attachment || '查看' }}</button>
+            </span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">签订日期:</span><span class="cm-compare-value">{{ detailRow.signDate || detailRow.createdAt || '-' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">审核人:</span><span class="cm-compare-value">{{ detailRow.reviewer || '未指定' }}</span></div>
+            <div class="cm-compare-item"><span class="cm-compare-label">创建时间:</span><span class="cm-compare-value">{{ detailRow.createdAt || '-' }}</span></div>
+          </div>
+          <div v-if="detailRow.remark" style="margin-top:10px;padding:10px 12px;background:#fff7e6;border-left:3px solid #e6a23c;border-radius:3px;color:#7a5c00;font-size:12px;line-height:1.6;">
+            <strong>备注:</strong>{{ detailRow.remark }}
+          </div>
+        </div>
+
+        <div class="cm-create-footer">
+          <div class="cm-footer-info">
+            <i class="el-icon-info"></i> 合同编号 {{ detailRow.contractNo }} · 状态 {{ detailRow.status }}
+          </div>
+          <div class="cm-footer-actions">
+            <el-button @click="detailVisible = false">关闭</el-button>
+          </div>
+        </div>
+      </div>
+    </ele-modal>
+
+    <!-- 发起合同弹窗 -->
+    <ele-modal
+      :visible.sync="createVisible"
+      width="920px"
+      title="发起合同"
+      append-to-body
+      :close-on-click-modal="false"
+      :before-close="closeCreate"
+     :movable="true">
+      <div class="cm-create-page">
+        <div class="cm-create-header">
+          <h2><i class="el-icon-document-add"></i>发起合同</h2>
+          <p>为员工创建新的劳动合同,填写完成后可保存为草稿或直接提交签署流程</p>
+        </div>
+
+        <div v-if="selectedEmployee" class="cm-create-employee-card" style="margin:14px 22px 0;">
+          <span class="cm-employee-avatar">{{ selectedEmployee.name.slice(0, 1) }}</span>
+          <div class="cm-employee-info">
+            <b>员工姓名:{{ selectedEmployee.name }}<span style="margin-left:24px;">员工工号:{{ selectedEmployee.employeeNo }}</span><span style="margin-left:24px;">所属法人:{{ selectedEmployee.legalEntity }}</span></b>
+            <small>部门:{{ selectedEmployee.department }} · 岗位:{{ selectedEmployee.position }} · 职级:{{ selectedEmployee.level }} · 建议薪资:¥ {{ Number(selectedEmployee.salary).toLocaleString() }}/月</small>
+          </div>
+        </div>
+
+        <div class="cm-create-section">
+          <div class="cm-create-section-title">合同基础信息</div>
+          <el-form ref="createFormRef" :model="createForm" :rules="createRules" label-position="top">
+            <div class="cm-create-grid">
+              <el-form-item label="合同编号">
+                <el-input v-model="createForm.contractNo" placeholder="自动生成,可手动修改" />
+              </el-form-item>
+              <el-form-item label="合同类型" prop="contractType">
+                <el-select v-model="createForm.contractType" placeholder="请选择合同类型">
+                  <el-option v-for="t in contractTypes" :key="t" :label="t" :value="t" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="调整薪资" prop="salary">
+                <el-input v-model="createForm.salary" placeholder="如:8000">
+                  <template slot="append">元/月</template>
+                </el-input>
+              </el-form-item>
+              <el-form-item label="合同起始日期" prop="startDate">
+                <el-date-picker v-model="createForm.startDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择" style="width:100%;" />
+              </el-form-item>
+              <el-form-item label="签署方式" prop="signMethod">
+                <div class="cm-create-sign-method">
+                  <button type="button" class="cm-method-btn" :class="{ 'is-active': createForm.signMethod === '线上' }" @click="createForm.signMethod = '线上'">线上签订</button>
+                  <button type="button" class="cm-method-btn" :class="{ 'is-active': createForm.signMethod === '线下' }" @click="createForm.signMethod = '线下'">线下签订</button>
+                </div>
+              </el-form-item>
+              <el-form-item label="合同模版" prop="templateId">
+                <el-select v-model="createForm.templateId" placeholder="请选择合同模版">
+                  <el-option v-for="t in contractTemplateOptions" :key="t.id" :label="t.name" :value="t.id" />
+                </el-select>
+              </el-form-item>
+            </div>
+          </el-form>
+          <div class="cm-create-tips">
+            <p><i class="el-icon-info"></i> 合同编号系统自动生成 HT-XXXX 格式,可手动修改</p>
+            <p><i class="el-icon-info"></i> 选择员工后,所属法人、部门、岗位等信息将自动从员工档案获取</p>
+            <p><i class="el-icon-info"></i> 签署方式选择「线上签订」时,系统将自动生成电子合同并发送给员工</p>
+          </div>
+        </div>
+
+        <div class="cm-create-section" style="background:#fafbfd;">
+          <div class="cm-create-section-title">原合同信息 / 发起后信息</div>
+          <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
+            <div class="cm-compare-card">
+              <div class="cm-compare-card-head">
+                <span class="cm-compare-card-title">员工当前信息</span>
+              </div>
+              <div class="cm-compare-list">
+                <div class="cm-compare-item"><span class="cm-compare-label">岗位:</span><span class="cm-compare-value">{{ selectedEmployee ? selectedEmployee.position : '-' }}</span></div>
+                <div class="cm-compare-item"><span class="cm-compare-label">职级:</span><span class="cm-compare-value">{{ selectedEmployee ? selectedEmployee.level : '-' }}</span></div>
+                <div class="cm-compare-item"><span class="cm-compare-label">部门:</span><span class="cm-compare-value">{{ selectedEmployee ? selectedEmployee.department : '-' }}</span></div>
+                <div class="cm-compare-item"><span class="cm-compare-label">直接主管:</span><span class="cm-compare-value">{{ selectedEmployee ? selectedEmployee.manager : '-' }}</span></div>
+                <div class="cm-compare-item" style="grid-column: span 2;"><span class="cm-compare-label">建议薪资:</span><span class="cm-compare-value">{{ selectedEmployee ? ('¥ ' + Number(selectedEmployee.salary).toLocaleString() + ' 元/月') : '-' }}</span></div>
+              </div>
+            </div>
+            <div class="cm-compare-card">
+              <div class="cm-compare-head-title"><span class="cm-compare-card-title">合同拟定信息</span></div>
+              <div class="cm-compare-list">
+                <div class="cm-compare-item"><span class="cm-compare-label">合同类型:</span><span class="cm-compare-value is-highlight">{{ createForm.contractType || '请选择合同类型' }}</span></div>
+                <div class="cm-compare-item"><span class="cm-compare-label">合同编号:</span><span class="cm-compare-value">{{ createForm.contractNo || '自动生成' }}</span></div>
+                <div class="cm-compare-item"><span class="cm-compare-label">合同起始:</span><span class="cm-compare-value">{{ createForm.startDate || '请选择起始日期' }}</span></div>
+                <div class="cm-compare-item"><span class="cm-compare-label">合同终止:</span><span class="cm-compare-value">{{ createForm.endDate || '长期' }}</span></div>
+                <div class="cm-compare-item" style="grid-column: span 2;"><span class="cm-compare-label">拟定薪资:</span><span class="cm-compare-value is-highlight">{{ createForm.salary ? ('¥ ' + Number(String(createForm.salary).replace(/\D/g, '')).toLocaleString() + ' 元/月') : '请填写薪资' }}</span></div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="cm-create-section">
+          <div class="cm-create-section-title">合同附件</div>
+          <div v-if="!createForm.attachmentName" class="cm-create-upload" @click="triggerCreateUpload">
+            <i class="el-icon-upload"></i>
+            <span>点击上传合同文件(支持 PDF / Word / 图片,单个文件不超过 10MB)</span>
+          </div>
+          <div v-else class="cm-create-uploaded">
+            <i class="el-icon-document"></i>
+            <span>已上传:{{ createForm.attachmentName }}</span>
+            <button class="cm-uploaded-close" @click="createForm.attachmentName = ''; createForm.attachment = ''"><i class="el-icon-close"></i></button>
+          </div>
+          <input ref="createFileInput" type="file" accept=".pdf,.doc,.docx,.jpg,.png" style="display:none" @change="handleCreateUpload" />
+        </div>
+
+        <div class="cm-create-footer">
+          <div class="cm-footer-info">
+            <i class="el-icon-info"></i> 保存草稿后可继续编辑,提交后合同将进入签署流程
+          </div>
+          <div class="cm-footer-actions">
+            <el-button @click="closeCreate">取消</el-button>
+            <el-button @click="saveCreateDraft">保存为草稿</el-button>
+            <el-button type="primary" @click="submitCreate">提交签署</el-button>
+          </div>
+        </div>
+      </div>
+    </ele-modal>
+
+    <el-drawer
+      :visible.sync="changeDrawer"
+      direction="rtl"
+      size="900px"
+      :with-header="false"
+      append-to-body
+      custom-class="cm-change-drawer"
+    >
+      <div class="cm-change-page" v-if="changeForm.employeeName">
+        <div class="cm-change-header">
+          <h2><i class="el-icon-document"></i>合同变更界面</h2>
+          <div class="cm-change-employee">
+            <span>员工姓名:<b>{{ changeForm.employeeName }}</b></span>
+            <span>员工工号:<b>{{ changeForm.employeeNo }}</b></span>
+            <span>所属法人:<b>{{ changeForm.legalEntity }}</b></span>
+            <span>原合同编号:<b>{{ changeForm.contractNo }}</b></span>
+            <span style="color:#1768e5;">原合同类型:<b>{{ changeForm.contractType }}</b></span>
+          </div>
+        </div>
+
+        <div class="cm-change-section">
+          <div class="cm-change-section-title">变更基础信息</div>
+          <el-form ref="changeFormRef" :model="changeForm" :rules="changeRules" label-position="top">
+            <div class="cm-change-grid">
+              <el-form-item label="变更类型" prop="changeType">
+                <el-select v-model="changeForm.changeType" placeholder="岗位职级变更">
+                  <el-option v-for="t in changeTypes" :key="t.value" :label="t.label" :value="t.value" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="调整薪资" prop="newSalary">
+                <el-input v-model.trim="changeForm.newSalary" placeholder="22000元/月">
+                  <template slot="append">元/月</template>
+                </el-input>
+              </el-form-item>
+              <el-form-item label="变更生效日期" prop="effectiveDate">
+                <el-date-picker v-model="changeForm.effectiveDate" type="date" value-format="yyyy-MM-dd" placeholder="2026-09-01" style="width:100%;" />
+              </el-form-item>
+              <el-form-item label="签署方式" prop="signMethod">
+                <div class="cm-sign-method">
+                  <button type="button" class="cm-method-btn" :class="{ 'is-active': changeForm.signMethod === '线上' }" @click="changeForm.signMethod = '线上'">线上签订</button>
+                  <button type="button" class="cm-method-btn" :class="{ 'is-active': changeForm.signMethod === '线下' }" @click="changeForm.signMethod = '线下'">线下签订</button>
+                </div>
+              </el-form-item>
+            </div>
+            <div class="cm-change-notes">
+              <p><i class="el-icon-warning-outline"></i> 变更报告等节点 9 访问之后 全部至全部将签合同</p>
+              <p><i class="el-icon-warning-outline"></i> 变更获批后 协助解除后续工作 合同将自动同步 更新至合同管理系统</p>
+            </div>
+            <div class="cm-change-grid">
+              <el-form-item label="合同模版" prop="templateId">
+                <el-select v-model="changeForm.templateId" placeholder="请选择合同模版">
+                  <el-option v-for="t in contractTemplates" :key="t.id" :label="t.name" :value="t.id" />
+                </el-select>
+              </el-form-item>
+            </div>
+            <el-form-item label="变更原因" prop="changeReason">
+              <el-input v-model.trim="changeForm.changeReason" type="textarea" :rows="2" placeholder="如:因组织架构调整,岗位调整为技术主管" />
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <div class="cm-compare-grid">
+          <div class="cm-compare-card">
+            <div class="cm-compare-card-head">
+              <span class="cm-compare-card-title">原合同信息</span>
+              <button class="cm-compare-card-action"><i class="el-icon-close"></i></button>
+            </div>
+            <div class="cm-compare-list">
+              <div class="cm-compare-item"><span class="cm-compare-label">岗位:</span><span class="cm-compare-value">{{ changeForm.original.position }}</span></div>
+              <div class="cm-compare-item"><span class="cm-compare-label">职级:</span><span class="cm-compare-value">{{ changeForm.original.level }}</span></div>
+              <div class="cm-compare-item"><span class="cm-compare-label">部门:</span><span class="cm-compare-value">{{ changeForm.original.department }}</span></div>
+              <div class="cm-compare-item"><span class="cm-compare-label">合同到期日:</span><span class="cm-compare-value">{{ changeForm.original.endDate || '长期' }}</span></div>
+              <div class="cm-compare-item" style="grid-column: span 2;"><span class="cm-compare-label">薪资标准:</span><span class="cm-compare-value">{{ changeForm.original.salary }}元/月</span></div>
+            </div>
+          </div>
+          <div class="cm-compare-card">
+            <div class="cm-compare-card-head">
+              <span class="cm-compare-card-title">变更后信息</span>
+              <button class="cm-compare-card-action"><i class="el-icon-close"></i></button>
+            </div>
+            <div class="cm-compare-list">
+              <div class="cm-compare-item"><span class="cm-compare-label">岗位:</span><span class="cm-compare-value is-highlight">{{ changeForm.updated.position }}</span></div>
+              <div class="cm-compare-item"><span class="cm-compare-label">职级:</span><span class="cm-compare-value is-highlight">{{ changeForm.updated.level }}</span></div>
+              <div class="cm-compare-item"><span class="cm-compare-label">部门:</span><span class="cm-compare-value">{{ changeForm.updated.department }}</span></div>
+              <div class="cm-compare-item"><span class="cm-compare-label">合同到期日:</span><span class="cm-compare-value">{{ changeForm.updated.endDate || '长期' }}</span></div>
+              <div class="cm-compare-item" style="grid-column: span 2;"><span class="cm-compare-label">薪资标准:</span><span class="cm-compare-value is-highlight">{{ changeForm.updated.salary }}元/月</span></div>
+            </div>
+          </div>
+        </div>
+
+        <div class="cm-attach-section">
+          <div class="cm-change-section-title">变更协议附件</div>
+          <div class="cm-attach-list">
+            <div v-for="(file, idx) in changeForm.attachments" :key="idx" class="cm-attach-item" @click="openAttach(file)">
+              <i class="el-icon-document cm-attach-icon"></i>
+              <div>
+                <div class="cm-attach-name">{{ file.name }}</div>
+                <div class="cm-attach-meta">{{ file.size }} · {{ file.uploadedAt }}</div>
+              </div>
+            </div>
+            <div class="cm-attach-add" @click="triggerAttachUpload">
+              <i class="el-icon-upload"></i>
+              <span>上传附件</span>
+            </div>
+            <input ref="attachInput" type="file" accept=".pdf,.doc,.docx" style="display:none" @change="handleAttachUpload" />
+          </div>
+        </div>
+
+        <div class="cm-change-footer">
+          <div class="cm-footer-info">
+            <i class="el-icon-info"></i> 变更后合同将自动生成新编号,原合同进入「已变更」状态
+          </div>
+          <div class="cm-footer-actions">
+            <el-button @click="closeChangeDrawer">取消</el-button>
+            <el-button type="primary" @click="submitChange">保存并提交审批</el-button>
+          </div>
+        </div>
+      </div>
+    </el-drawer>
+  </main>
+</template>
+
+<script>
+import {
+  contractSeeds,
+  LEGAL_ENTITIES,
+  DEPARTMENTS,
+  CONTRACT_TYPES,
+  CONTRACT_STATUS,
+  SIGN_METHODS,
+  CHANGE_TYPES,
+  CONTRACT_TEMPLATES,
+  POSITIONS,
+  LEVELS,
+  employeeOptions,
+  contractTemplateOptions,
+  contractStatusOptions,
+  clone as deepClone,
+} from './mock';
+
+const STORE_KEY = 'oa-contracts-v5';
+
+function blankCreateForm() {
+  return {
+    employeeNo: '',
+    contractNo: '',
+    contractType: '',
+    templateId: null,
+    legalEntity: '',
+    department: '',
+    positionName: '',
+    startDate: '',
+    endDate: '',
+    probationEnd: '',
+    salary: 0,
+    manager: '',
+    signMethod: '线上',
+    status: '待签署',
+    attachment: '',
+    attachmentName: '',
+  };
+}
+
+function blankChangeForm() {
+  return {
+    employeeName: '',
+    employeeNo: '',
+    legalEntity: '',
+    contractNo: '',
+    contractType: '',
+    changeType: 'POSITION_LEVEL',
+    newSalary: '',
+    effectiveDate: '',
+    templateId: null,
+    signMethod: '线上',
+    changeReason: '',
+    original: { position: '', level: '', department: '', endDate: '', salary: '' },
+    updated: { position: '', level: '', department: '', endDate: '', salary: '' },
+    attachments: [
+      { name: '岗位调整审批单.pdf', size: '256 KB', uploadedAt: '2026-08-25 10:30' },
+      { name: '薪酬确认单.pdf', size: '128 KB', uploadedAt: '2026-08-25 10:32' },
+    ],
+  };
+}
+
+export default {
+  name: 'ContractManagement',
+  data() {
+    return {
+      loading: false,
+      rows: [],
+      filters: { contractNo: '', contractTypeShort: '', status: '', department: '' },
+      legalEntities: LEGAL_ENTITIES,
+      departments: DEPARTMENTS,
+      contractTypes: CONTRACT_TYPES,
+      contractStatus: CONTRACT_STATUS,
+      signMethods: SIGN_METHODS,
+      changeTypes: CHANGE_TYPES,
+      contractTemplates: CONTRACT_TEMPLATES,
+      positions: POSITIONS,
+      levels: LEVELS,
+      changeDrawer: false,
+      changeForm: blankChangeForm(),
+      employeeOptions,
+      contractTemplateOptions,
+      contractStatusOptions,
+      createVisible: false,
+      createForm: blankCreateForm(),
+      createRules: {
+        contractType: [{ required: true, message: '请选择合同类型', trigger: 'change' }],
+        salary: [{ required: true, message: '请填写调整薪资', trigger: 'blur' }],
+        startDate: [{ required: true, message: '请选择合同起始日期', trigger: 'change' }],
+        signMethod: [{ required: true, message: '请选择签署方式', trigger: 'change' }],
+        templateId: [{ required: true, message: '请选择合同模版', trigger: 'change' }],
+      },
+      changeRules: {
+        changeType: [{ required: true, message: '请选择变更类型', trigger: 'change' }],
+        newSalary: [{ required: true, message: '请填写调整后薪资', trigger: 'blur' }],
+        effectiveDate: [{ required: true, message: '请选择生效日期', trigger: 'change' }],
+        templateId: [{ required: true, message: '请选择合同模版', trigger: 'change' }],
+        signMethod: [{ required: true, message: '请选择签署方式', trigger: 'change' }],
+        changeReason: [{ required: true, message: '请填写变更原因', trigger: 'blur' }],
+      },
+      detailVisible: false,
+      detailRow: null,
+      columns: [
+        { type: 'index', label: '序号', width: 50, align: 'center', headerAlign: 'center' },
+        { prop: 'contractNo', label: '合同编号', minWidth: 130, showOverflowTooltip: true },
+        { prop: 'employeeName', label: '员工姓名', minWidth: 90 },
+        { prop: 'legalEntity', label: '所属法人', minWidth: 160, showOverflowTooltip: true },
+        { prop: 'contractType', label: '合同类型', minWidth: 130, showOverflowTooltip: true },
+        { prop: 'department', label: '所属部门', minWidth: 110 },
+        { prop: 'positionName', label: '岗位名称', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'positionDuty', label: '岗位职责', minWidth: 180, slot: 'positionDuty' },
+        { prop: 'signDoc', label: '签署文件', minWidth: 110, slot: 'signDoc', align: 'center' },
+        { prop: 'signMethod', label: '签署方式', minWidth: 80, align: 'center' },
+        { prop: 'duration', label: '合同期限', minWidth: 200, slot: 'duration' },
+        { prop: 'status', label: '合同状态', minWidth: 90, slot: 'status', align: 'center' },
+        { prop: 'attachment', label: '附件', minWidth: 110, slot: 'attachment', align: 'center' },
+        { prop: 'action', label: '操作', width: 320, slot: 'action' },
+      ],
+    };
+  },
+  computed: {
+    selectedEmployee() {
+      return this.employeeOptions.find((e) => e.employeeNo === this.createForm.employeeNo) || null;
+    },
+    // 仅做过滤(不再切片分页) — 分页交给 ele-pro-table
+    filteredRecords() {
+      const { contractNo, contractTypeShort, status, department } = this.filters;
+      const key = String(contractNo || '').trim().toLowerCase();
+      return this.rows.filter((row) => {
+        if (key && !String(row.contractNo || '').toLowerCase().includes(key)) return false;
+        if (contractTypeShort && row.contractTypeShort !== contractTypeShort) return false;
+        if (status && row.status !== status) return false;
+        if (department && row.department !== department) return false;
+        return true;
+      });
+    },
+  },
+  created() {
+    this.load();
+  },
+  methods: {
+    nowStr() {
+      const d = new Date();
+      const pad = (n) => String(n).padStart(2, '0');
+      return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
+    },
+    statusClass(s) {
+      const map = {
+        '待签署': 'pending', '在签': 'signing', '试用期': 'trial',
+        '生效中': 'active', '即将到期': 'expiring',
+        '已到期': 'expired', '已终止': 'terminated', '已变更': 'changed', '已归档': 'archived'
+      };
+      return map[s] || 'pending';
+    },
+    load() {
+      try {
+        const cached = localStorage.getItem(STORE_KEY);
+        this.rows = cached ? JSON.parse(cached) : deepClone(contractSeeds);
+        if (!cached) this.persist();
+      } catch (e) {
+        this.rows = deepClone(contractSeeds);
+      }
+    },
+    persist() { try { localStorage.setItem(STORE_KEY, JSON.stringify(this.rows)); } catch (e) {} },
+    applyFilters() { this.$refs.table && this.$refs.table.reload(); },
+    resetFilters() { this.filters = { contractNo: '', contractTypeShort: '', status: '', department: '' }; this.applyFilters(); },
+    reload() { this.load(); this.$message.success('已刷新'); },
+    viewFile(name) { if (name) this.$message.info('查看文件:' + name); else this.$message.warning('暂无文件'); },
+    viewDetail(row) {
+      this.detailRow = Object.assign({}, row);
+      this.detailVisible = true;
+    },
+    exportList() { this.$message.info('导出功能(原型暂未提供)'); },
+    openCreate() {
+      this.createForm = blankCreateForm();
+      this.createForm.contractNo = this.nextContractNo();
+      this.createVisible = true;
+    },
+    closeCreate(done) {
+      this.createVisible = false;
+      this.createForm = blankCreateForm();
+      if (typeof done === 'function') done();
+    },
+    nextContractNo() {
+      const max = this.rows
+        .map((r) => Number(String(r.contractNo || '').replace(/\D/g, '')) || 0)
+        .reduce((a, b) => Math.max(a, b), 100);
+      return 'HT' + String(max + 1).padStart(4, '0');
+    },
+    onEmployeeChange() {
+      const emp = this.selectedEmployee;
+      if (emp) {
+        this.createForm.legalEntity = emp.legalEntity;
+        this.createForm.department = emp.department;
+        this.createForm.positionName = emp.position;
+        this.createForm.manager = emp.manager;
+        this.createForm.salary = emp.salary;
+      }
+    },
+    triggerCreateUpload() {
+      const inputs = this.$refs.createFileInput;
+      const arr = Array.isArray(inputs) ? inputs : [inputs];
+      const target = arr.find((i) => i);
+      if (target) target.click();
+      else {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.accept = '.pdf,.doc,.docx,.jpg,.png';
+        input.onchange = (e) => this.handleCreateUpload(e);
+        input.click();
+      }
+    },
+    handleCreateUpload(event) {
+      const file = event.target.files && event.target.files[0];
+      if (!file) return;
+      if (file.size > 10 * 1024 * 1024) {
+        this.$message.error('文件大小不能超过 10MB');
+        return;
+      }
+      this.createForm.attachmentName = file.name;
+      this.createForm.attachment = file.name;
+      this.$message.success('已上传:' + file.name);
+      if (event.target) event.target.value = '';
+    },
+    saveCreateDraft() {
+      if (!this.createForm.employeeNo) return this.$message.warning('请选择员工');
+      if (!this.createForm.contractType) return this.$message.warning('请选择合同类型');
+      this.commitCreate('草稿');
+    },
+    submitCreate() {
+      const valid = this.$refs.createFormRef ? this.$refs.createFormRef.validate() : Promise.resolve(false);
+      valid.then((ok) => {
+        if (!ok) return;
+        if (!this.createForm.startDate) return this.$message.warning('请选择合同起始日期');
+        this.commitCreate('待签署');
+      }).catch(() => {});
+    },
+    commitCreate(status) {
+      const data = deepClone(this.createForm);
+      data.status = status;
+      data.id = Date.now();
+      data.createdAt = this.nowStr();
+      data.employeeName = this.selectedEmployee ? this.selectedEmployee.name : '';
+      data.contractTypeShort = data.contractType.includes('实习') ? '实习' : (data.contractType.includes('兼职') ? '兼职' : '全职');
+      data.positionDuty = '';
+      data.signDoc = '';
+      data.attachment = data.attachmentName;
+      data.level = this.selectedEmployee ? this.selectedEmployee.level : '';
+      data.reviewer = '';
+      data.remark = '';
+      this.rows.unshift(data);
+      this.persist();
+      this.applyFilters();
+      this.closeCreate();
+      this.$message.success(status === '草稿' ? '已保存为草稿' : '合同已发起,进入签署流程');
+    },
+    openChange(row) {
+      this.changeForm = blankChangeForm();
+      this.changeForm.employeeName = row.employeeName;
+      this.changeForm.employeeNo = row.employeeNo;
+      this.changeForm.legalEntity = row.legalEntity;
+      this.changeForm.contractNo = row.contractNo;
+      this.changeForm.contractType = row.contractType;
+      this.changeForm.original = { position: row.positionName, level: row.level, department: row.department, endDate: row.endDate, salary: row.salary };
+      this.changeForm.updated = { position: '技术主管', level: 'P6', department: row.department, endDate: row.endDate, salary: 22000 };
+      this.changeForm.newSalary = '22000';
+      this.changeForm.effectiveDate = '2026-09-01';
+      this.changeForm.changeReason = '因组织架构调整,岗位调整为技术主管';
+      this.changeDrawer = true;
+    },
+    closeChangeDrawer() { this.changeDrawer = false; this.changeForm = blankChangeForm(); },
+    async submitChange() {
+      const valid = await this.$refs.changeFormRef.validate().catch(() => false);
+      if (!valid) return;
+      const idx = this.rows.findIndex((r) => r.id === this.changeForm.id);
+      if (idx > -1) {
+        this.rows[idx].positionName = this.changeForm.updated.position;
+        this.rows[idx].level = this.changeForm.updated.level;
+        this.rows[idx].salary = Number(String(this.changeForm.newSalary).replace(/\D/g, '')) || this.rows[idx].salary;
+        this.rows[idx].status = '已变更';
+        this.persist();
+        this.applyFilters();
+      }
+      this.$message.success('变更已提交审批,审核通过后自动生效');
+      this.closeChangeDrawer();
+    },
+    handleRenew(row) { this.$prompt('请输入续签合同期限(年)', '续签合同', { inputValue: 3 }).then(({ value }) => { if (!value) return; const years = Number(value) || 3; const end = row.endDate ? new Date(row.endDate) : new Date(); if (!row.endDate) { end.setFullYear(end.getFullYear() + years); } else { end.setFullYear(end.getFullYear() + years); } const endStr = end.getFullYear() + '-' + String(end.getMonth() + 1).padStart(2, '0') + '-' + String(end.getDate()).padStart(2, '0'); row.endDate = endStr; row.status = '启用'; this.persist(); this.applyFilters(); this.$message.success('已续签至 ' + endStr); }).catch(() => {}); },
+    handleTerminate(row) { this.$prompt('请输入解除原因', '解除合同', { inputPlaceholder: '如:协商一致解除、严重违纪等' }).then(({ value }) => { if (value === undefined) return; row.status = '已终止'; row.remark = (row.remark ? row.remark + ';' : '') + '已解除:' + (value || '无'); this.persist(); this.applyFilters(); this.$message.success('合同已解除'); }).catch(() => {}); },
+    handleSign(row) { row.status = '启用'; this.persist(); this.applyFilters(); this.$message.success('合同已签订并启用'); },
+    handleArchive(row) { this.$confirm('确定将该合同归档吗?归档后只读', '归档确认', { type: 'info' }).then(() => { row.status = '已归档'; this.persist(); this.applyFilters(); this.$message.success('合同已归档'); }).catch(() => {}); },
+    triggerAttachUpload() {
+      const inputs = this.$refs.attachInput;
+      const arr = Array.isArray(inputs) ? inputs : [inputs];
+      const target = arr.find((i) => i);
+      if (target) target.click();
+      else {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.accept = '.pdf,.doc,.docx';
+        input.onchange = (e) => this.handleAttachUpload(e);
+        input.click();
+      }
+    },
+    handleAttachUpload(event) {
+      const file = event.target.files && event.target.files[0];
+      if (!file) return;
+      if (file.size > 10 * 1024 * 1024) { this.$message.error('附件大小不能超过 10MB'); return; }
+      this.changeForm.attachments.push({
+        name: file.name,
+        size: (file.size / 1024).toFixed(0) + ' KB',
+        uploadedAt: this.nowStr(),
+      });
+      this.$message.success('已上传:' + file.name);
+      if (event.target) event.target.value = '';
+    },
+    openAttach(file) { this.$message.info('打开附件:' + file.name); },
+  },
+};
+</script>
+
+<style scoped lang="scss" src="../../styles/views/contractManagement/index.scss"></style>

+ 194 - 0
src/views/contractManagement/mock.js

@@ -0,0 +1,194 @@
+// 合同管理 - 离线 mock 数据
+
+// 法人
+export const LEGAL_ENTITIES = [
+  '中英科技有限公司',
+  '长沙中盈制造有限公司',
+  '常德精工机械有限公司',
+  '北京天成伟达工矿设备有限公司',
+  '湖南中盈科技有限公司',
+  '湖南云盾消防设备有限公司',
+];
+
+// 部门
+export const DEPARTMENTS = [
+  '人力资源部', '信息部', '产品研发部', '财务部',
+  '市场部', '生产一车间', '质量部', '商务部', '运营管理部',
+];
+
+// 合同类型
+export const CONTRACT_TYPES = [
+  '全职', '兼职', '实习', '劳务派遣', '顾问', '外包',
+];
+
+// 合同状态
+export const CONTRACT_STATUS = [
+  { value: '启用', label: '启用' },
+  { value: '待签署', label: '待签署' },
+  { value: '试用期', label: '试用期' },
+  { value: '已到期', label: '已到期' },
+  { value: '已变更', label: '已变更' },
+  { value: '已终止', label: '已终止' },
+  { value: '已归档', label: '已归档' },
+];
+
+// 签署方式
+export const SIGN_METHODS = ['线上', '线下'];
+
+// 变更类型
+export const CHANGE_TYPES = [
+  { value: 'POSITION_LEVEL', label: '岗位职级变更' },
+  { value: 'POSITION_TRANSFER', label: '岗位调动' },
+  { value: 'DEPT_TRANSFER', label: '部门调动' },
+  { value: 'SALARY_ADJUST', label: '薪资调整' },
+  { value: 'CONTRACT_RENEW', label: '合同续签' },
+  { value: 'TERMINATION', label: '合同解除' },
+];
+
+// 合同模版(关联到合同模版页)
+export const CONTRACT_TEMPLATES = [
+  { id: 1, name: '固定期限劳动合同(标准)' },
+  { id: 2, name: '无固定期限劳动合同' },
+  { id: 3, name: '岗位变更协议' },
+  { id: 4, name: '薪资调整协议' },
+  { id: 5, name: '合同续签协议' },
+];
+
+// 岗位
+export const POSITIONS = [
+  '前端开发工程师', '后端开发工程师', '技术经理', '技术主管',
+  '产品经理', 'UI 设计师', '运营专员', 'HR 专员',
+  '财务专员', '销售经理', '生产主管', '质检员',
+];
+
+// 职级
+export const LEVELS = ['P3', 'P4', 'P5', 'P6', 'P7', 'M1', 'M2', 'M3'];
+
+// 合同记录种子数据
+export const contractSeeds = [
+  {
+    id: 1,
+    contractNo: 'HT20260815',
+    employeeNo: 'EMP20260801',
+    employeeName: '李晨曦',
+    legalEntity: '湖南中盈科技有限公司',
+    contractType: '固定期限劳动合同',
+    contractTypeShort: '全职',
+    department: '产品研发部',
+    positionName: '前端开发工程师',
+    positionDuty: '负责公司产品前端开发与维护',
+    signDoc: '劳动合同标准版.pdf',
+    signMethod: '线上',
+    startDate: '2026-08-15',
+    endDate: '2029-08-15',
+    status: '启用',
+    attachment: '员工合同_HC001.pdf',
+    level: 'P5',
+    salary: 18000,
+    manager: '王宇',
+    remark: '三年期固定期限合同',
+    createdAt: '2026-08-10 09:30',
+  },
+  {
+    id: 2,
+    contractNo: 'HT20260720',
+    employeeNo: 'EMP20260702',
+    employeeName: '周敏',
+    legalEntity: '中英科技有限公司',
+    contractType: '固定期限劳动合同',
+    contractTypeShort: '全职',
+    department: '人力资源部',
+    positionName: '招聘专员',
+    positionDuty: '负责公司各岗位招聘与人才库维护',
+    signDoc: '劳动合同标准版.pdf',
+    signMethod: '线上',
+    startDate: '2025-08-18',
+    endDate: '2028-08-17',
+    status: '启用',
+    attachment: '员工合同_HC002.pdf',
+    level: 'P3',
+    salary: 7500,
+    manager: '李亚洪',
+    remark: '',
+    createdAt: '2025-08-15 10:00',
+  },
+  {
+    id: 3,
+    contractNo: 'HT20260801',
+    employeeNo: 'EMP20260803',
+    employeeName: '刘洋',
+    legalEntity: '中英科技有限公司',
+    contractType: '固定期限劳动合同',
+    contractTypeShort: '全职',
+    department: '产品研发部',
+    positionName: '后端开发工程师',
+    positionDuty: '负责公司后端系统设计与开发',
+    signDoc: '劳动合同标准版.pdf',
+    signMethod: '线下',
+    startDate: '2024-08-20',
+    endDate: '2027-08-19',
+    status: '试用期',
+    attachment: '员工合同_HC003.pdf',
+    level: 'P4',
+    salary: 13000,
+    manager: '王宇',
+    remark: '试用期合同',
+    createdAt: '2024-08-18 14:30',
+  },
+  {
+    id: 4,
+    contractNo: 'HT20260501',
+    employeeNo: 'EMP20260504',
+    employeeName: '杨帆',
+    legalEntity: '中英科技有限公司',
+    contractType: '无固定期限劳动合同',
+    contractTypeShort: '全职',
+    department: '产品研发部',
+    positionName: '技术经理',
+    positionDuty: '负责产品研发部技术规划与团队管理',
+    signDoc: '无固定期限合同.pdf',
+    signMethod: '线下',
+    startDate: '2020-03-15',
+    endDate: '',
+    status: '启用',
+    attachment: '员工合同_HC004.pdf',
+    level: 'M2',
+    salary: 22000,
+    manager: '李亚洪',
+    remark: '已续签为无固定期限',
+    createdAt: '2020-03-10 09:00',
+  },
+];
+
+
+// 员工选项(用于发起合同)
+export const employeeOptions = [
+  { employeeNo: 'EMP20260801', name: '李晨曦', department: '产品研发部', position: '前端开发工程师', level: 'P5', salary: 18000, manager: '王宇', legalEntity: '湖南中盈科技有限公司' },
+  { employeeNo: 'EMP20260702', name: '周敏', department: '人力资源部', position: '招聘专员', level: 'P3', salary: 7500, manager: '李亚洪', legalEntity: '中英科技有限公司' },
+  { employeeNo: 'EMP20260803', name: '刘洋', department: '产品研发部', position: '后端开发工程师', level: 'P4', salary: 13000, manager: '王宇', legalEntity: '中英科技有限公司' },
+  { employeeNo: 'EMP20260504', name: '杨帆', department: '产品研发部', position: '技术经理', level: 'M2', salary: 22000, manager: '李亚洪', legalEntity: '中英科技有限公司' },
+  { employeeNo: 'EMP20260810', name: '张丽', department: '市场部', position: '市场专员', level: 'P3', salary: 8000, manager: '郭涛', legalEntity: '北京天成伟达工矿设备有限公司' },
+  { employeeNo: 'EMP20260815', name: '陈晨', department: '生产一车间', position: '生产主管', level: 'M1', salary: 15000, manager: '黄强', legalEntity: '长沙中盈制造有限公司' },
+];
+
+// 合同模版选项
+export const contractTemplateOptions = [
+  { id: 1, name: '固定期限劳动合同(标准)' },
+  { id: 2, name: '无固定期限劳动合同' },
+  { id: 3, name: '实习协议' },
+  { id: 4, name: '兼职协议' },
+  { id: 5, name: '保密协议' },
+];
+
+// 签署方式
+export const signMethods = ['线上', '线下'];
+
+// 合同状态
+export const contractStatusOptions = [
+  { value: '草稿', label: '草稿' },
+  { value: '待签署', label: '待签署' },
+  { value: '试用期', label: '试用期' },
+  { value: '生效中', label: '生效中' },
+];
+
+export const clone = (value) => JSON.parse(JSON.stringify(value));

+ 679 - 0
src/views/contractTemplate/index.vue

@@ -0,0 +1,679 @@
+<template>
+  <main class="ct-page">
+    <section class="ct-workspace">
+      <div class="ct-filter">
+        <div class="ct-filter-cell">
+          <span class="ct-filter-label">合同名称</span>
+          <el-input v-model.trim="filters.name" clearable placeholder="支持模糊查询" size="small" @keyup.enter.native="applyFilters" />
+        </div>
+        <div class="ct-filter-cell">
+          <span class="ct-filter-label">状态</span>
+          <el-select v-model="filters.enabled" clearable placeholder="启用" size="small">
+            <el-option label="启用" :value="true" />
+            <el-option label="禁用" :value="false" />
+          </el-select>
+        </div>
+        <div class="ct-filter-actions">
+          <el-button type="primary" size="small" @click="applyFilters">查询</el-button>
+          <el-button size="small" @click="resetFilters">重置</el-button>
+        </div>
+      </div>
+
+      <ele-pro-table ref="table" row-key="id" :columns="columns" :datasource="filteredRecords"
+        :need-page="true" :page-size="10" :page-sizes="[10, 20, 50, 100]"
+        :toolkit="['size', 'columns', 'fullscreen']" stripe class="ct-pro-table">
+        <template slot="toolbar">
+          <el-button type="primary" icon="el-icon-plus" size="small" @click="addRow">新增 +</el-button>
+        </template>
+        <template v-slot:name="{ row }">
+          <input v-model.trim="row.name" class="ct-cell-input" placeholder="请输入合同名称" @blur="markChanged(row)" />
+        </template>
+        <template v-slot:code="{ row }">
+          <span class="ct-cell-readonly">{{ row.code }}</span>
+        </template>
+        <template v-slot:contractType="{ row }">
+          <el-select v-model="row.contractType" filterable clearable class="ct-cell-select" placeholder="请选择"
+            size="mini" @change="markChanged(row)">
+            <el-option v-for="t in contractTypes" :key="t" :label="t" :value="t" />
+          </el-select>
+        </template>
+        <template v-slot:legalEntity="{ row }">
+          <el-select v-model="row.legalEntity" filterable clearable class="ct-cell-select" placeholder="请选择"
+            size="mini" @change="markChanged(row)">
+            <el-option v-for="l in legalEntities" :key="l" :label="l" :value="l" />
+          </el-select>
+        </template>
+        <template v-slot:positionSequence="{ row }">
+          <el-select v-model="row.positionSequence" filterable clearable class="ct-cell-select" placeholder="请选择"
+            size="mini" @change="markChanged(row)">
+            <el-option v-for="p in positionSequences" :key="p" :label="p" :value="p" />
+          </el-select>
+        </template>
+        <template v-slot:positionName="{ row }">
+          <el-select v-model="row.positionName" filterable clearable class="ct-cell-select" placeholder="请选择"
+            size="mini" @change="markChanged(row)">
+            <el-option v-for="p in positionNames" :key="p" :label="p" :value="p" />
+          </el-select>
+        </template>
+        <template v-slot:positionLevel="{ row }">
+          <el-select v-model="row.positionLevel" clearable class="ct-cell-select" placeholder="请选择"
+            size="mini" @change="markChanged(row)">
+            <el-option v-for="l in positionLevels" :key="l" :label="l" :value="l" />
+          </el-select>
+        </template>
+        <template v-slot:content="{ row }">
+          <button class="ct-link" @click="openWordEditor(row)">查看</button>
+        </template>
+        <template v-slot:attachment="{ row }">
+          <div class="ct-attachment-cell">
+            <button class="ct-link" @click="triggerUpload(row)">附件上传</button>
+            <div v-if="row.attachmentName" class="ct-attachment-name">
+              <i class="el-icon-document"></i> {{ row.attachmentName }}
+            </div>
+          </div>
+        </template>
+        <template v-slot:enabled="{ row }">
+          <button class="ct-status-btn" :class="row.enabled ? 'active' : 'inactive'" @click="toggleStatus(row)">
+            {{ row.enabled ? '启用' : '禁用' }}
+          </button>
+        </template>
+        <template v-slot:reviewer="{ row }">
+          <input v-model.trim="row.reviewer" class="ct-cell-input" placeholder="审核人" @blur="markChanged(row)" />
+        </template>
+        <template v-slot:action="{ row }">
+          <button class="ct-action-link" @click="openEdit(row)">编辑</button>
+          <button class="ct-action-link danger" @click="removeRow(row)">删除</button>
+        </template>
+        <template slot="empty">
+          <div class="ct-empty-row">暂无合同模版</div>
+        </template>
+      </ele-pro-table>
+
+      <input ref="fileInput" type="file" accept=".pdf,.doc,.docx,.txt" style="display:none" />
+
+      <div class="ct-notes">
+        <p>1. 合同模版内容点击弹框 word 编辑页面,输入 word 内容,点击保存提交即可,可用于线上签订电子合同</p>
+        <p>2. 也可合同模版通过上传附件形式,可查看附件合同内容。</p>
+        <p>3. 合同名称手动输入,合同编码系统自动生成不可编辑修改,手动点击启用,用颜色标识注释。</p>
+        <p>4. 信息填写后点击启用后走审核流程,对合同模版审批操作,审核功能按照现有系统功能走审批流程。</p>
+      </div>
+    </section>
+
+    <!-- 简单新增弹窗(第一层) -->
+    <ele-modal
+      :visible.sync="quickVisible"
+      width="900px"
+      title="快速新增合同模版"
+      append-to-body
+      custom-class="ct-quick-dialog"
+      :close-on-click-modal="false"
+     :movable="true">
+      <div class="ct-quick-tip">
+        <i class="el-icon-info"></i> 选择下方的「新增模板」可打开完整编辑(含富文本),或选择「导入模板」快速导入(当前版本暂未接入)。
+      </div>
+
+      <div class="ct-quick-actions">
+        <el-button type="primary" icon="el-icon-document-add" size="small" @click="openFull">新增模板</el-button>
+        <el-button icon="el-icon-upload" size="small" @click="importTemplate">导入模板</el-button>
+      </div>
+
+      <div class="ct-quick-section">
+        <div class="ct-quick-section-title">模版筛选</div>
+        <div class="ct-quick-filter-row">
+          <div class="ct-quick-filter-cell">
+            <span class="ct-quick-filter-label">模版列表</span>
+            <el-select v-model="quickFilters.list" clearable placeholder="全部" size="small">
+              <el-option label="全部" value="" />
+              <el-option label="我创建的" value="MINE" />
+              <el-option label="启用中" value="ACTIVE" />
+            </el-select>
+          </div>
+          <div class="ct-quick-filter-cell">
+            <span class="ct-quick-filter-label">模板名称</span>
+            <el-input v-model.trim="quickFilters.name" clearable placeholder="请输入" size="small" />
+          </div>
+          <div class="ct-quick-filter-cell">
+            <span class="ct-quick-filter-label">合同类型</span>
+            <el-select v-model="quickFilters.contractType" clearable placeholder="全部" size="small">
+              <el-option v-for="t in contractTypes" :key="t" :label="t" :value="t" />
+            </el-select>
+          </div>
+          <div class="ct-quick-filter-cell">
+            <span class="ct-quick-filter-label">版本</span>
+            <el-select v-model="quickFilters.version" clearable placeholder="全部" size="small">
+              <el-option v-for="v in versions" :key="v" :label="v" :value="v" />
+            </el-select>
+          </div>
+          <div class="ct-quick-filter-cell">
+            <span class="ct-quick-filter-label">状态</span>
+            <el-select v-model="quickFilters.status" clearable placeholder="全部" size="small">
+              <el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
+            </el-select>
+          </div>
+          <div class="ct-quick-filter-cell ct-quick-flex">
+            <el-input v-model.trim="quickFilters.keyword" clearable placeholder="操作:搜索模板名称、编码或岗位" prefix-icon="el-icon-search" size="small" />
+          </div>
+        </div>
+      </div>
+
+      <div class="ct-quick-section">
+        <div class="ct-quick-section-title">现有模版列表({{ quickFilteredRows.length }} 条)</div>
+        <table class="ct-quick-table">
+          <thead>
+            <tr>
+              <th style="width:50px;">序号</th>
+              <th>模版名称</th>
+              <th style="width:130px;">模板编号</th>
+              <th style="width:120px;">合同类型</th>
+              <th style="width:80px;">版本</th>
+              <th style="width:90px;">状态</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(row, index) in quickFilteredRows" :key="row.id">
+              <td>{{ index + 1 }}</td>
+              <td style="text-align:left;padding-left:10px;">{{ row.name }}</td>
+              <td>{{ row.code }}</td>
+              <td>{{ row.contractType }}</td>
+              <td>{{ row.version }}</td>
+              <td>
+                <span class="ct-status-pill" :class="statusClass(row.status)">{{ row.status }}</span>
+              </td>
+            </tr>
+            <tr v-if="!quickFilteredRows.length">
+              <td colspan="6" style="padding:20px;color:#7e8b9d;">暂无模版数据</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+
+      <div class="ct-quick-foot">
+        <el-button @click="quickVisible = false">关闭</el-button>
+      </div>
+    </ele-modal>
+
+    <!-- 完整编辑弹窗(第二层) -->
+    <ele-modal
+      :visible.sync="dialogVisible"
+      width="960px"
+      :title="dialogTitle"
+      append-to-body
+      custom-class="ct-dialog"
+      :close-on-click-modal="false"
+      :before-close="closeDialog"
+     :movable="true">
+      <el-form ref="formRef" :model="form" :rules="rules" label-position="top">
+        <div class="ct-form-grid">
+          <el-form-item label="所属法人" prop="legalEntity">
+            <el-select v-model="form.legalEntity" filterable placeholder="法人法人">
+              <el-option v-for="l in legalEntities" :key="l" :label="l" :value="l" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="模板名称" prop="name">
+            <el-input v-model.trim="form.name" placeholder="请输入合同模版名称" />
+          </el-form-item>
+          <el-form-item label="模板编码" prop="code">
+            <el-input v-model.trim="form.code" placeholder="自动生成,可手动修改" />
+          </el-form-item>
+          <el-form-item label="合同类型" prop="contractType">
+            <el-select v-model="form.contractType" placeholder="请选择合同类型">
+              <el-option v-for="t in contractTypes" :key="t" :label="t" :value="t" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="岗位名称">
+            <el-input v-model.trim="form.positionName" placeholder="如:销售经理" />
+          </el-form-item>
+          <el-form-item label="模板版本">
+            <el-input v-model.trim="form.version" placeholder="V1.0" />
+          </el-form-item>
+          <el-form-item label="岗位层级">
+            <el-select v-model="form.positionLevel" clearable placeholder="请选择">
+              <el-option v-for="l in positionLevels" :key="l" :label="l" :value="l" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="合同文件模版">
+            <el-select v-model="form.fileTemplate" clearable placeholder="请选择">
+              <el-option v-for="f in fileTemplates" :key="f" :label="f" :value="f" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="保密条款">
+            <el-select v-model="form.confidential" placeholder="请选择">
+              <el-option v-for="o in confidentialOptions" :key="o" :label="o" :value="o" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="状态">
+            <el-select v-model="form.status" placeholder="请选择">
+              <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-date-picker v-model="form.effectiveDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择" style="width:100%;" />
+          </el-form-item>
+          <el-form-item label="员工手册">
+            <el-select v-model="form.employeeHandbook" clearable placeholder="请选择">
+              <el-option v-for="h in employeeHandbooks" :key="h" :label="h" :value="h" />
+            </el-select>
+          </el-form-item>
+        </div>
+      </el-form>
+
+      <div class="ct-section-title">合同正文(富文本)</div>
+      <div class="ct-editor-wrap">
+        <div class="ct-editor-toolbar">
+          <el-button size="mini" :type="editor.bold ? 'primary' : 'default'" @click="execFormat('bold')" icon="el-icon-edit">加粗</el-button>
+          <el-button size="mini" :type="editor.italic ? 'primary' : 'default'" @click="execFormat('italic')" icon="el-icon-edit-outline">斜体</el-button>
+          <span class="ct-tb-sep"></span>
+          <el-button size="mini" @click="applyBlock('h1')">标题1</el-button>
+          <el-button size="mini" @click="applyBlock('h2')">标题2</el-button>
+          <el-button size="mini" @click="applyBlock('h3')">标题3</el-button>
+          <span class="ct-tb-sep"></span>
+          <el-popover placement="bottom-start" width="400" trigger="click" popper-class="ct-variable-popover">
+            <div class="ct-var-grid">
+              <div v-for="v in placeholderVariables" :key="v" class="ct-var-item" @click="insertVariable(v)">{{ formatVar(v) }}</div>
+            </div>
+            <el-button slot="reference" size="mini" icon="el-icon-document-add">插入占位符</el-button>
+          </el-popover>
+        </div>
+        <div ref="editorBody" class="ct-editor-body" contenteditable="true" spellcheck="false" @input="onEditorInput" @blur="onEditorInput" v-html="form.content"></div>
+        <div class="ct-editor-foot">
+          <span><i class="el-icon-info"></i> 支持加粗、斜体、标题样式;可通过「插入占位符」添加系统变量</span>
+          <span>字符数:{{ editor.charCount }}</span>
+        </div>
+      </div>
+
+      <template slot="footer">
+        <el-button @click="closeDialog">取消</el-button>
+        <el-button @click="saveAsDraft">保存为草稿</el-button>
+        <el-button type="primary" @click="saveAndSubmit">保存并提交审批</el-button>
+      </template>
+    </ele-modal>
+  </main>
+</template>
+
+<script>
+import {
+  contractTemplateSeeds,
+  LEGAL_ENTITIES,
+  CONTRACT_TYPES,
+  TEMPLATE_CODES,
+  POSITION_NAMES,
+  TEMPLATE_VERSIONS,
+  POSITION_LEVELS,
+  FILE_TEMPLATES,
+  CONFIDENTIAL_OPTIONS,
+  EMPLOYEE_HANDBOOKS,
+  STATUS_OPTIONS,
+  POSITION_SEQUENCES,
+  PLACEHOLDER_VARIABLES,
+  clone as deepClone,
+} from './mock';
+
+const STORE_KEY = 'oa-contract-templates-v13';
+
+function blank() {
+  return {
+    id: null,
+    name: '',
+    code: '',
+    contractType: '',
+    legalEntity: '',
+    positionName: '',
+    positionSequence: '',
+    positionLevel: '',
+    fileTemplate: '',
+    confidential: '无',
+    status: '草稿',
+    version: 'V1.0',
+    effectiveDate: '',
+    employeeHandbook: '',
+    content: '',
+    reviewer: '',
+    createdAt: '',
+    attachment: '',
+    attachmentName: '',
+    enabled: false,
+  };
+}
+
+function blankQuick() {
+  return {
+    name: '',
+    code: '',
+    contractType: '',
+    version: 'V1.0',
+    status: '草稿',
+    legalEntity: '',
+  };
+}
+
+export default {
+  name: 'ContractTemplate',
+  data() {
+    return {
+      rows: [],
+      filters: { name: '', enabled: '' },
+      legalEntities: LEGAL_ENTITIES,
+      contractTypes: CONTRACT_TYPES,
+      templateCodes: TEMPLATE_CODES,
+      positionNames: POSITION_NAMES,
+      positionSequences: POSITION_SEQUENCES,
+      versions: TEMPLATE_VERSIONS,
+      positionLevels: POSITION_LEVELS,
+      fileTemplates: FILE_TEMPLATES,
+      confidentialOptions: CONFIDENTIAL_OPTIONS,
+      employeeHandbooks: EMPLOYEE_HANDBOOKS,
+      statusOptions: STATUS_OPTIONS,
+      placeholderVariables: PLACEHOLDER_VARIABLES,
+      // 简单弹窗
+      quickVisible: false,
+      quickForm: blankQuick(),
+      quickFilters: { list: '', name: '', contractType: '', version: '', status: '', keyword: '' },
+      quickRules: {
+        name: [{ required: true, message: '请输入模版名称', trigger: 'blur' }],
+        code: [{ required: true, message: '请输入模版编号', trigger: 'blur' }],
+        contractType: [{ required: true, message: '请选择合同类型', trigger: 'change' }],
+        version: [{ required: true, message: '请选择版本', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+        legalEntity: [{ required: true, message: '请选择所属法人', trigger: 'change' }],
+      },
+      // 完整编辑弹窗
+      dialogVisible: false,
+      dialogMode: 'create',
+      form: blank(),
+      rules: {
+        name: [{ required: true, message: '请输入模板名称', trigger: 'blur' }],
+        code: [{ required: true, message: '请输入模板编码', trigger: 'blur' }],
+        contractType: [{ required: true, message: '请选择合同类型', trigger: 'change' }],
+        legalEntity: [{ required: true, message: '请选择所属法人', trigger: 'change' }],
+      },
+      editor: { bold: false, italic: false, charCount: 0 },
+      // 当前正在上传附件的行(用于 triggerUpload)
+      _uploadRow: null,
+      columns: [
+        { type: 'index', label: '序号', width: 50, align: 'center', headerAlign: 'center' },
+        { prop: 'name', label: '合同名称', minWidth: 180, slot: 'name' },
+        { prop: 'code', label: '合同编码', minWidth: 140, slot: 'code' },
+        { prop: 'contractType', label: '合同类型', minWidth: 130, slot: 'contractType' },
+        { prop: 'legalEntity', label: '所属法人', minWidth: 160, slot: 'legalEntity' },
+        { prop: 'positionSequence', label: '岗位序列', minWidth: 110, slot: 'positionSequence' },
+        { prop: 'positionName', label: '岗位名称', minWidth: 120, slot: 'positionName' },
+        { prop: 'positionLevel', label: '岗位层级', minWidth: 90, slot: 'positionLevel', align: 'center' },
+        { prop: 'content', label: '合同内容', minWidth: 90, slot: 'content', align: 'center' },
+        { prop: 'attachment', label: '合同模版附件', minWidth: 130, slot: 'attachment', align: 'center' },
+        { prop: 'enabled', label: '状态', minWidth: 80, slot: 'enabled', align: 'center' },
+        { prop: 'createdAt', label: '创建时间', minWidth: 140, showOverflowTooltip: true },
+        { prop: 'reviewer', label: '审核人', minWidth: 90, slot: 'reviewer', align: 'center' },
+        { prop: 'action', label: '操作', width: 130, slot: 'action', align: 'center' },
+      ],
+    };
+  },
+  computed: {
+    dialogTitle() {
+      return this.dialogMode === 'create' ? '新增合同模版' : '编辑合同模版';
+    },
+    filteredRecords() {
+      const { name, enabled } = this.filters;
+      const key = String(name || '').trim().toLowerCase();
+      return this.rows.filter((row) => {
+        if (key && !String(row.name || '').toLowerCase().includes(key)) return false;
+        if (enabled === true && !row.enabled) return false;
+        if (enabled === false && row.enabled) return false;
+        return true;
+      });
+    },
+    totalCount() {
+      return this.filteredRecords.length;
+    },
+    quickFilteredRows() {
+      const { name, contractType, version, status, list, keyword } = this.quickFilters;
+      const key = String(name || '').trim().toLowerCase();
+      const kw = String(keyword || '').trim().toLowerCase();
+      return this.rows.filter((row) => {
+        if (key && !String(row.name || '').toLowerCase().includes(key)) return false;
+        if (contractType && row.contractType !== contractType) return false;
+        if (version && row.version !== version) return false;
+        if (status && row.status !== status) return false;
+        if (list === 'ACTIVE' && row.status !== '启用') return false;
+        if (kw && ![row.name, row.code, row.positionName].some((v) => String(v || '').toLowerCase().includes(kw))) return false;
+        return true;
+      });
+    },
+  },
+  created() {
+    this.load();
+  },
+  methods: {
+    nowStr() {
+      const d = new Date();
+      const pad = (n) => String(n).padStart(2, '0');
+      return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
+    },
+    nextCode() {
+      const max = this.rows
+        .map((r) => Number(String(r.code || '').replace(/[^0-9]/g, '')) || 0)
+        .reduce((a, b) => Math.max(a, b), 0);
+      return 'HT-2026-' + String(max + 1).padStart(3, '0');
+    },
+    statusClass(s) {
+      const map = {
+        '启用': 'active',
+        '停用': 'inactive',
+        '草稿': 'draft',
+        '审核中': 'pending',
+      };
+      return map[s] || 'draft';
+    },
+    load() {
+      try {
+        const cached = localStorage.getItem(STORE_KEY);
+        if (cached) this.rows = JSON.parse(cached);
+        else { this.rows = deepClone(contractTemplateSeeds); this.persist(); }
+      } catch (e) { this.rows = deepClone(contractTemplateSeeds); }
+    },
+    persist() { try { localStorage.setItem(STORE_KEY, JSON.stringify(this.rows)); } catch (e) {} },
+    applyFilters() { this.$refs.table && this.$refs.table.reload(); },
+    resetFilters() { this.filters = { name: '', enabled: '' }; this.applyFilters(); },
+    markChanged(row) {
+      if (!row.createdAt) row.createdAt = this.nowStr();
+      this.persist();
+    },
+    async toggleStatus(row) {
+      if (!row.name) return this.$message.warning('请先填写合同名称');
+      if (row.enabled) {
+        row.enabled = false;
+        this.persist();
+        this.$message.success('模版已停用');
+      } else {
+        this.$confirm('点击「启用」后将进入审核流程,请确认合同信息已填写完整。', '提交审核', { type: 'info' })
+          .then(() => {
+            row.enabled = true;
+            row.createdAt = this.nowStr();
+            this.persist();
+            this.$message.success('已提交审核,审核通过后自动启用');
+          })
+          .catch(() => {});
+      }
+    },
+    triggerUpload(row) {
+      this._uploadRow = row;
+      const input = this.$refs.fileInput;
+      if (input) input.click();
+      else {
+        const el = document.createElement('input');
+        el.type = 'file';
+        el.accept = '.pdf,.doc,.docx,.txt';
+        el.onchange = (e) => this.handleUpload(e, row);
+        el.click();
+      }
+    },
+    handleUpload(event, row) {
+      const file = event.target.files && event.target.files[0];
+      if (!file) return;
+      if (file.size > 10 * 1024 * 1024) {
+        this.$message.error('附件大小不能超过 10MB');
+        return;
+      }
+      (row || this._uploadRow).attachmentName = file.name;
+      (row || this._uploadRow).attachment = file.name;
+      if (!(row || this._uploadRow).createdAt) (row || this._uploadRow).createdAt = this.nowStr();
+      this.persist();
+      this.$message.success('附件「' + file.name + '」已上传');
+      if (event.target) event.target.value = '';
+    },
+    addRow() {
+      this.quickForm = blankQuick();
+      this.quickForm.code = this.nextCode();
+      this.quickFilters = { list: '', name: '', contractType: '', version: '', status: '', keyword: '' };
+      this.quickVisible = true;
+    },
+    importTemplate() {
+      this.$message.warning('导入模板功能暂未接入');
+    },
+    async quickSave() {
+      if (!this.$refs.quickFormRef) return this.$message.warning('表单未加载完成');
+      const valid = await this.$refs.quickFormRef.validate().catch(() => false);
+      if (!valid) return;
+      this.commitQuick();
+    },
+    commitQuick() {
+      const row = deepClone(blank());
+      row.name = this.quickForm.name;
+      row.code = this.quickForm.code;
+      row.contractType = this.quickForm.contractType;
+      row.version = this.quickForm.version;
+      row.status = this.quickForm.status;
+      row.legalEntity = this.quickForm.legalEntity;
+      row.createdAt = this.nowStr();
+      this.rows.unshift(row);
+      this.persist();
+      this.quickVisible = false;
+      this.$message.success('已快速创建模版');
+    },
+    openFull() {
+      this.form = blank();
+      this.form.name = this.quickForm.name;
+      this.form.code = this.quickForm.code;
+      this.form.contractType = this.quickForm.contractType;
+      this.form.version = this.quickForm.version;
+      this.form.status = this.quickForm.status;
+      this.form.legalEntity = this.quickForm.legalEntity;
+      this.form.confidential = '无';
+      this.dialogMode = 'create';
+      this.dialogVisible = true;
+      this.$nextTick(() => this.updateCharCount());
+    },
+    openEdit(row) {
+      this.dialogMode = 'edit';
+      this.form = deepClone(row);
+      this.dialogVisible = true;
+      this.$nextTick(() => this.updateCharCount());
+    },
+    closeDialog(done) {
+      this.$confirm('当前修改尚未保存,确定关闭吗?', '提示', { type: 'warning' })
+        .then(() => {
+          this.dialogVisible = false;
+          this.form = blank();
+          if (typeof done === 'function') done();
+        })
+        .catch(() => {});
+    },
+    formatVar(name) {
+      return '{{' + name + '}}';
+    },
+    onEditorInput() {
+      const html = this.$refs.editorBody ? this.$refs.editorBody.innerHTML : '';
+      this.form.content = html;
+      this.updateCharCount();
+    },
+    updateCharCount() {
+      if (!this.$refs.editorBody) return;
+      const text = this.$refs.editorBody.innerText || '';
+      this.editor.charCount = text.length;
+    },
+    execFormat(cmd) {
+      if (!this.$refs.editorBody) return;
+      this.$refs.editorBody.focus();
+      document.execCommand(cmd, false, null);
+      this.editor[cmd] = !this.editor[cmd];
+      this.onEditorInput();
+    },
+    applyBlock(tag) {
+      if (!this.$refs.editorBody) return;
+      this.$refs.editorBody.focus();
+      document.execCommand('formatBlock', false, tag);
+      this.onEditorInput();
+    },
+    insertVariable(name) {
+      if (!this.$refs.editorBody) return;
+      this.$refs.editorBody.focus();
+      const token = '{{' + name + '}}';
+      const sel = window.getSelection();
+      if (sel && sel.rangeCount > 0) {
+        const range = sel.getRangeAt(0);
+        const text = document.createTextNode(token);
+        range.insertNode(text);
+        range.setStartAfter(text);
+        range.setEndAfter(text);
+        sel.removeAllRanges();
+        sel.addRange(range);
+      } else {
+        document.execCommand('insertText', false, token);
+      }
+      this.onEditorInput();
+      this.$message.success('已插入占位符:' + token);
+    },
+    async saveAsDraft() {
+      if (!this.$refs.formRef) return this.$message.warning('表单未加载完成');
+      const valid = await this.$refs.formRef.validate().catch(() => false);
+      if (!valid) return;
+      this.commit('草稿');
+    },
+    async saveAndSubmit() {
+      if (!this.$refs.formRef) return this.$message.warning('表单未加载完成');
+      const valid = await this.$refs.formRef.validate().catch(() => false);
+      if (!valid) return;
+      if (!this.form.content || this.form.content.replace(/<[^>]+>/g, '').trim().length < 10) {
+        return this.$message.warning('请填写合同正文内容(不少于 10 个字符)');
+      }
+      this.commit('审核中');
+    },
+    commit(status) {
+      const data = deepClone(this.form);
+      data.status = status;
+      if (status === '审核中') data.submittedAt = this.nowStr();
+      if (!data.id) {
+        data.id = Date.now();
+        data.createdAt = this.nowStr();
+        this.rows.unshift(data);
+      } else {
+        const idx = this.rows.findIndex((r) => r.id === data.id);
+        if (idx > -1) this.$set(this.rows, idx, data);
+      }
+      this.persist();
+      this.dialogVisible = false;
+      this.form = blank();
+      if (status === '草稿') this.$message.success('已保存为草稿,可继续编辑');
+      else this.$message.success('已提交审批,审核通过后自动启用');
+    },
+    openWordEditor(row) {
+      this.$message.info('打开合同内容编辑器(原型 Word 风格):' + row.name);
+    },
+    removeRow(row) {
+      if (!row.id) {
+        this.rows = this.rows.filter((r) => r !== row);
+        this.persist();
+        return;
+      }
+      this.$confirm('确定删除该合同模版吗?删除后不可恢复。', '删除确认', { type: 'warning' })
+        .then(() => {
+          this.rows = this.rows.filter((r) => r.id !== row.id);
+          this.persist();
+          this.$message.success('模版已删除');
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss" src="../../styles/views/contractTemplate/index.scss"></style>

+ 429 - 0
src/views/contractTemplate/mock.js

@@ -0,0 +1,429 @@
+// 合同模版 - 离线 mock 数据
+
+// 法人
+export const LEGAL_ENTITIES = [
+  '中英科技有限公司', '长沙中盈制造有限公司', '常德精工机械有限公司',
+  '北京天成伟达工矿设备有限公司', '湖南中盈科技有限公司', '湖南云盾消防设备有限公司',
+];
+
+// 合同类型
+export const CONTRACT_TYPES = [
+  '固定期限劳动合同', '无固定期限劳动合同', '以完成一定任务为期限',
+  '劳务派遣合同', '实习协议', '兼职协议', '保密协议',
+  '竞业限制协议', '培训服务协议', '合同变更协议', '合同终止协议', '合同续签协议',
+];
+
+// 合同模板编码 (种子参考,可保留但模板编码会自动生成)
+export const TEMPLATE_CODES = [
+  '固定 001-HT01-01', '固定 002-HT02-01', '固定 003-HT03-01',
+  '派遣 001-DB01-01', '实习 001-SX01-01',
+  '保密 001-BM01-01', '保密 002-BM02-01',
+  '竞业 001-JY01-01', '兼职 001-JZ01-01',
+];
+
+// 岗位名称
+export const POSITION_NAMES = [
+  '销售经理', '销售专员', '区域销售', '采购经理', '采购专员',
+  '生产主管', '生产专员', '操作工', '技术工程师', '技术员',
+  '财务经理', '成本会计', '出纳', 'HR 经理', '招聘专员', '薪酬专员', '行政主管', '前台',
+];
+
+// 模板版本
+export const TEMPLATE_VERSIONS = ['V1.0', 'V1.1', 'V1.5', 'V2.0', 'V2.1', 'V3.0'];
+
+// 岗位层级
+export const POSITION_LEVELS = ['P1', 'P2', 'P3', 'P4', 'P5', 'M1', 'M2', 'M3', 'O1', 'O2', 'O3'];
+
+// 合同文件模版
+export const FILE_TEMPLATES = ['劳动合同标准版', '派遣合同模版', '实习协议模版', '保密协议模版', '竞业限制模版', '兼职合同模版'];
+
+// 保密条款
+export const CONFIDENTIAL_OPTIONS = ['无', '包含保密条款', '包含竞业限制条款', '包含保密+竞业条款'];
+
+// 员工手册
+export const EMPLOYEE_HANDBOOKS = ['通用员工手册', '管理层员工手册', '技术员工手册', '销售员工手册', '生产员工手册'];
+
+// 状态 (用于表单/筛选/statusClass 渲染)
+export const STATUS_OPTIONS = [
+  { value: '草稿', label: '草稿' },
+  { value: '启用', label: '启用' },
+  { value: '停用', label: '停用' },
+  { value: '审核中', label: '审核中' },
+];
+
+// 岗位序列
+export const POSITION_SEQUENCES = ['管理序列', '技术序列', '销售序列', '生产序列', '职能序列', '操作序列'];
+
+// 系统占位变量 (富文本编辑器插入)
+export const PLACEHOLDER_VARIABLES = [
+  '公司名称', '公司地址', '法定代表人',
+  '员工姓名', '性别', '身份证号', '通讯地址', '联系电话',
+  '工作岗位', '工作地点', '所属部门',
+  '合同起始日期', '合同终止日期', '试用期至',
+  '月薪资', '试用期薪资',
+];
+
+// 审核人 (用于自动填充 reviewer 字段)
+export const REVIEWERS = ['李亚洪', '张伟', '王宇', '郭涛', '黄强', '陈丽'];
+
+// 默认合同正文富文本
+export const DEFAULT_CONTRACT_CONTENT = `<h2>劳动合同</h2><p>甲方(用人单位):<strong>{{公司名称}}</strong></p><p>乙方(员工):<strong>{{员工姓名}}</strong></p><p>身份证号:{{身份证号}}</p><p>联系方式:{{联系电话}}</p><h3>一、合同期限</h3><p>本合同为固定期限劳动合同,自 <strong>{{合同起始日期}}</strong> 起至 <strong>{{合同终止日期}}</strong> 止。其中试用期至 <strong>{{试用期至}}</strong>。</p><h3>二、工作内容与地点</h3><p>乙方从事 <strong>{{工作岗位}}</strong> 工作,工作地点为 <strong>{{工作地点}}</strong>,隶属于 <strong>{{所属部门}}</strong>。</p><h3>三、薪资待遇</h3><p>乙方月薪资为人民币 <strong>{{月薪资}}</strong> 元(其中试用期内 <strong>{{试用期薪资}}</strong> 元)。</p><h3>四、其他约定</h3><p>本合同未尽事宜,按《中华人民共和国劳动合同法》及甲方依法制定的规章制度执行。</p>`;
+
+// 种子数据 (合同模版)
+export const contractTemplateSeeds = [
+  {
+    id: 1,
+    name: '固定期限劳动合同',
+    code: 'HT-2026-001',
+    contractType: '固定期限劳动合同',
+    legalEntity: '中英科技有限公司',
+    positionName: '销售经理',
+    positionSequence: '销售序列',
+    positionLevel: 'M1',
+    fileTemplate: '劳动合同标准版',
+    confidential: '包含保密条款',
+    status: '启用',
+    enabled: true,
+    version: 'V2.0',
+    effectiveDate: '2026-01-15',
+    employeeHandbook: '管理层员工手册',
+    content: DEFAULT_CONTRACT_CONTENT,
+    reviewer: '李亚洪',
+    createdAt: '2026-08-10 09:30',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 2,
+    name: 'MB-001-HT01-01',
+    code: 'HT-2026-002',
+    contractType: '固定期限劳动合同',
+    legalEntity: '中英科技有限公司',
+    positionName: '技术工程师',
+    positionSequence: '技术序列',
+    positionLevel: 'P4',
+    fileTemplate: '劳动合同标准版',
+    confidential: '包含保密+竞业条款',
+    status: '启用',
+    enabled: true,
+    version: 'V2.0',
+    effectiveDate: '2026-02-01',
+    employeeHandbook: '技术员工手册',
+    content: DEFAULT_CONTRACT_CONTENT,
+    reviewer: '李亚洪',
+    createdAt: '2026-08-12 14:20',
+    attachment: '劳动合同标准版.docx',
+    attachmentName: '劳动合同标准版.docx',
+  },
+  {
+    id: 3,
+    name: '固定期限劳动合同',
+    code: 'HT-2026-003',
+    contractType: '固定期限劳动合同',
+    legalEntity: '长沙中盈制造有限公司',
+    positionName: '生产主管',
+    positionSequence: '生产序列',
+    positionLevel: 'M1',
+    fileTemplate: '劳动合同标准版',
+    confidential: '无',
+    status: '启用',
+    enabled: true,
+    version: 'V2.0',
+    effectiveDate: '2026-03-10',
+    employeeHandbook: '生产员工手册',
+    content: DEFAULT_CONTRACT_CONTENT,
+    reviewer: '张伟',
+    createdAt: '2026-08-15 10:00',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 4,
+    name: '保密协议',
+    code: 'HT-2026-004',
+    contractType: '保密协议',
+    legalEntity: '中英科技有限公司',
+    positionName: '技术工程师',
+    positionSequence: '技术序列',
+    positionLevel: 'P3',
+    fileTemplate: '保密协议模版',
+    confidential: '包含保密条款',
+    status: '启用',
+    enabled: true,
+    version: 'V1.0',
+    effectiveDate: '2026-01-01',
+    employeeHandbook: '技术员工手册',
+    content: '<h2>保密协议</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方:<strong>{{员工姓名}}</strong></p><p>乙方在职期间及离职后,应对甲方的商业秘密承担保密义务。</p>',
+    reviewer: '李亚洪',
+    createdAt: '2026-07-20 14:30',
+    attachment: '保密协议模版.docx',
+    attachmentName: '保密协议模版.docx',
+  },
+  {
+    id: 5,
+    name: 'MB-001-BM06-01',
+    code: 'HT-2026-005',
+    contractType: '保密协议',
+    legalEntity: '中英科技有限公司',
+    positionName: 'HR 经理',
+    positionSequence: '职能序列',
+    positionLevel: 'M1',
+    fileTemplate: '保密协议模版',
+    confidential: '包含保密条款',
+    status: '启用',
+    enabled: true,
+    version: 'V1.0',
+    effectiveDate: '2026-04-01',
+    employeeHandbook: '管理层员工手册',
+    content: '<h2>保密协议(管理层)</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方:<strong>{{员工姓名}}</strong></p><p>鉴于乙方担任公司管理层职务,特别约定保密义务如下...</p>',
+    reviewer: '李亚洪',
+    createdAt: '2026-07-25 11:15',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 6,
+    name: '草稿待完善',
+    code: 'HT-2026-006',
+    contractType: '固定期限劳动合同',
+    legalEntity: '北京天成伟达工矿设备有限公司',
+    positionName: '区域销售',
+    positionSequence: '销售序列',
+    positionLevel: 'M2',
+    fileTemplate: '劳动合同标准版',
+    confidential: '无',
+    status: '草稿',
+    enabled: false,
+    version: 'V1.0',
+    effectiveDate: '',
+    employeeHandbook: '销售员工手册',
+    content: '<p>草稿待完善...</p>',
+    reviewer: '',
+    createdAt: '2026-08-25 16:45',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 7,
+    name: '竞业限制协议',
+    code: 'HT-2026-007',
+    contractType: '竞业限制协议',
+    legalEntity: '中英科技有限公司',
+    positionName: '技术工程师',
+    positionSequence: '技术序列',
+    positionLevel: 'P4',
+    fileTemplate: '竞业限制模版',
+    confidential: '包含竞业限制条款',
+    status: '停用',
+    enabled: false,
+    version: 'V1.0',
+    effectiveDate: '2026-05-01',
+    employeeHandbook: '技术员工手册',
+    content: '<h2>竞业限制协议</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方:<strong>{{员工姓名}}</strong></p><p>乙方在职期间及离职后 24 个月内,不得从事与甲方相竞争的业务。</p>',
+    reviewer: '李亚洪',
+    createdAt: '2026-06-10 09:00',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 8,
+    name: 'MB-001-JY07-01',
+    code: 'HT-2026-008',
+    contractType: '竞业限制协议',
+    legalEntity: '中英科技有限公司',
+    positionName: 'HR 经理',
+    positionSequence: '职能序列',
+    positionLevel: 'M2',
+    fileTemplate: '竞业限制模版',
+    confidential: '包含竞业限制条款',
+    status: '停用',
+    enabled: false,
+    version: 'V1.0',
+    effectiveDate: '2026-05-15',
+    employeeHandbook: '管理层员工手册',
+    content: '<h2>竞业限制协议(管理层)</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方:<strong>{{员工姓名}}</strong></p>',
+    reviewer: '李亚洪',
+    createdAt: '2026-06-12 14:00',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 9,
+    name: '竞业限制协议',
+    code: 'HT-2026-009',
+    contractType: '竞业限制协议',
+    legalEntity: '中英科技有限公司',
+    positionName: '技术工程师',
+    positionSequence: '技术序列',
+    positionLevel: 'P5',
+    fileTemplate: '竞业限制模版',
+    confidential: '包含竞业限制条款',
+    status: '停用',
+    enabled: false,
+    version: 'V1.0',
+    effectiveDate: '2026-05-20',
+    employeeHandbook: '技术员工手册',
+    content: '<h2>竞业限制协议(高级技术岗)</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方:<strong>{{员工姓名}}</strong></p>',
+    reviewer: '李亚洪',
+    createdAt: '2026-06-15 10:30',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 10,
+    name: '实习协议',
+    code: 'HT-2026-010',
+    contractType: '实习协议',
+    legalEntity: '湖南中盈科技有限公司',
+    positionName: '技术员',
+    positionSequence: '技术序列',
+    positionLevel: 'P2',
+    fileTemplate: '实习协议模版',
+    confidential: '无',
+    status: '审核中',
+    enabled: false,
+    version: 'V1.5',
+    effectiveDate: '2026-09-01',
+    employeeHandbook: '技术员工手册',
+    content: '<h2>实习协议</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方(实习生):<strong>{{员工姓名}}</strong></p><p>实习期为 <strong>{{合同起始日期}}</strong> 至 <strong>{{合同终止日期}}</strong>。</p>',
+    reviewer: '张伟',
+    createdAt: '2026-08-28 11:00',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 11,
+    name: '兼职协议',
+    code: 'HT-2026-011',
+    contractType: '兼职协议',
+    legalEntity: '常德精工机械有限公司',
+    positionName: '销售专员',
+    positionSequence: '销售序列',
+    positionLevel: 'P3',
+    fileTemplate: '兼职合同模版',
+    confidential: '包含保密条款',
+    status: '审核中',
+    enabled: false,
+    version: 'V1.0',
+    effectiveDate: '2026-09-15',
+    employeeHandbook: '销售员工手册',
+    content: '<h2>兼职协议</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方(兼职员工):<strong>{{员工姓名}}</strong></p>',
+    reviewer: '郭涛',
+    createdAt: '2026-08-30 15:20',
+    attachment: '兼职合同模版.pdf',
+    attachmentName: '兼职合同模版.pdf',
+  },
+  {
+    id: 12,
+    name: '劳务派遣合同',
+    code: 'HT-2026-012',
+    contractType: '劳务派遣合同',
+    legalEntity: '湖南云盾消防设备有限公司',
+    positionName: '操作工',
+    positionSequence: '操作序列',
+    positionLevel: 'O2',
+    fileTemplate: '派遣合同模版',
+    confidential: '无',
+    status: '启用',
+    enabled: true,
+    version: 'V2.1',
+    effectiveDate: '2026-04-20',
+    employeeHandbook: '生产员工手册',
+    content: '<h2>劳务派遣合同</h2><p>派遣方:<strong>{{公司名称}}</strong></p><p>实际用工方:<strong>{{所属部门}}</strong></p><p>派遣员工:<strong>{{员工姓名}}</strong></p>',
+    reviewer: '黄强',
+    createdAt: '2026-07-15 09:45',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 13,
+    name: '无固定期限劳动合同',
+    code: 'HT-2026-013',
+    contractType: '无固定期限劳动合同',
+    legalEntity: '中英科技有限公司',
+    positionName: '技术经理',
+    positionSequence: '管理序列',
+    positionLevel: 'M2',
+    fileTemplate: '劳动合同标准版',
+    confidential: '包含保密+竞业条款',
+    status: '启用',
+    enabled: true,
+    version: 'V3.0',
+    effectiveDate: '2026-06-01',
+    employeeHandbook: '管理层员工手册',
+    content: DEFAULT_CONTRACT_CONTENT,
+    reviewer: '李亚洪',
+    createdAt: '2026-08-01 14:00',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 14,
+    name: '培训服务协议',
+    code: 'HT-2026-014',
+    contractType: '培训服务协议',
+    legalEntity: '北京天成伟达工矿设备有限公司',
+    positionName: '技术工程师',
+    positionSequence: '技术序列',
+    positionLevel: 'P4',
+    fileTemplate: '劳动合同标准版',
+    confidential: '包含保密条款',
+    status: '草稿',
+    enabled: false,
+    version: 'V1.0',
+    effectiveDate: '',
+    employeeHandbook: '技术员工手册',
+    content: '<h2>培训服务协议</h2><p>甲方:<strong>{{公司名称}}</strong></p><p>乙方:<strong>{{员工姓名}}</strong></p>',
+    reviewer: '',
+    createdAt: '2026-08-29 10:30',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 15,
+    name: '财务岗位劳动合同',
+    code: 'HT-2026-015',
+    contractType: '固定期限劳动合同',
+    legalEntity: '长沙中盈制造有限公司',
+    positionName: '财务经理',
+    positionSequence: '职能序列',
+    positionLevel: 'M1',
+    fileTemplate: '劳动合同标准版',
+    confidential: '包含保密条款',
+    status: '启用',
+    enabled: true,
+    version: 'V2.0',
+    effectiveDate: '2026-02-20',
+    employeeHandbook: '管理层员工手册',
+    content: DEFAULT_CONTRACT_CONTENT,
+    reviewer: '陈丽',
+    createdAt: '2026-08-05 16:20',
+    attachment: '',
+    attachmentName: '',
+  },
+  {
+    id: 16,
+    name: '采购岗位劳动合同',
+    code: 'HT-2026-016',
+    contractType: '固定期限劳动合同',
+    legalEntity: '常德精工机械有限公司',
+    positionName: '采购经理',
+    positionSequence: '职能序列',
+    positionLevel: 'M1',
+    fileTemplate: '劳动合同标准版',
+    confidential: '无',
+    status: '停用',
+    enabled: false,
+    version: 'V1.5',
+    effectiveDate: '2025-12-01',
+    employeeHandbook: '管理层员工手册',
+    content: DEFAULT_CONTRACT_CONTENT,
+    reviewer: '张伟',
+    createdAt: '2025-12-15 09:00',
+    attachment: '',
+    attachmentName: '',
+  },
+];
+
+// 深拷贝工具
+export const clone = (value) => JSON.parse(JSON.stringify(value));