Преглед на файлове

feat(attendance): 统一编辑与参考弹框

xieyong преди 1 месец
родител
ревизия
4e710fa47d

+ 10 - 9
src/views/attendance/components/RuleAssistantPanel.vue

@@ -7,29 +7,29 @@
           <p>对助理管理打卡规则进行细粒度配置</p>
         </header>
         <el-form-item label="启用助理管理">
-          <el-switch v-model="form.assistantEnabled" active-text="开启" inactive-text="关闭" />
+          <el-switch v-model="form.assistantEnabled" active-text="开启" inactive-text="关闭" :disabled="readonly" />
         </el-form-item>
         <template v-if="form.assistantEnabled">
           <el-form-item label="无需打卡员工">
-            <el-select v-model="form.assistantExemptUsers" multiple collapse-tags placeholder="员工列表" style="width:100%">
+            <el-select v-model="form.assistantExemptUsers" multiple collapse-tags placeholder="员工列表" style="width:100%" :disabled="readonly">
               <el-option v-for="u in assistantUserOptions" :key="u" :label="u" :value="u" />
             </el-select>
             <span class="form-hint">无需按规则打卡,不计为异常</span>
           </el-form-item>
           <el-form-item label="时区">
-            <el-select v-model="form.timezone" placeholder="选择时区" style="width:100%">
+            <el-select v-model="form.timezone" placeholder="选择时区" style="width:100%" :disabled="readonly">
               <el-option v-for="tz in timezoneOptions" :key="tz" :label="tz" :value="tz" />
             </el-select>
             <span class="form-hint">成员将按照设置的时区进行打卡和统计,保存后不支持修改</span>
           </el-form-item>
           <div class="assistant-row">
             <el-form-item label="打卡提醒">
-              <el-select v-model="form.reminderOffset" placeholder="选择打卡提醒" style="width:100%">
+              <el-select v-model="form.reminderOffset" placeholder="选择打卡提醒" style="width:100%" :disabled="readonly">
                 <el-option v-for="r in reminderOptions" :key="r" :label="r" :value="r" />
               </el-select>
             </el-form-item>
             <el-form-item label="范围外打卡">
-              <el-select v-model="form.asstOutOfRange" placeholder="选择范围外处理" style="width:100%">
+              <el-select v-model="form.asstOutOfRange" placeholder="选择范围外处理" style="width:100%" :disabled="readonly">
                 <el-option label="记录为地点异常" value="记录为地点异常" />
                 <el-option label="允许打卡" value="允许打卡" />
                 <el-option label="不允许打卡" value="不允许打卡" />
@@ -37,7 +37,7 @@
             </el-form-item>
           </div>
           <el-form-item label="拍照与人脸识别">
-            <el-select v-model="form.faceRecognition" placeholder="选择识别方式" style="width:100%">
+            <el-select v-model="form.faceRecognition" placeholder="选择识别方式" style="width:100%" :disabled="readonly">
               <el-option label="启用" value="启用" />
               <el-option label="禁用" value="禁用" />
               <el-option label="仅异常时启用" value="仅异常时启用" />
@@ -53,10 +53,10 @@
             <p>对员工每次打卡的方式进行细粒度控制</p>
           </header>
           <el-form-item label="每次打卡均需拍照">
-            <el-switch v-model="form.photoEachPunch" />
+            <el-switch v-model="form.photoEachPunch" :disabled="readonly" />
           </el-form-item>
           <el-form-item label="每次打卡均需人脸识别">
-            <el-switch v-model="form.faceEachPunch" />
+            <el-switch v-model="form.faceEachPunch" :disabled="readonly" />
           </el-form-item>
         </section>
         <section class="form-section">
@@ -64,7 +64,7 @@
             <h3>外出打卡记录同步</h3>
           </header>
           <el-form-item label="外出打卡记录同步至上下班打卡">
-            <el-switch v-model="form.outsourceSync" />
+            <el-switch v-model="form.outsourceSync" :disabled="readonly" />
             <span class="form-hint">开启后,员工上下班期内外出打卡记录将同步至上下班中,结果显示为「外出打卡」</span>
           </el-form-item>
         </section>
@@ -81,6 +81,7 @@ export default {
     assistantUserOptions: { type: Array, default: () => [] },
     timezoneOptions: { type: Array, default: () => [] },
     reminderOptions: { type: Array, default: () => [] },
+    readonly: { type: Boolean, default: false },
   },
 };
 </script>

+ 12 - 1
src/views/attendance/components/RuleFormPanels.vue

@@ -4,6 +4,7 @@
       v-if="activeTab === 'method'"
       :form="form"
       :rules="rules"
+      :readonly="readonly"
       @add-location="$emit('add-location')"
       @add-wifi="$emit('add-wifi')"
       @add-device="$emit('add-device')"
@@ -21,19 +22,26 @@
       :week-options="weekOptions"
       :time-options="timeOptions"
       :related-target-options="relatedTargetOptions"
+      :readonly="readonly"
+    />
+    <rule-rest-panel
+      v-else-if="activeTab === 'rest'"
+      :readonly="readonly"
     />
-    <rule-rest-panel v-else-if="activeTab === 'rest'" />
     <rule-overtime-panel
       v-else-if="activeTab === 'overtime'"
       :form="form"
+      :readonly="readonly"
     />
     <rule-makeup-panel
       v-else-if="activeTab === 'makeup'"
       :form="form"
+      :readonly="readonly"
     />
     <rule-leave-panel
       v-else-if="activeTab === 'leave'"
       :form="form"
+      :readonly="readonly"
     />
     <rule-assistant-panel
       v-else-if="activeTab === 'assistant'"
@@ -41,10 +49,12 @@
       :assistant-user-options="assistantUserOptions"
       :timezone-options="timezoneOptions"
       :reminder-options="reminderOptions"
+      :readonly="readonly"
     />
     <rule-outside-panel
       v-else-if="activeTab === 'outside'"
       :form="form"
+      :readonly="readonly"
     />
   </div>
 </template>
@@ -81,6 +91,7 @@ export default {
     timezoneOptions: { type: Array, default: () => [] },
     reminderOptions: { type: Array, default: () => [] },
     rules: { type: Object, default: () => ({}) },
+    readonly: { type: Boolean, default: false },
   },
 };
 </script>

+ 4 - 3
src/views/attendance/components/RuleLeavePanel.vue

@@ -7,10 +7,10 @@
           <p>员工请假离岗和返岗时是否需要打卡</p>
         </header>
         <el-form-item label="请假时离岗/返岗需打卡">
-          <el-switch v-model="form.leaveNeedPunch" active-text="开启" inactive-text="关闭" />
+          <el-switch v-model="form.leaveNeedPunch" active-text="开启" inactive-text="关闭" :disabled="readonly" />
         </el-form-item>
         <el-form-item label="打卡时间">
-          <el-select v-model="form.leavePunchWindow" placeholder="选择打卡时间" style="width:240px">
+          <el-select v-model="form.leavePunchWindow" placeholder="选择打卡时间" style="width:240px" :disabled="readonly">
             <el-option label="不限制" value="不限制" />
             <el-option label="上下班前后 30 分钟" value="上下班前后 30 分钟" />
             <el-option label="上下班前后 1 小时" value="上下班前后 1 小时" />
@@ -48,7 +48,7 @@
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="操作" width="90" align="center" fixed="right">
+          <el-table-column v-if="!readonly" label="操作" width="90" align="center" fixed="right">
             <template slot-scope="{ row, $index }">
               <el-button type="text" size="mini" :class="row.action === '已归档' ? 'is-archived' : ''" @click="onLeaveRuleAction(row, $index)">{{ row.action }}</el-button>
             </template>
@@ -64,6 +64,7 @@ export default {
   name: 'RuleLeavePanel',
   props: {
     form: { type: Object, required: true },
+    readonly: { type: Boolean, default: false },
   },
   methods: {
     onLeaveRuleAction(row, index) {

+ 9 - 8
src/views/attendance/components/RuleMakeupPanel.vue

@@ -7,14 +7,14 @@
           <p>员工异常打卡时可提交申请,审批通过后修正异常</p>
         </header>
         <el-form-item label="补卡规则">
-          <el-switch v-model="form.makeupEnabled" active-text="启用" inactive-text="关闭" />
+          <el-switch v-model="form.makeupEnabled" active-text="启用" inactive-text="关闭" :disabled="readonly" />
         </el-form-item>
         <template v-if="form.makeupEnabled">
           <el-form-item label="允许提交补卡申请">
             <span class="field-hint">员工异常打卡时可提交申请,审批通过后修正异常</span>
           </el-form-item>
           <el-form-item label="补卡类型">
-            <el-select v-model="form.makeupType" placeholder="选择补卡类型" style="width:100%">
+            <el-select v-model="form.makeupType" placeholder="选择补卡类型" style="width:100%" :disabled="readonly">
               <el-option label="缺卡/旷工" value="缺卡/旷工" />
               <el-option label="迟到/早退" value="迟到/早退" />
               <el-option label="其他异常" value="其他异常" />
@@ -23,7 +23,7 @@
           </el-form-item>
           <div class="makeup-row">
             <el-form-item label="允许补卡时间限制">
-              <el-select v-model="form.makeupTimeLimit" placeholder="选择时间限制" style="width:100%">
+              <el-select v-model="form.makeupTimeLimit" placeholder="选择时间限制" style="width:100%" :disabled="readonly">
                 <el-option label="不限制" value="不限制" />
                 <el-option label="异常后 1 天内" value="异常后 1 天内" />
                 <el-option label="异常后 3 天内" value="异常后 3 天内" />
@@ -32,17 +32,17 @@
               </el-select>
             </el-form-item>
             <el-form-item label="补卡提醒">
-              <el-select v-model="form.makeupReminder" placeholder="选择提醒" style="width:120px">
+              <el-select v-model="form.makeupReminder" placeholder="选择提醒" style="width:120px" :disabled="readonly">
                 <el-option label="启用" value="启用" />
                 <el-option label="禁用" value="禁用" />
               </el-select>
-              <el-time-picker v-if="form.makeupReminder === '启用'" v-model="form.makeupReminderTime" placeholder="提醒时间" value-format="HH:mm" format="HH:mm" style="width:140px" />
+              <el-time-picker v-if="form.makeupReminder === '启用'" v-model="form.makeupReminderTime" placeholder="提醒时间" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
               <span class="form-hint">将予设定时间提醒成员补卡</span>
             </el-form-item>
           </div>
           <div class="makeup-row">
             <el-form-item label="每月允许补卡次数">
-              <el-select v-model="form.makeupMaxPerMonth" placeholder="选择次数" style="width:100%">
+              <el-select v-model="form.makeupMaxPerMonth" placeholder="选择次数" style="width:100%" :disabled="readonly">
                 <el-option label="不限" value="不限" />
                 <el-option label="1次" value="1次" />
                 <el-option label="2次" value="2次" />
@@ -51,7 +51,7 @@
               </el-select>
             </el-form-item>
             <el-form-item label="每月补卡截止日期">
-              <el-select v-model="form.makeupDeadline" placeholder="选择截止日期" style="width:100%">
+              <el-select v-model="form.makeupDeadline" placeholder="选择截止日期" style="width:100%" :disabled="readonly">
                 <el-option label="不设置" value="不设置" />
                 <el-option label="当月最后一天" value="当月最后一天" />
                 <el-option label="次月 5 日" value="次月 5 日" />
@@ -70,7 +70,7 @@
             <p>定位不准等原因无法打卡时,可提交审批打卡</p>
           </header>
           <el-form-item label="允许提交审批打卡申请">
-            <el-switch v-model="form.approvalPunchEnabled" active-text="启用" inactive-text="关闭" />
+            <el-switch v-model="form.approvalPunchEnabled" active-text="启用" inactive-text="关闭" :disabled="readonly" />
             <span class="form-hint">定位不准等原因无法打卡时,可提交审批打卡</span>
           </el-form-item>
         </section>
@@ -84,6 +84,7 @@ export default {
   name: 'RuleMakeupPanel',
   props: {
     form: { type: Object, required: true },
+    readonly: { type: Boolean, default: false },
   },
 };
 </script>

+ 17 - 14
src/views/attendance/components/RuleMethodPanel.vue

@@ -7,7 +7,7 @@
           <p>手机和考勤机满足任意一项即可打卡</p>
         </header>
         <el-form-item label="选择方式" prop="method">
-          <el-radio-group v-model="form.method" class="method-radio">
+          <el-radio-group v-model="form.method" class="method-radio" :disabled="readonly">
             <el-radio label="phone">手机</el-radio>
             <el-radio label="device">考勤机</el-radio>
             <el-radio label="phone-device">手机+考勤机</el-radio>
@@ -19,11 +19,11 @@
           <h3>打卡位置</h3>
           <p>设置打卡地点范围(GPS / 客户现场)</p>
         </header>
-        <el-button size="small" icon="el-icon-plus" @click="$emit('add-location')">添加</el-button>
+        <el-button v-if="!readonly" size="small" icon="el-icon-plus" @click="$emit('add-location')">添加</el-button>
         <el-table :data="form.locations" class="location-table" size="small" empty-text="暂未添加打卡位置">
           <el-table-column prop="name" label="地点名称" minWidth="220" />
           <el-table-column prop="range" label="有效范围" width="160" />
-          <el-table-column label="操作" width="80" align="center">
+          <el-table-column v-if="!readonly" label="操作" width="80" align="center">
             <template slot-scope="{ row, $index }">
               <el-button type="text" size="mini" class="is-danger"
                 @click="form.locations.splice($index, 1)">删除</el-button>
@@ -31,7 +31,7 @@
           </el-table-column>
         </el-table>
         <el-form-item label="范围外打卡" class="range-form-item">
-          <el-select v-model="form.outOfRange" placeholder="选择范围外打卡策略" style="width:240px">
+          <el-select v-model="form.outOfRange" placeholder="选择范围外打卡策略" style="width:240px" :disabled="readonly">
             <el-option label="记录为地点异常" value="异常" />
             <el-option label="允许打卡" value="允许" />
             <el-option label="不允许打卡" value="不允许" />
@@ -43,11 +43,11 @@
           <h3>打卡Wi-Fi</h3>
           <p>绑定办公网络 Wi-Fi</p>
         </header>
-        <el-button size="small" icon="el-icon-plus" @click="$emit('add-wifi')">添加</el-button>
+        <el-button v-if="!readonly" size="small" icon="el-icon-plus" @click="$emit('add-wifi')">添加</el-button>
         <el-table :data="form.wifis" class="location-table" size="small" empty-text="暂未添加打卡Wi-Fi">
           <el-table-column prop="ssid" label="WiFi 名称" minWidth="200" />
           <el-table-column prop="bssid" label="MAC 地址" minWidth="200" />
-          <el-table-column label="操作" width="80" align="center">
+          <el-table-column v-if="!readonly" label="操作" width="80" align="center">
             <template slot-scope="{ row, $index }">
               <el-button type="text" size="mini" class="is-danger"
                 @click="form.wifis.splice($index, 1)">删除</el-button>
@@ -70,7 +70,7 @@
           <h3>考勤设备</h3>
           <p>添加并管理用于打卡的考勤机设备</p>
         </header>
-        <el-button size="small" icon="el-icon-plus" @click="$emit('add-device')">添加考勤机</el-button>
+        <el-button v-if="!readonly" size="small" icon="el-icon-plus" @click="$emit('add-device')">添加考勤机</el-button>
         <el-table :data="form.devices" class="location-table device-table" size="small"
           empty-text="暂未添加考勤设备,点击右上角“添加考勤机”按钮添加">
           <el-table-column prop="name" label="设备名称" minWidth="120" />
@@ -90,13 +90,15 @@
           <el-table-column label="操作" minWidth="280" align="left">
             <template slot-scope="{ row, $index }">
               <el-button type="text" size="mini" @click="$emit('device-view', row)">查看详情</el-button>
-              <el-button type="text" size="mini" @click="$emit('device-edit', row)">编辑</el-button>
-              <el-button type="text" size="mini" @click="$emit('device-test', row)">连接测试</el-button>
-              <el-button type="text" size="mini" @click="$emit('device-sync', row)">同步数据</el-button>
-              <el-button type="text" size="mini" @click="$emit('device-restart', row)">重启设备</el-button>
-              <el-button type="text" size="mini" class="is-danger"
-                @click="$emit('device-delete', row)">删除</el-button>
-              <el-button type="text" size="mini" @click="$emit('device-log', row)">日志</el-button>
+              <template v-if="!readonly">
+                <el-button type="text" size="mini" @click="$emit('device-edit', row)">编辑</el-button>
+                <el-button type="text" size="mini" @click="$emit('device-test', row)">连接测试</el-button>
+                <el-button type="text" size="mini" @click="$emit('device-sync', row)">同步数据</el-button>
+                <el-button type="text" size="mini" @click="$emit('device-restart', row)">重启设备</el-button>
+                <el-button type="text" size="mini" class="is-danger"
+                  @click="$emit('device-delete', row)">删除</el-button>
+                <el-button type="text" size="mini" @click="$emit('device-log', row)">日志</el-button>
+              </template>
             </template>
           </el-table-column>
         </el-table>
@@ -111,6 +113,7 @@ export default {
   props: {
     form: { type: Object, required: true },
     rules: { type: Object, default: () => ({}) },
+    readonly: { type: Boolean, default: false },
   },
 };
 </script>

+ 9 - 8
src/views/attendance/components/RuleOutsidePanel.vue

@@ -7,11 +7,11 @@
           <p>外勤 / 客户拜访现场打卡</p>
         </header>
         <el-form-item label="启用外出打卡">
-          <el-switch v-model="form.outsideEnabled" active-text="开启" inactive-text="关闭" />
+          <el-switch v-model="form.outsideEnabled" active-text="开启" inactive-text="关闭" :disabled="readonly" />
         </el-form-item>
         <template v-if="form.outsideEnabled">
           <el-form-item label="外出打卡方式" prop="outsideMode">
-            <el-radio-group v-model="form.outsideMode" class="outside-mode">
+            <el-radio-group v-model="form.outsideMode" class="outside-mode" :disabled="readonly">
               <el-radio label="time">上班打卡时间</el-radio>
               <el-radio label="location">定位打卡</el-radio>
             </el-radio-group>
@@ -19,20 +19,20 @@
           <div v-if="form.outsideMode === 'time'" class="outside-subfield">
             <span class="outside-subfield-label">打卡时段</span>
             <div class="time-pair">
-              <el-time-picker v-model="form.outsideStart" placeholder="开始时间" value-format="HH:mm" format="HH:mm" style="width:140px" />
+              <el-time-picker v-model="form.outsideStart" placeholder="开始时间" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
               <span class="dash">—</span>
-              <el-time-picker v-model="form.outsideEnd" placeholder="结束时间" value-format="HH:mm" format="HH:mm" style="width:140px" />
+              <el-time-picker v-model="form.outsideEnd" placeholder="结束时间" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
             </div>
           </div>
           <div v-else-if="form.outsideMode === 'location'" class="outside-subfield outside-subfield--row">
             <span class="outside-subfield-label">定位打卡</span>
-            <el-select v-model="form.outsideAddress" placeholder="不限制定打卡地址" style="width:240px">
+            <el-select v-model="form.outsideAddress" placeholder="不限制定打卡地址" style="width:240px" :disabled="readonly">
               <el-option label="不限制定打卡地址" value="不限制" />
               <el-option label="仅限指定地址" value="限制定" />
               <el-option label="限制地理围栏" value="围栏" />
             </el-select>
             <span class="outside-photo-label">是否需要拍照</span>
-            <el-switch v-model="form.outsidePhoto" active-text="开启" inactive-text="关闭" />
+            <el-switch v-model="form.outsidePhoto" active-text="开启" inactive-text="关闭" :disabled="readonly" />
           </div>
         </template>
       </section>
@@ -43,9 +43,9 @@
         </header>
         <el-form-item label="汇报提醒时间">
           <div class="time-pair">
-            <el-time-picker v-model="form.reportStart" placeholder="开始时间" value-format="HH:mm" format="HH:mm" style="width:140px" />
+            <el-time-picker v-model="form.reportStart" placeholder="开始时间" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
             <span class="dash">—</span>
-            <el-time-picker v-model="form.reportEnd" placeholder="结束时间" value-format="HH:mm" format="HH:mm" style="width:140px" />
+            <el-time-picker v-model="form.reportEnd" placeholder="结束时间" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
           </div>
         </el-form-item>
       </section>
@@ -58,6 +58,7 @@ export default {
   name: 'RuleOutsidePanel',
   props: {
     form: { type: Object, required: true },
+    readonly: { type: Boolean, default: false },
   },
 };
 </script>

+ 20 - 19
src/views/attendance/components/RuleOvertimePanel.vue

@@ -8,13 +8,13 @@
         </header>
         <div class="overtime-row">
           <el-form-item label="加班时长单位">
-            <el-select v-model="form.overtimeUnit" placeholder="选择单位" style="width:100%">
+            <el-select v-model="form.overtimeUnit" placeholder="选择单位" style="width:100%" :disabled="readonly">
               <el-option label="小时" value="小时" />
               <el-option label="天" value="天" />
             </el-select>
           </el-form-item>
           <el-form-item label="加班时长取整方式">
-            <el-select v-model="form.overtimeRound" placeholder="选择取整" style="width:100%">
+            <el-select v-model="form.overtimeRound" placeholder="选择取整" style="width:100%" :disabled="readonly">
               <el-option label="四舍五入" value="四舍五入" />
               <el-option label="向上取整" value="向上取整" />
               <el-option label="向下取整" value="向下取整" />
@@ -22,7 +22,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="保留小数至">
-            <el-select v-model="form.overtimeDecimal" placeholder="选择小数位" style="width:100%">
+            <el-select v-model="form.overtimeDecimal" placeholder="选择小数位" style="width:100%" :disabled="readonly">
               <el-option label="0位整数" value="0位整数" />
               <el-option label="1位小数" value="1位小数" />
               <el-option label="2位小数" value="2位小数" />
@@ -30,7 +30,7 @@
           </el-form-item>
         </div>
         <el-form-item label="加班单位换算按 1 天 =">
-          <el-input-number v-model="form.overtimeDayHours" :min="1" :max="24" :step="1" style="width:120px" />
+          <el-input-number v-model="form.overtimeDayHours" :min="1" :max="24" :step="1" style="width:120px" :disabled="readonly" />
           <span class="form-hint">小时</span>
         </el-form-item>
       </section>
@@ -42,11 +42,11 @@
         </header>
         <div class="overtime-period-row">
           <el-form-item label="允许加班">
-            <el-switch v-model="form.workOvertimeEnabled" />
+            <el-switch v-model="form.workOvertimeEnabled" :disabled="readonly" />
           </el-form-item>
           <el-form-item label="加班时段" v-if="form.workOvertimeEnabled">
             <span class="period-label">允许加班的时段</span>
-            <el-select v-model="form.workOvertimePeriod" placeholder="时间段选择" style="width:160px">
+            <el-select v-model="form.workOvertimePeriod" placeholder="时间段选择" style="width:160px" :disabled="readonly">
               <el-option label="下班后到次日上班前" value="下班后到次日上班前" />
               <el-option label="午休时段" value="午休时段" />
               <el-option label="所有非工作时间" value="所有非工作时间" />
@@ -55,7 +55,7 @@
         </div>
         <template v-if="form.workOvertimeEnabled">
           <el-form-item label="加班时长计算方式">
-            <el-select v-model="form.workCalcMethod" placeholder="选择计算方式" style="width:200px">
+            <el-select v-model="form.workCalcMethod" placeholder="选择计算方式" style="width:200px" :disabled="readonly">
               <el-option label="按打卡时长计算" value="按打卡时长计算" />
               <el-option label="按审批时长计算" value="按审批时长计算" />
             </el-select>
@@ -63,10 +63,10 @@
           <div class="overtime-section-subtitle">计算规则</div>
           <div class="overtime-toggle-row">
             <el-form-item label="加班扣除休息时间">
-              <el-switch v-model="form.workDeductRest" />
+              <el-switch v-model="form.workDeductRest" :disabled="readonly" />
             </el-form-item>
             <el-form-item label="加班时长计算调休或加班费">
-              <el-switch v-model="form.workCompOrPay" />
+              <el-switch v-model="form.workCompOrPay" :disabled="readonly" />
             </el-form-item>
           </div>
         </template>
@@ -79,11 +79,11 @@
         </header>
         <div class="overtime-period-row">
           <el-form-item label="允许加班">
-            <el-switch v-model="form.restOvertimeEnabled" />
+            <el-switch v-model="form.restOvertimeEnabled" :disabled="readonly" />
           </el-form-item>
           <el-form-item label="加班时段" v-if="form.restOvertimeEnabled">
             <span class="period-label">允许加班的时段</span>
-            <el-select v-model="form.restOvertimePeriod" placeholder="时间段选择" style="width:160px">
+            <el-select v-model="form.restOvertimePeriod" placeholder="时间段选择" style="width:160px" :disabled="readonly">
               <el-option label="全天" value="全天" />
               <el-option label="工作时段外" value="工作时段外" />
               <el-option label="所有时段" value="所有时段" />
@@ -92,7 +92,7 @@
         </div>
         <template v-if="form.restOvertimeEnabled">
           <el-form-item label="加班时长计算方式">
-            <el-select v-model="form.restCalcMethod" placeholder="选择计算方式" style="width:200px">
+            <el-select v-model="form.restCalcMethod" placeholder="选择计算方式" style="width:200px" :disabled="readonly">
               <el-option label="按打卡时长计算" value="按打卡时长计算" />
               <el-option label="按审批时长计算" value="按审批时长计算" />
             </el-select>
@@ -100,10 +100,10 @@
           <div class="overtime-section-subtitle">计算规则</div>
           <div class="overtime-toggle-row">
             <el-form-item label="加班扣除休息时间">
-              <el-switch v-model="form.restDeductRest" />
+              <el-switch v-model="form.restDeductRest" :disabled="readonly" />
             </el-form-item>
             <el-form-item label="加班时长计算调休或加班费">
-              <el-switch v-model="form.restCompOrPay" />
+              <el-switch v-model="form.restCompOrPay" :disabled="readonly" />
             </el-form-item>
           </div>
         </template>
@@ -116,11 +116,11 @@
         </header>
         <div class="overtime-period-row">
           <el-form-item label="允许加班">
-            <el-switch v-model="form.holidayOvertimeEnabled" />
+            <el-switch v-model="form.holidayOvertimeEnabled" :disabled="readonly" />
           </el-form-item>
           <el-form-item label="加班时段" v-if="form.holidayOvertimeEnabled">
             <span class="period-label">允许加班的时段</span>
-            <el-select v-model="form.holidayOvertimePeriod" placeholder="时间段选择" style="width:160px">
+            <el-select v-model="form.holidayOvertimePeriod" placeholder="时间段选择" style="width:160px" :disabled="readonly">
               <el-option label="全天" value="全天" />
               <el-option label="工作时段外" value="工作时段外" />
               <el-option label="所有时段" value="所有时段" />
@@ -129,7 +129,7 @@
         </div>
         <template v-if="form.holidayOvertimeEnabled">
           <el-form-item label="加班时长计算方式">
-            <el-select v-model="form.holidayCalcMethod" placeholder="选择计算方式" style="width:200px">
+            <el-select v-model="form.holidayCalcMethod" placeholder="选择计算方式" style="width:200px" :disabled="readonly">
               <el-option label="按打卡时长计算" value="按打卡时长计算" />
               <el-option label="按审批时长计算" value="按审批时长计算" />
             </el-select>
@@ -137,10 +137,10 @@
           <div class="overtime-section-subtitle">计算规则</div>
           <div class="overtime-toggle-row">
             <el-form-item label="加班扣除休息时间">
-              <el-switch v-model="form.holidayDeductRest" />
+              <el-switch v-model="form.holidayDeductRest" :disabled="readonly" />
             </el-form-item>
             <el-form-item label="加班时长计算调休或加班费">
-              <el-switch v-model="form.holidayCompOrPay" />
+              <el-switch v-model="form.holidayCompOrPay" :disabled="readonly" />
             </el-form-item>
           </div>
         </template>
@@ -154,6 +154,7 @@ export default {
   name: 'RuleOvertimePanel',
   props: {
     form: { type: Object, required: true },
+    readonly: { type: Boolean, default: false },
   },
 };
 </script>

+ 25 - 6
src/views/attendance/components/RuleRestPanel.vue

@@ -17,12 +17,12 @@
             <el-input v-model="form.calendarCode" disabled />
           </el-form-item>
           <el-form-item label="日历名称" prop="calendarName">
-            <el-input v-model="form.calendarName" maxlength="128" />
+            <el-input v-model="form.calendarName" maxlength="128" :disabled="readonly" />
           </el-form-item>
           <el-form-item label="日历类型" prop="calendarType">
             <el-select
               v-model="form.calendarType"
-              :disabled="!!form.id"
+              :disabled="readonly || !!form.id"
               style="width: 100%"
             >
               <el-option
@@ -34,7 +34,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="适用年份" prop="applyYear">
-            <el-select v-model="formApplyYear" style="width: 100%">
+            <el-select v-model="formApplyYear" style="width: 100%" :disabled="readonly">
               <el-option
                 v-for="year in yearOptions"
                 :key="year"
@@ -48,6 +48,7 @@
               v-model="form.applyMonth"
               multiple
               style="width: 100%"
+              :disabled="readonly"
               @change="handleApplyMonthChange"
             >
               <el-option
@@ -65,6 +66,7 @@
               :inactive-value="0"
               active-text="启用"
               inactive-text="禁用"
+              :disabled="readonly"
             />
           </el-form-item>
         </div>
@@ -77,7 +79,7 @@
           <p>选择按周休模式,自定义模式下可勾选任意休息星期</p>
         </header>
         <div class="rest-rule-editor">
-          <el-radio-group v-model="form.restMode">
+          <el-radio-group v-model="form.restMode" :disabled="readonly">
             <el-radio-button
               v-for="item in restModeOptions"
               :key="item.value"
@@ -90,6 +92,7 @@
             v-if="form.restMode === 'custom'"
             v-model="form.restWeekdays"
             class="custom-rest-weekdays"
+            :disabled="readonly"
           >
             <el-checkbox-button
               v-for="item in restWeekdayOptions"
@@ -133,8 +136,10 @@
                 :class="{
                   'is-work': (form.legalHolidayWorkDates || []).includes(
                     item.calendarDate
-                  )
+                  ),
+                  'is-disabled': readonly
                 }"
+                :disabled="readonly"
                 @click="toggleLegalHolidayWorkStatus(item.calendarDate)"
               >
                 <span>{{ formatHolidayDate(item.calendarDate) }}</span>
@@ -171,11 +176,13 @@
               value-format="yyyy-MM-dd"
               placeholder="选择日期"
               style="width: 160px"
+              :disabled="readonly"
             />
             <el-select
               v-model="rule.dateType"
               placeholder="日期类型"
               style="width: 130px"
+              :disabled="readonly"
             >
               <el-option label="上班" :value="1" />
               <el-option label="休息" :value="2" />
@@ -186,8 +193,10 @@
               placeholder="备注"
               maxlength="80"
               style="flex: 1"
+              :disabled="readonly"
             />
             <el-button
+              v-if="!readonly"
               type="text"
               icon="el-icon-delete"
               class="danger-link"
@@ -196,7 +205,7 @@
               删除
             </el-button>
           </div>
-          <el-button size="small" icon="el-icon-plus" @click="addHolidayRule">
+          <el-button v-if="!readonly" size="small" icon="el-icon-plus" @click="addHolidayRule">
             新增特殊日期
           </el-button>
         </div>
@@ -215,6 +224,7 @@
             :rows="3"
             maxlength="500"
             show-word-limit
+            :disabled="readonly"
           />
         </el-form-item>
       </section>
@@ -420,6 +430,11 @@
       existingCalendars: {
         type: Array,
         default: () => []
+      },
+      // 只读模式(查看场景下使用,禁用所有交互)
+      readonly: {
+        type: Boolean,
+        default: false
       }
     },
     data() {
@@ -1195,6 +1210,10 @@
   .rule-rest-panel .legal-holiday-date.is-work em {
     color: #409eff;
   }
+  .rule-rest-panel .legal-holiday-date.is-disabled {
+    cursor: not-allowed;
+    opacity: 0.7;
+  }
   .rule-rest-panel .form-section header .form-hint {
     color: #909399;
     font-size: 12px;

+ 17 - 16
src/views/attendance/components/RuleWorkTimePanel.vue

@@ -7,7 +7,7 @@
           <p>选择需要打卡的工作日</p>
         </header>
         <div class="week-chip-wrap">
-          <el-checkbox-group v-model="form.workDays" class="week-chips">
+          <el-checkbox-group v-model="form.workDays" class="week-chips" :disabled="readonly">
             <el-checkbox-button v-for="d in weekOptions" :key="d" :label="d">{{ d }}</el-checkbox-button>
           </el-checkbox-group>
         </div>
@@ -22,15 +22,15 @@
           <header class="shift-head">
             <span class="shift-no"><i>{{ si + 1 }}</i></span>
             <div class="shift-head-actions">
-              <el-button v-if="form.workShifts.length > 1" type="text" size="mini" class="is-danger" @click="removeWorkShift(si)">删除该时段</el-button>
+              <el-button v-if="!readonly && form.workShifts.length > 1" type="text" size="mini" class="is-danger" @click="removeWorkShift(si)">删除该时段</el-button>
             </div>
           </header>
 
           <div class="form-grid">
             <el-form-item label="上班时间">
               <div class="field-with-dropdown">
-                <el-time-picker v-model="shift.start" placeholder="上班时间" value-format="HH:mm" format="HH:mm" class="time-pick" />
-                <el-select v-model="shift.startOpen" class="open-select">
+                <el-time-picker v-model="shift.start" placeholder="上班时间" value-format="HH:mm" format="HH:mm" class="time-pick" :disabled="readonly" />
+                <el-select v-model="shift.startOpen" class="open-select" :disabled="readonly">
                   <el-option label="需要打卡" value="是" />
                   <el-option label="无需打卡" value="否" />
                 </el-select>
@@ -38,8 +38,8 @@
             </el-form-item>
             <el-form-item label="下班时间">
               <div class="field-with-dropdown">
-                <el-time-picker v-model="shift.end" placeholder="下班时间" value-format="HH:mm" format="HH:mm" class="time-pick" />
-                <el-select v-model="shift.endOpen" class="open-select">
+                <el-time-picker v-model="shift.end" placeholder="下班时间" value-format="HH:mm" format="HH:mm" class="time-pick" :disabled="readonly" />
+                <el-select v-model="shift.endOpen" class="open-select" :disabled="readonly">
                   <el-option label="需要打卡" value="是" />
                   <el-option label="无需打卡" value="否" />
                 </el-select>
@@ -50,14 +50,14 @@
           <div class="lunch-block">
             <div class="lunch-title">午休时间</div>
             <div class="lunch-row">
-              <el-time-picker v-model="shift.lunchBreaks[0].start" placeholder="开始" value-format="HH:mm" format="HH:mm" style="width:140px" />
+              <el-time-picker v-model="shift.lunchBreaks[0].start" placeholder="开始" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
               <span class="dash">至</span>
-              <el-time-picker v-model="shift.lunchBreaks[0].end" placeholder="结束" value-format="HH:mm" format="HH:mm" style="width:140px" />
+              <el-time-picker v-model="shift.lunchBreaks[0].end" placeholder="结束" value-format="HH:mm" format="HH:mm" style="width:140px" :disabled="readonly" />
             </div>
           </div>
         </div>
 
-        <el-button plain icon="el-icon-plus" class="add-shift-btn" native-type="button" @click="addWorkShift">添加工作时间段</el-button>
+        <el-button v-if="!readonly" plain icon="el-icon-plus" class="add-shift-btn" native-type="button" @click="addWorkShift">添加工作时间段</el-button>
       </section>
 
       <section class="form-section">
@@ -67,17 +67,17 @@
         </header>
         <div class="form-grid">
           <el-form-item label="关联打开对象">
-            <el-select v-model="form.relatedTarget" placeholder="选择关联对象" style="width:100%">
+            <el-select v-model="form.relatedTarget" placeholder="选择关联对象" style="width:100%" :disabled="readonly">
               <el-option v-for="t in relatedTargetOptions" :key="t.value" :label="t.label" :value="t.value" />
             </el-select>
           </el-form-item>
           <el-form-item label="可打卡时间段">
             <div class="time-pair">
-              <el-select v-model="form.punchStart" placeholder="开始时间" style="width:120px">
+              <el-select v-model="form.punchStart" placeholder="开始时间" style="width:120px" :disabled="readonly">
                 <el-option v-for="t in timeOptions" :key="t" :label="t" :value="t" />
               </el-select>
               <span class="dash">至</span>
-              <el-select v-model="form.punchEnd" placeholder="结束时间" style="width:120px">
+              <el-select v-model="form.punchEnd" placeholder="结束时间" style="width:120px" :disabled="readonly">
                 <el-option v-for="t in timeOptions" :key="t" :label="t" :value="t" />
               </el-select>
             </div>
@@ -91,10 +91,10 @@
           <p>为员工提供弹性上下班时间</p>
         </header>
         <el-form-item label="是否允许弹性打卡">
-          <el-switch v-model="form.flexEnabled" active-text="开启" inactive-text="关闭" />
+          <el-switch v-model="form.flexEnabled" active-text="开启" inactive-text="关闭" :disabled="readonly" />
         </el-form-item>
         <el-form-item v-if="form.flexEnabled" label="下班晚走,次日可晚到">
-          <el-switch v-model="form.lateComp" active-text="开启" inactive-text="关闭" />
+          <el-switch v-model="form.lateComp" active-text="开启" inactive-text="关闭" :disabled="readonly" />
           <span class="field-hint">开启后,当日下班晚走,次日上班可最晚延后相同时长</span>
         </el-form-item>
       </section>
@@ -106,11 +106,11 @@
         </header>
         <el-form-item label="半天工作时间">
           <div class="time-pair">
-            <el-select v-model="form.halfStart" placeholder="开始时间" style="width:120px">
+            <el-select v-model="form.halfStart" placeholder="开始时间" style="width:120px" :disabled="readonly">
               <el-option v-for="t in timeOptions" :key="t" :label="t" :value="t" />
             </el-select>
             <span class="dash">至</span>
-            <el-select v-model="form.halfEnd" placeholder="结束时间" style="width:120px">
+            <el-select v-model="form.halfEnd" placeholder="结束时间" style="width:120px" :disabled="readonly">
               <el-option v-for="t in timeOptions" :key="t" :label="t" :value="t" />
             </el-select>
           </div>
@@ -128,6 +128,7 @@ export default {
     weekOptions: { type: Array, default: () => [] },
     timeOptions: { type: Array, default: () => [] },
     relatedTargetOptions: { type: Array, default: () => [] },
+    readonly: { type: Boolean, default: false },
   },
   methods: {
     addWorkShift() {

+ 36 - 90
src/views/attendance/rule/index.vue

@@ -68,8 +68,8 @@
       <div class="dialog-intro">
         <div class="intro-icon"><i class="el-icon-time"></i></div>
         <div>
-          <strong>{{ dialogTitle === '编辑打卡规则' ? '维护打卡规则' : '创建新的打卡规则' }}</strong>
-          <p>配置打卡方式、上下班时间、节假日、加班与补卡等规则,保存后同步到考勤策略。</p>
+          <strong>{{ dialogIntroTitle }}</strong>
+          <p>{{ dialogIntroDesc }}</p>
         </div>
       </div>
       <div class="rule-dialog-tabs">
@@ -87,6 +87,7 @@
           :timezone-options="timezoneOptions"
           :reminder-options="reminderOptions"
           :rules="rules"
+          :readonly="dialogReadonly"
           @add-location="addLocation"
           @add-wifi="addWifi"
           @add-device="openDeviceCard"
@@ -100,8 +101,8 @@
         />
       </div>
       <span slot="footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="save">保存规则</el-button>
+        <el-button @click="dialogVisible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+        <el-button v-if="!dialogReadonly" type="primary" @click="save">保存规则</el-button>
       </span>
     </ele-modal>
 
@@ -204,33 +205,6 @@
         <el-button @click="viewDialogVisible = false">关闭</el-button>
       </span>
     </ele-modal>
-
-    <el-drawer custom-class="rule-drawer" :title="detailRow ? '打卡规则详情 - ' + detailRow.name : ''" :visible.sync="detailVisible" direction="rtl"
-      size="640px" append-to-body>
-      <div v-if="detailRow" style="padding:0 18px 18px;">
-        <div class="employee-summary">
-          <span><i class="el-icon-time"></i></span>
-          <div>
-            <h3>{{ detailRow.name }}</h3>
-            <p>{{ detailRow.id }} · {{ scopeLabel(detailRow.scope) }} · {{ ruleTypeLabel(detailRow.type) }}</p>
-          </div>
-        </div>
-        <div class="info-grid info-grid--two">
-          <div><span>上下班时间</span><strong>{{ detailRow.workTime }}</strong></div>
-          <div><span>节假日与特殊日期</span><strong>{{ detailRow.holiday }}</strong></div>
-          <div><span>休息日</span><strong>{{ detailRow.restDays.join("、") }}</strong></div>
-          <div><span>打卡方式</span><strong>{{ detailRow.methods.map(methodLabel).join("、") }}</strong></div>
-          <div><span>打卡位置</span><strong>{{ detailRow.location }}</strong></div>
-          <div><span>打卡WiFi</span><strong>{{ detailRow.wifi }}</strong></div>
-          <div><span>考勤机</span><strong>{{ detailRow.devices }}</strong></div>
-          <div><span>加班规则</span><strong>{{ detailRow.overtime }}</strong></div>
-          <div><span>加班时长设置</span><strong>{{ detailRow.overtimeDuration }}</strong></div>
-          <div><span>补卡设置</span><strong>{{ detailRow.makeup }}</strong></div>
-          <div><span>请假时补卡设置</span><strong>{{ detailRow.leaveMakeup }}</strong></div>
-          <div><span>最近更新</span><strong>{{ detailRow.updatedAt }}</strong></div>
-        </div>
-      </div>
-    </el-drawer>
   </main>
 </template>
 
@@ -249,8 +223,6 @@ export default {
       ruleTypes: RULE_TYPES,
       filter: { keyword: '', method: '', scope: '', type: '' },
       records: PUNCH_RULES.map(r => ({ ...r })),
-      detailRow: null,
-      detailVisible: false,
       locationDialogVisible: false,
       wifiDialogVisible: false,
       locationForm: { name: '', range: '300米' },
@@ -274,6 +246,7 @@ export default {
       departmentOptions: ['总部', '研发中心', '市场部', '运营部', '人事行政部', '财务部'],
       dialogVisible: false,
       dialogTitle: '新增打卡规则',
+      dialogReadonly: false,
       activeTab: 'method',
       dialogTabs: [
         { key: 'method', label: '打卡方式' },
@@ -361,6 +334,14 @@ export default {
     enabledCount() {
       return this.records.filter(r => r.enabled).length;
     },
+    dialogIntroTitle() {
+      if (this.dialogReadonly) return '查看打卡规则详情';
+      return this.dialogTitle === '编辑打卡规则' ? '维护打卡规则' : '创建新的打卡规则';
+    },
+    dialogIntroDesc() {
+      if (this.dialogReadonly) return '查看打卡方式、上下班时间、节假日、加班与补卡等规则配置。';
+      return '配置打卡方式、上下班时间、节假日、加班与补卡等规则,保存后同步到考勤策略。';
+    },
   },
   methods: {
     ruleTypeLabel,
@@ -477,6 +458,7 @@ export default {
       this.form = this.empty();
       this.activeTab = 'method';
       this.dialogTitle = '新增打卡规则';
+      this.dialogReadonly = false;
       this.dialogVisible = true;
     },
     openEdit(row) {
@@ -498,6 +480,7 @@ export default {
       this.form = formData;
       this.activeTab = 'method';
       this.dialogTitle = '编辑打卡规则';
+      this.dialogReadonly = false;
       this.dialogVisible = true;
     },
     toggle(row) {
@@ -649,8 +632,26 @@ export default {
       return h + '小时' + mi + '分钟';
     },
     viewDetail(row) {
-      this.detailRow = row;
-      this.detailVisible = true;
+      // 复用编辑弹窗,仅以只读模式打开,不显示保存按钮
+      const base = this.empty();
+      const formData = {
+        ...base,
+        ...row,
+        overtimeOn: row.overtime === '开启',
+        locations: row.location ? [{ name: row.location, range: '300米' }] : [],
+        wifis: row.wifi ? [{ ssid: row.wifi, bssid: '-' }] : []
+      };
+      if (!formData.workShifts && row.workTime) {
+        const m = row.workTime.match(/(\d{2}:\d{2})\s*-\s*(\d{2}:\d{2})/);
+        if (m) {
+          formData.workShifts = [{ key: 'WS1', start: m[1], end: m[2], startOpen: '是', endOpen: '是', lunchBreaks: [{ key: 'L1', start: '12:00', end: '13:00' }] }];
+        }
+      }
+      this.form = formData;
+      this.activeTab = 'method';
+      this.dialogTitle = '查看打卡规则';
+      this.dialogReadonly = true;
+      this.dialogVisible = true;
     },
     save() {
       const data = { ...this.form };
@@ -999,61 +1000,6 @@ export default {
   font-size: 11px
 }
 
-/* ===== drawer(el-drawer custom-class="rule-drawer")===== */
-.rule-drawer .employee-summary {
-  display: flex;
-  align-items: center;
-  gap: 11px;
-  padding: 14px;
-  border: 1px solid #dfe7f0;
-  border-radius: 10px;
-  background: #f8faff;
-  margin-bottom: 18px
-}
-
-.rule-drawer .employee-summary > span {
-  width: 46px;
-  height: 46px;
-  display: grid;
-  place-items: center;
-  border-radius: 50%;
-  color: #1768e5;
-  background: #e6efff;
-  font-size: 18px
-}
-
-.rule-drawer .employee-summary h3 {
-  margin: 0;
-  font-size: 15px
-}
-
-.rule-drawer .employee-summary p {
-  margin: 5px 0 0;
-  color: #8390a2;
-  font-size: 11px
-}
-
-.rule-drawer .employee-summary > div:nth-child(3) {
-  margin-left: auto;
-  text-align: right
-}
-
-.rule-drawer .employee-summary > div:nth-child(3) small,
-.rule-drawer .employee-summary > div:nth-child(3) strong {
-  display: block
-}
-
-.rule-drawer .employee-summary > div:nth-child(3) small {
-  color: #8995a5;
-  font-size: 10px
-}
-
-.rule-drawer .employee-summary > div:nth-child(3) strong {
-  margin-top: 3px;
-  color: #1768e5;
-  font-size: 13px
-}
-
 /* ===== 上下班时间 Tab 附加样式 ===== */
 .rule-dialog .tab-panel .week-chip-wrap {
   display: flex;