¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢RPAå表 |
| | | export function listRpa(query) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢RPAè¯¦ç» |
| | | export function getRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/" + rpaId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢RPA |
| | | export function addRpa(data) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹RPA |
| | | export function updateRpa(data) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤RPA |
| | | export function delRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/" + rpaId, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | // æ¹éå é¤RPA |
| | | export function delRpaBatch(rpaIds) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/batch", |
| | | method: "delete", |
| | | data: rpaIds, |
| | | }); |
| | | } |
| | | |
| | | // å¯å¨RPA |
| | | export function startRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/start/" + rpaId, |
| | | method: "post", |
| | | }); |
| | | } |
| | | |
| | | // 忢RPA |
| | | export function stopRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/stop/" + rpaId, |
| | | method: "post", |
| | | }); |
| | | } |
| | | |
| | | // è·åRPAç¶æ |
| | | export function getRpaStatus(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/status/" + rpaId, |
| | | method: "get", |
| | | }); |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" type="border-card"> |
| | | <!-- åæè®¾ç½® --> |
| | | <el-tab-pane label="åæè®¾ç½®" name="holiday"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('holiday', 'add')">æ°å¢åæ</el-button> |
| | | |
| | | <el-table :data="holidayData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" label="åæåç§°" /> |
| | | <el-table-column prop="type" label="åæç±»å"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startDate" label="å¼å§æ¥æ" /> |
| | | <el-table-column prop="endDate" label="ç»ææ¥æ" /> |
| | | <el-table-column prop="days" label="天æ°" align="center" /> |
| | | <el-table-column prop="status" label="ç¶æ" > |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('holiday', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('holiday', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- å¹´å设置 --> |
| | | <el-tab-pane label="å¹´å设置" name="annual"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('annual', 'add')">æ°å¢å¹´åè§å</el-button> |
| | | |
| | | <el-table :data="annualData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="employeeType" label="å工类å"/> |
| | | <el-table-column prop="workYears" label="å·¥ä½å¹´é" /> |
| | | <el-table-column prop="annualDays" label="å¹´å天æ°" align="center" /> |
| | | <el-table-column prop="maxCarryOver" label="æå¤§ç»è½¬å¤©æ°" align="center" /> |
| | | <el-table-column prop="status" label="ç¶æ"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('annual', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('annual', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- å ç设置 --> |
| | | <el-tab-pane label="å ç设置" name="overtime"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('overtime', 'add')">æ°å¢å çè§å</el-button> |
| | | |
| | | <el-table :data="overtimeData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" label="è§ååç§°" /> |
| | | <el-table-column prop="type" label="å çç±»å" > |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startTime" label="å¼å§æ¶é´" /> |
| | | <el-table-column prop="endTime" label="ç»ææ¶é´" /> |
| | | <el-table-column prop="rate" label="åç" align="center" /> |
| | | <el-table-column prop="status" label="ç¶æ" > |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('overtime', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('overtime', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- ä¸çæ¶é´è®¾ç½® --> |
| | | <el-tab-pane label="ä¸çæ¶é´è®¾ç½®" name="worktime"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('worktime', 'add')">æ°å¢æ¶é´æ®µ</el-button> |
| | | |
| | | <el-table :data="worktimeData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" label="æ¶é´æ®µåç§°" /> |
| | | <el-table-column prop="startTime" label="ä¸çæ¶é´"/> |
| | | <el-table-column prop="endTime" label="ä¸çæ¶é´" /> |
| | | <el-table-column prop="flexibleStart" label="å¼¹æ§ä¸ç"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.flexibleStart ? 'success' : 'info'"> |
| | | {{ scope.row.flexibleStart ? 'æ¯' : 'å¦' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="flexibleMinutes" label="å¼¹æ§æ¶é´(åé)" width="120" align="center" /> |
| | | <el-table-column prop="status" label="ç¶æ" > |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('worktime', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('worktime', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- éç¨å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="åç§°" prop="name" v-if="currentType !== 'annual'"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥åç§°" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç±»å" prop="type" v-if="currentType === 'holiday' || currentType === 'overtime'"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©ç±»å" style="width: 100%"> |
| | | <el-option |
| | | v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å工类å" prop="employeeType" v-if="currentType === 'annual'"> |
| | | <el-select v-model="form.employeeType" placeholder="è¯·éæ©å工类å" style="width: 100%"> |
| | | <el-option label="æ£å¼åå·¥" value="regular" /> |
| | | <el-option label="è¯ç¨æåå·¥" value="probation" /> |
| | | <el-option label="å®ä¹ ç" value="intern" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å·¥ä½å¹´é" prop="workYears" v-if="currentType === 'annual'"> |
| | | <el-input v-model="form.workYears" placeholder="å¦ï¼1-3å¹´ã3-5å¹´ç" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¹´å天æ°" prop="annualDays" v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.annualDays" :min="0" :max="365" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æå¤§ç»è½¬å¤©æ°" prop="maxCarryOver" v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.maxCarryOver" :min="0" :max="30" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ¥æèå´" prop="dateRange" v-if="currentType === 'holiday'"> |
| | | <el-date-picker |
| | | v-model="form.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | style="width: 100%" |
| | | @change="calculateDays" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="天æ°" prop="days" v-if="currentType === 'holiday'"> |
| | | <el-input-number v-model="form.days" :min="0" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.startTime" |
| | | placeholder="å¼å§æ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('startTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.endTime" |
| | | placeholder="ç»ææ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('endTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="åç" prop="rate" v-if="currentType === 'overtime'"> |
| | | <el-input-number v-model="form.rate" :min="1" :max="3" :step="0.5" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¸çæ¶é´" prop="workStartTime" v-if="currentType === 'worktime'"> |
| | | <el-time-picker |
| | | v-model="form.workStartTime" |
| | | placeholder="ä¸çæ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workStartTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¸çæ¶é´" prop="workEndTime" v-if="currentType === 'worktime'"> |
| | | <el-time-picker |
| | | v-model="form.workEndTime" |
| | | placeholder="ä¸çæ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workEndTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¼¹æ§ä¸ç" prop="flexibleStart" v-if="currentType === 'worktime'"> |
| | | <el-switch v-model="form.flexibleStart" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¼¹æ§æ¶é´(åé)" prop="flexibleMinutes" v-if="currentType === 'worktime' && form.flexibleStart"> |
| | | <el-input-number v-model="form.flexibleMinutes" :min="0" :max="120" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio value="active">å¯ç¨</el-radio> |
| | | <el-radio value="inactive">åç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | // å½åæ¿æ´»çæ ç¾é¡µ |
| | | const activeTab = ref('holiday') |
| | | |
| | | // å¼¹çªç¸å
³ |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const currentType = ref('') |
| | | const currentAction = ref('') |
| | | const currentEditId = ref('') |
| | | const formRef = ref() |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: '', // å çå¼å§æ¶é´ |
| | | endTime: '', // å çç»ææ¶é´ |
| | | workStartTime: '', // ä¸çæ¶é´ |
| | | workEndTime: '', // ä¸çæ¶é´ |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: 'active' |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥åç§°', trigger: 'blur' }], |
| | | type: [{ required: true, message: 'è¯·éæ©ç±»å', trigger: 'change' }], |
| | | dateRange: [{ required: true, message: 'è¯·éæ©æ¥æèå´', trigger: 'change' }], |
| | | days: [{ required: true, message: '请è¾å
¥å¤©æ°', trigger: 'blur' }], |
| | | employeeType: [{ required: true, message: 'è¯·éæ©å工类å', trigger: 'change' }], |
| | | workYears: [{ required: true, message: '请è¾å
¥å·¥ä½å¹´é', trigger: 'blur' }], |
| | | annualDays: [{ required: true, message: '请è¾å
¥å¹´å天æ°', trigger: 'blur' }], |
| | | maxCarryOver: [{ required: true, message: '请è¾å
¥æå¤§ç»è½¬å¤©æ°', trigger: 'blur' }], |
| | | startTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©å¼å§æ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©å¼å§æ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | endTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ç»ææ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©ç»ææ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | workStartTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©ä¸çæ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | workEndTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©ä¸çæ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | rate: [{ required: true, message: '请è¾å
¥åç', trigger: 'blur' }] |
| | | } |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const holidayData = ref([ |
| | | { id: '1', name: 'æ¥è', type: 'legal', startDate: '2024-02-10', endDate: '2024-02-17', days: 8, status: 'active' }, |
| | | { id: '2', name: 'æ¸
æè', type: 'legal', startDate: '2024-04-05', endDate: '2024-04-05', days: 1, status: 'active' }, |
| | | { id: '3', name: 'å³å¨è', type: 'legal', startDate: '2024-05-01', endDate: '2024-05-05', days: 5, status: 'active' } |
| | | ]) |
| | | |
| | | const annualData = ref([ |
| | | { id: '1', employeeType: 'regular', workYears: '1-3å¹´', annualDays: 5, maxCarryOver: 2, status: 'active' }, |
| | | { id: '2', employeeType: 'regular', workYears: '3-5å¹´', annualDays: 10, maxCarryOver: 5, status: 'active' }, |
| | | { id: '3', employeeType: 'regular', workYears: '5年以ä¸', annualDays: 15, maxCarryOver: 10, status: 'active' } |
| | | ]) |
| | | |
| | | const overtimeData = ref([ |
| | | { id: '1', name: '工使¥å ç', type: 'weekday', startTime: '18:00', endTime: '22:00', rate: 1.5, status: 'active' }, |
| | | { id: '2', name: '卿«å ç', type: 'weekend', startTime: '09:00', endTime: '18:00', rate: 2.0, status: 'active' }, |
| | | { id: '3', name: 'æ·±å¤å ç', type: 'night', startTime: '22:00', endTime: '06:00', rate: 2.5, status: 'active' } |
| | | ]) |
| | | |
| | | const worktimeData = ref([ |
| | | { id: '1', name: 'æ å工使¶é´', startTime: '09:00', endTime: '18:00', flexibleStart: true, flexibleMinutes: 30, status: 'active' }, |
| | | { id: '2', name: 'æ©çæ¶é´', startTime: '08:00', endTime: '17:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' }, |
| | | { id: '3', name: 'æçæ¶é´', startTime: '14:00', endTime: '23:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' } |
| | | ]) |
| | | |
| | | // å·¥å
·å½æ° |
| | | const getTagType = (type) => { |
| | | const tagMap = { |
| | | legal: 'success', adjustment: 'warning', special: 'info', company: 'primary', |
| | | weekday: 'primary', weekend: 'warning', holiday: 'danger', night: 'info' |
| | | } |
| | | return tagMap[type] || 'info' |
| | | } |
| | | |
| | | const getTypeLabel = (type) => { |
| | | const labelMap = { |
| | | legal: 'æ³å®è忥', adjustment: 'è°ä¼æ¥', special: 'ç¹æ®åæ', company: 'å
¬å¸åæ', |
| | | weekday: '工使¥å ç', weekend: '卿«å ç', holiday: 'è忥å ç', night: 'æ·±å¤å ç' |
| | | } |
| | | return labelMap[type] || type |
| | | } |
| | | |
| | | const getTypeOptions = () => { |
| | | if (currentType.value === 'holiday') { |
| | | return [ |
| | | { label: 'æ³å®è忥', value: 'legal' }, |
| | | { label: 'è°ä¼æ¥', value: 'adjustment' }, |
| | | { label: 'ç¹æ®åæ', value: 'special' }, |
| | | { label: 'å
¬å¸åæ', value: 'company' } |
| | | ] |
| | | } else if (currentType.value === 'overtime') { |
| | | return [ |
| | | { label: '工使¥å ç', value: 'weekday' }, |
| | | { label: '卿«å ç', value: 'weekend' }, |
| | | { label: 'è忥å ç', value: 'holiday' }, |
| | | { label: 'æ·±å¤å ç', value: 'night' } |
| | | ] |
| | | } |
| | | return [] |
| | | } |
| | | |
| | | // 计ç®åæå¤©æ° |
| | | const calculateDays = () => { |
| | | try { |
| | | if (form.dateRange && form.dateRange.length === 2 && form.dateRange[0] && form.dateRange[1]) { |
| | | const start = new Date(form.dateRange[0]) |
| | | const end = new Date(form.dateRange[1]) |
| | | |
| | | if (isNaN(start.getTime()) || isNaN(end.getTime())) { |
| | | console.warn('æ æçæ¥ææ ¼å¼') |
| | | return |
| | | } |
| | | |
| | | const diffTime = Math.abs(end - start) |
| | | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1 |
| | | form.days = diffDays |
| | | } |
| | | } catch (error) { |
| | | console.error('计ç®å¤©æ°å¤±è´¥:', error) |
| | | } |
| | | } |
| | | |
| | | // éªè¯æ¶é´æ ¼å¼ |
| | | const validateTime = (time) => { |
| | | if (!time) return '' |
| | | if (typeof time === 'string') return time |
| | | if (time instanceof Date) { |
| | | return time.toTimeString().slice(0, 5) |
| | | } |
| | | return '' |
| | | } |
| | | |
| | | // éªè¯æ¶é´å段 |
| | | const validateTimeField = (fieldName) => { |
| | | try { |
| | | const value = form[fieldName] |
| | | if (value && typeof value === 'object' && value.hour !== undefined) { |
| | | // å¦ææ¯æ¶é´å¯¹è±¡ï¼è½¬æ¢ä¸ºåç¬¦ä¸²æ ¼å¼ |
| | | const hours = value.hour.toString().padStart(2, '0') |
| | | const minutes = value.minute.toString().padStart(2, '0') |
| | | form[fieldName] = `${hours}:${minutes}` |
| | | } |
| | | } catch (error) { |
| | | console.error(`éªè¯æ¶é´å段 ${fieldName} 失败:`, error) |
| | | form[fieldName] = '' |
| | | } |
| | | } |
| | | |
| | | // æå¼å¼¹çª |
| | | const openDialog = (type, action, row = null) => { |
| | | try { |
| | | currentType.value = type |
| | | currentAction.value = action |
| | | |
| | | if (action === 'add') { |
| | | dialogTitle.value = `æ°å¢${getTypeName(type)}` |
| | | currentEditId.value = '' |
| | | resetForm() |
| | | } else if (action === 'edit' && row) { |
| | | dialogTitle.value = `ç¼è¾${getTypeName(type)}` |
| | | currentEditId.value = row.id |
| | | fillForm(row) |
| | | } |
| | | |
| | | dialogVisible.value = true |
| | | } catch (error) { |
| | | console.error('æå¼å¼¹çªå¤±è´¥:', error) |
| | | ElMessage.error('æå¼å¼¹çªå¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } |
| | | |
| | | const getTypeName = (type) => { |
| | | const nameMap = { |
| | | holiday: 'åæ', |
| | | annual: 'å¹´åè§å', |
| | | overtime: 'å çè§å', |
| | | worktime: 'æ¶é´æ®µ' |
| | | } |
| | | return nameMap[type] || '' |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: '', |
| | | endTime: '', |
| | | workStartTime: '', |
| | | workEndTime: '', |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: 'active' |
| | | }) |
| | | } |
| | | |
| | | const fillForm = (row) => { |
| | | if (currentType.value === 'holiday') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | dateRange: [new Date(row.startDate), new Date(row.endDate)], |
| | | days: row.days, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'annual') { |
| | | Object.assign(form, { |
| | | employeeType: row.employeeType, |
| | | workYears: row.workYears, |
| | | annualDays: row.annualDays, |
| | | maxCarryOver: row.maxCarryOver, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'overtime') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | startTime: row.startTime || '', |
| | | endTime: row.endTime || '', |
| | | rate: row.rate, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'worktime') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | workStartTime: row.startTime || '', |
| | | workEndTime: row.endTime || '', |
| | | flexibleStart: row.flexibleStart, |
| | | flexibleMinutes: row.flexibleMinutes, |
| | | status: row.status |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | if (!formRef.value) { |
| | | ElMessage.error('表åå¼ç¨ä¸åå¨') |
| | | return |
| | | } |
| | | |
| | | await formRef.value.validate() |
| | | |
| | | if (currentAction.value === 'add') { |
| | | addItem() |
| | | } else if (currentAction.value === 'edit') { |
| | | editItem() |
| | | } |
| | | |
| | | dialogVisible.value = false |
| | | ElMessage.success('æä½æå') |
| | | } catch (error) { |
| | | console.error('表åéªè¯å¤±è´¥:', error) |
| | | ElMessage.error('表åéªè¯å¤±è´¥ï¼è¯·æ£æ¥è¾å
¥') |
| | | } |
| | | } |
| | | |
| | | const addItem = () => { |
| | | const newItem = { ...form, id: Date.now().toString() } |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | newItem.startDate = form.dateRange[0].toISOString().split('T')[0] |
| | | newItem.endDate = form.dateRange[1].toISOString().split('T')[0] |
| | | holidayData.value.push(newItem) |
| | | } else if (currentType.value === 'annual') { |
| | | annualData.value.push(newItem) |
| | | } else if (currentType.value === 'overtime') { |
| | | newItem.startTime = form.startTime || '' |
| | | newItem.endTime = form.endTime || '' |
| | | overtimeData.value.push(newItem) |
| | | } else if (currentType.value === 'worktime') { |
| | | newItem.startTime = form.workStartTime || '' |
| | | newItem.endTime = form.workEndTime || '' |
| | | worktimeData.value.push(newItem) |
| | | } |
| | | } |
| | | |
| | | const editItem = () => { |
| | | let dataArray |
| | | let index |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | dataArray = holidayData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | name: form.name, |
| | | type: form.type, |
| | | startDate: form.dateRange[0].toISOString().split('T')[0], |
| | | endDate: form.dateRange[1].toISOString().split('T')[0], |
| | | days: form.days, |
| | | status: form.status |
| | | } |
| | | } |
| | | } else if (currentType.value === 'annual') { |
| | | dataArray = annualData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status |
| | | } |
| | | } |
| | | } else if (currentType.value === 'overtime') { |
| | | dataArray = overtimeData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | name: form.name, |
| | | type: form.type, |
| | | startTime: form.startTime || '', |
| | | endTime: form.endTime || '', |
| | | rate: form.rate, |
| | | status: form.status |
| | | } |
| | | } |
| | | } else if (currentType.value === 'worktime') { |
| | | dataArray = worktimeData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | name: form.name, |
| | | startTime: form.workStartTime || '', |
| | | endTime: form.workEndTime || '', |
| | | flexibleStart: form.flexibleStart, |
| | | flexibleMinutes: form.flexibleMinutes, |
| | | status: form.status |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // å é¤é¡¹ç® |
| | | const deleteItem = (type, row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿ä¸ªé¡¹ç®åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | let dataArray |
| | | if (type === 'holiday') dataArray = holidayData.value |
| | | else if (type === 'annual') dataArray = annualData.value |
| | | else if (type === 'overtime') dataArray = overtimeData.value |
| | | else if (type === 'worktime') dataArray = worktimeData.value |
| | | |
| | | const index = dataArray.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | dataArray.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | console.log('èå¤ç®¡ç页é¢å è½½å®æ') |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | // æ¸
çå·¥ä½ |
| | | dialogVisible.value = false |
| | | currentType.value = '' |
| | | currentAction.value = '' |
| | | currentEditId.value = '' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-tabs__content) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ç¥è¯æ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ç¥è¯æ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">ç¥è¯ç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="ååç¹æ¹" :value="'contract'" /> |
| | | <el-option label="å®¡æ¹æ¡ä¾" :value="'approval'" /> |
| | | <el-option label="è§£å³æ¹æ¡" :value="'solution'" /> |
| | | <el-option label="ç»éªæ»ç»" :value="'experience'" /> |
| | | <el-option label="æä½æå" :value="'guide'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢ç¥è¯</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾ç¥è¯å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¥è¯æ é¢" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请è¾å
¥ç¥è¯æ é¢" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¥è¯ç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©ç¥è¯ç±»å" style="width: 100%"> |
| | | <el-option label="ååç¹æ¹" value="contract" /> |
| | | <el-option label="å®¡æ¹æ¡ä¾" value="approval" /> |
| | | <el-option label="è§£å³æ¹æ¡" value="solution" /> |
| | | <el-option label="ç»éªæ»ç»" value="experience" /> |
| | | <el-option label="æä½æå" value="guide" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éç¨åºæ¯" prop="scenario"> |
| | | <el-input v-model="form.scenario" placeholder="请è¾å
¥éç¨åºæ¯" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§£å³æç" prop="efficiency"> |
| | | <el-select v-model="form.efficiency" placeholder="è¯·éæ©è§£å³æç" style="width: 100%"> |
| | | <el-option label="æ¾èæå" value="high" /> |
| | | <el-option label="ä¸è¬æå" value="medium" /> |
| | | <el-option label="轻微æå" value="low" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="é®é¢æè¿°" prop="problem"> |
| | | <el-input |
| | | v-model="form.problem" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请æè¿°éå°çé®é¢" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="è§£å³æ¹æ¡" prop="solution"> |
| | | <el-input |
| | | v-model="form.solution" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="è¯·è¯¦ç»æè¿°è§£å³æ¹æ¡" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å
³é®è¦ç¹" prop="keyPoints"> |
| | | <el-input |
| | | v-model="form.keyPoints" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å
³é®è¦ç¹ï¼ç¨éå·åé" |
| | | /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建人" prop="creator"> |
| | | <el-input v-model="form.creator" placeholder="请è¾å
¥å建人" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä½¿ç¨æ¬¡æ°" prop="usageCount"> |
| | | <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ¥çç¥è¯è¯¦æ
å¼¹çª --> |
| | | <el-dialog |
| | | v-model="viewDialogVisible" |
| | | title="ç¥è¯è¯¦æ
" |
| | | width="900px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="knowledge-detail"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="ç¥è¯æ é¢" :span="2"> |
| | | <span class="detail-title">{{ currentKnowledge.title }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç¥è¯ç±»å"> |
| | | <el-tag :type="getTypeTagType(currentKnowledge.type)"> |
| | | {{ getTypeLabel(currentKnowledge.type) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="éç¨åºæ¯"> |
| | | {{ currentKnowledge.scenario }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§£å³æç"> |
| | | <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)"> |
| | | {{ getEfficiencyLabel(currentKnowledge.efficiency) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä½¿ç¨æ¬¡æ°"> |
| | | <el-tag type="info">{{ currentKnowledge.usageCount }} 次</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å建人"> |
| | | {{ currentKnowledge.creator }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å建æ¶é´"> |
| | | {{ currentKnowledge.createTime }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>é®é¢æè¿°</h4> |
| | | <div class="detail-content">{{ currentKnowledge.problem }}</div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>è§£å³æ¹æ¡</h4> |
| | | <div class="detail-content">{{ currentKnowledge.solution }}</div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>å
³é®è¦ç¹</h4> |
| | | <div class="key-points"> |
| | | <el-tag |
| | | v-for="(point, index) in currentKnowledge.keyPoints.split(',')" |
| | | :key="index" |
| | | type="success" |
| | | style="margin-right: 8px; margin-bottom: 8px;" |
| | | > |
| | | {{ point.trim() }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>使ç¨ç»è®¡</h4> |
| | | <div class="usage-stats"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ currentKnowledge.usageCount }}</div> |
| | | <div class="stat-label">ä½¿ç¨æ¬¡æ°</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div> |
| | | <div class="stat-label">æçæå</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div> |
| | | <div class="stat-label">å¹³åèçæ¶é´</div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | <el-button type="primary" @click="copyKnowledge">å¤å¶ç¥è¯</el-button> |
| | | <el-button type="success" @click="markAsFavorite">æ¶è</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥ç¥è¯æ é¢", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | { required: true, message: "è¯·éæ©ç¥è¯ç±»å", trigger: "change" } |
| | | ], |
| | | problem: [ |
| | | { required: true, message: "请æè¿°éå°çé®é¢", trigger: "blur" } |
| | | ], |
| | | solution: [ |
| | | { required: true, message: "è¯·è¯¦ç»æè¿°è§£å³æ¹æ¡", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "medium", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0 |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | viewDialogVisible: false, |
| | | currentKnowledge: {} |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | viewDialogVisible, |
| | | currentKnowledge |
| | | } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¥è¯æ é¢", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "ç¥è¯ç±»å", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | contract: "ååç¹æ¹", |
| | | approval: "å®¡æ¹æ¡ä¾", |
| | | solution: "è§£å³æ¹æ¡", |
| | | experience: "ç»éªæ»ç»", |
| | | guide: "æä½æå" |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "éç¨åºæ¯", |
| | | prop: "scenario", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "è§£å³æç", |
| | | prop: "efficiency", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const efficiencyMap = { |
| | | high: "æ¾èæå", |
| | | medium: "ä¸è¬æå", |
| | | low: "轻微æå" |
| | | }; |
| | | return efficiencyMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ä½¿ç¨æ¬¡æ°", |
| | | prop: "usageCount", |
| | | width: 100, |
| | | align: "center" |
| | | }, |
| | | { |
| | | label: "å建人", |
| | | prop: "creator", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | viewKnowledge(row); |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | let mockData = [ |
| | | { |
| | | id: "1", |
| | | title: "ç¹æ®ååå®¡æ¹æµç¨ä¼åæ¹æ¡", |
| | | type: "contract", |
| | | scenario: "大é¢ååå¿«é审æ¹", |
| | | efficiency: "high", |
| | | problem: "大é¢ååå®¡æ¹æµç¨å¤æï¼å®¡æ¹æ¶é´é¿ï¼å½±åä¸å¡è¿å±", |
| | | solution: "建ç«ç»¿è²ééï¼å¯¹ç¬¦åæ¡ä»¶çååéç¨ç®åå®¡æ¹æµç¨ï¼ç±é¨é¨è´è´£äººç´æ¥å®¡æ¹ï¼å¹³åå®¡æ¹æ¶é´ä»3天缩çè³1天", |
| | | keyPoints: "绿è²é鿡件,ç®åæµç¨,å®¡æ¹æé,æ¶é´æ§å¶", |
| | | creator: "å¼ ç»ç", |
| | | usageCount: 15, |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | title: "è·¨é¨é¨åä½å®¡æ¹ç»éªæ»ç»", |
| | | type: "experience", |
| | | scenario: "å¤é¨é¨åä½é¡¹ç®", |
| | | efficiency: "medium", |
| | | problem: "è·¨é¨é¨é¡¹ç®å®¡æ¹æ¶ï¼åé¨é¨æè§ä¸ç»ä¸ï¼å®¡æ¹è¿åº¦ç¼æ
¢", |
| | | solution: "建ç«é¡¹ç®åè°æºå¶ï¼æå®é¡¹ç®è´è´£äººï¼å®æå¬å¼åè°ä¼è®®ï¼ç»ä¸åæ¹æè§ååè¿è¡å®¡æ¹", |
| | | keyPoints: "项ç®åè°,宿ä¼è®®,ç»ä¸æè§,è´è´£äººå¶åº¦", |
| | | creator: "æä¸»ç®¡", |
| | | usageCount: 8, |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | title: "ç´§æ¥éè´å®¡æ¹æä½æå", |
| | | type: "guide", |
| | | scenario: "ç´§æ¥éè´éæ±", |
| | | efficiency: "high", |
| | | problem: "ç´§æ¥éè´æ¶å®¡æ¹æµç¨å¤æï¼æ æ³æ»¡è¶³ç´§æ¥éæ±", |
| | | solution: "å¶å®ç´§æ¥éè´å®¡æ¹æ åï¼æç¡®ç´§æ¥ç¨åº¦å级ï¼ä¸å级å«éç¨ä¸åå®¡æ¹æµç¨ï¼ç¡®ä¿ç´§æ¥éæ±å¾å°åæ¶å¤ç", |
| | | keyPoints: "ç´§æ¥å级,æ åå¶å®,æµç¨ç®å,åæ¶å¤ç", |
| | | creator: "çä¸å", |
| | | usageCount: 12, |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // ç¥è¯æ 颿¨¡æ¿ |
| | | const titleTemplates = [ |
| | | "{type}å®¡æ¹æµç¨ä¼åæ¹æ¡", |
| | | "{scenario}å¤çç»éªæ»ç»", |
| | | "{type}ç¹æ®æ
åµå¤çæå", |
| | | "{scenario}å¿«éå®¡æ¹æ¹æ¡", |
| | | "{type}æ ååæä½æµç¨", |
| | | "{scenario}é®é¢è§£å³æ¹æ¡", |
| | | "{type}æä½³å®è·µæ»ç»", |
| | | "{scenario}æçæåæ¹æ¡" |
| | | ]; |
| | | |
| | | // ç¥è¯ç±»åé
ç½® |
| | | const knowledgeTypes = [ |
| | | { type: "contract", label: "ååç¹æ¹", efficiency: "high" }, |
| | | { type: "approval", label: "å®¡æ¹æ¡ä¾", efficiency: "medium" }, |
| | | { type: "solution", label: "è§£å³æ¹æ¡", efficiency: "high" }, |
| | | { type: "experience", label: "ç»éªæ»ç»", efficiency: "medium" }, |
| | | { type: "guide", label: "æä½æå", efficiency: "low" } |
| | | ]; |
| | | |
| | | // åºæ¯å表 |
| | | const scenarios = ["大é¢åå审æ¹", "è·¨é¨é¨åä½", "ç´§æ¥éè´", "ç¹æ®ç³è¯·", "æµç¨ä¼å", "é®é¢å¤ç", "æ åå建设", "æçæå"]; |
| | | |
| | | // èªå¨çææ°æ°æ® |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)]; |
| | | const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)]; |
| | | |
| | | // çæéæºæ é¢ |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace('{type}', randomType.label) |
| | | .replace('{scenario}', randomScenario); |
| | | |
| | | const newKnowledge = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | scenario: randomScenario, |
| | | efficiency: randomType.efficiency, |
| | | problem: `å¨${randomScenario}è¿ç¨ä¸éå°çé®é¢æè¿°...`, |
| | | solution: `é对${randomScenario}çè§£å³æ¹æ¡åæä½æ¥éª¤...`, |
| | | keyPoints: "å
³é®è¦ç¹1,å
³é®è¦ç¹2,å
³é®è¦ç¹3,å
³é®è¦ç¹4", |
| | | creator: ["å¼ ç»ç", "æä¸»ç®¡", "çä¸å", "åæ»ç"][Math.floor(Math.random() * 4)], |
| | | usageCount: Math.floor(Math.random() * 20) + 1, |
| | | createTime: now.toLocaleString() |
| | | }; |
| | | |
| | | // æ·»å å°æ°æ®å¼å¤´ |
| | | mockData.unshift(newKnowledge); |
| | | |
| | | // ä¿ææ°æ®éå¨åçèå´å
ï¼æå¤ä¿ç30æ¡ï¼ |
| | | if (mockData.length > 30) { |
| | | mockData = mockData.slice(0, 30); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] èªå¨çææ°ç¥è¯: ${title}`); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10åéå·æ°ä¸æ¬¡ (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | if (searchForm.value.title) { |
| | | filteredData = filteredData.filter(item => |
| | | item.title.toLowerCase().includes(searchForm.value.title.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.type) { |
| | | filteredData = filteredData.filter(item => item.type === searchForm.value.type); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ°å¢ç¥è¯"; |
| | | // é置表å |
| | | Object.assign(form.value, { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "medium", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0 |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "ç¼è¾ç¥è¯"; |
| | | Object.assign(form.value, { |
| | | title: row.title, |
| | | type: row.type, |
| | | scenario: row.scenario, |
| | | efficiency: row.efficiency, |
| | | problem: row.problem, |
| | | solution: row.solution, |
| | | keyPoints: row.keyPoints, |
| | | creator: row.creator, |
| | | usageCount: row.usageCount |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æ¥çç¥è¯è¯¦æ
|
| | | const viewKnowledge = (row) => { |
| | | currentKnowledge.value = { ...row }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ç±»å |
| | | const getTypeTagType = (type) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ææ¬ |
| | | const getTypeLabel = (type) => { |
| | | const typeMap = { |
| | | contract: "ååç¹æ¹", |
| | | approval: "å®¡æ¹æ¡ä¾", |
| | | solution: "è§£å³æ¹æ¡", |
| | | experience: "ç»éªæ»ç»", |
| | | guide: "æä½æå" |
| | | }; |
| | | return typeMap[type] || type; |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ç±»å |
| | | const getEfficiencyTagType = (efficiency) => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info" |
| | | }; |
| | | return typeMap[efficiency] || "info"; |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ææ¬ |
| | | const getEfficiencyLabel = (efficiency) => { |
| | | const efficiencyMap = { |
| | | high: "æ¾èæå", |
| | | medium: "ä¸è¬æå", |
| | | low: "轻微æå" |
| | | }; |
| | | return efficiencyMap[efficiency] || efficiency; |
| | | }; |
| | | |
| | | // è·åæçæåç¾åæ¯ |
| | | const getEfficiencyScore = (efficiency) => { |
| | | const scoreMap = { |
| | | high: 40, |
| | | medium: 25, |
| | | low: 15 |
| | | }; |
| | | return scoreMap[efficiency] || 0; |
| | | }; |
| | | |
| | | // è·åå¹³åèçæ¶é´ |
| | | const getTimeSaved = (efficiency) => { |
| | | const timeMap = { |
| | | high: "2-3天", |
| | | medium: "1-2天", |
| | | low: "0.5-1天" |
| | | }; |
| | | return timeMap[efficiency] || "æªç¥"; |
| | | }; |
| | | |
| | | // å¤å¶ç¥è¯ |
| | | const copyKnowledge = () => { |
| | | const knowledgeText = ` |
| | | ç¥è¯æ é¢ï¼${currentKnowledge.value.title} |
| | | ç¥è¯ç±»åï¼${getTypeLabel(currentKnowledge.value.type)} |
| | | éç¨åºæ¯ï¼${currentKnowledge.value.scenario} |
| | | é®é¢æè¿°ï¼${currentKnowledge.value.problem} |
| | | è§£å³æ¹æ¡ï¼${currentKnowledge.value.solution} |
| | | å
³é®è¦ç¹ï¼${currentKnowledge.value.keyPoints} |
| | | å建人ï¼${currentKnowledge.value.creator} |
| | | `.trim(); |
| | | |
| | | // å¤å¶å°åªè´´æ¿ |
| | | navigator.clipboard.writeText(knowledgeText).then(() => { |
| | | ElMessage.success("ç¥è¯å
容已å¤å¶å°åªè´´æ¿"); |
| | | }).catch(() => { |
| | | ElMessage.error("å¤å¶å¤±è´¥ï¼è¯·æå¨å¤å¶"); |
| | | }); |
| | | }; |
| | | |
| | | // æ¶èç¥è¯ |
| | | const markAsFavorite = () => { |
| | | // å¢å ä½¿ç¨æ¬¡æ° |
| | | const index = mockData.findIndex(item => item.id === currentKnowledge.value.id); |
| | | if (index !== -1) { |
| | | mockData[index].usageCount += 1; |
| | | currentKnowledge.value.usageCount += 1; |
| | | } |
| | | |
| | | ElMessage.success("å·²æ¶èï¼ä½¿ç¨æ¬¡æ°+1"); |
| | | }; |
| | | |
| | | // æäº¤ç¥è¯è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢ç¥è¯ |
| | | const newKnowledge = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario, |
| | | efficiency: form.value.efficiency, |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints, |
| | | creator: form.value.creator, |
| | | usageCount: form.value.usageCount, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(newKnowledge); |
| | | ElMessage.success("ç¥è¯å建æå"); |
| | | } else { |
| | | // ç¼è¾ç¥è¯ |
| | | const index = mockData.findIndex(item => item.id === selectedIds.value[0]); |
| | | if (index !== -1) { |
| | | Object.assign(mockData[index], { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario, |
| | | efficiency: form.value.efficiency, |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints, |
| | | creator: form.value.creator, |
| | | usageCount: form.value.usageCount |
| | | }); |
| | | ElMessage.success("ç¥è¯æ´æ°æå"); |
| | | } |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å é¤ç¥è¯ |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çç¥è¯"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // ä»mockDataä¸å é¤éä¸ç项 |
| | | selectedIds.value.forEach(id => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .knowledge-detail { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .detail-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .detail-section { |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | .detail-section h4 { |
| | | margin: 0 0 12px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | } |
| | | |
| | | .detail-content { |
| | | background: #f8f9fa; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | line-height: 1.6; |
| | | color: #606266; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .key-points { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .usage-stats { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .stat-item { |
| | | text-align: center; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">éç¥æ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥éç¥æ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">éç¥ç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="æ¾åéç¥" :value="'holiday'" /> |
| | | <el-option label="å¤ç½éç¥" :value="'penalty'" /> |
| | | <el-option label="å¼ä¼éç¥" :value="'meeting'" /> |
| | | <el-option label="临æ¶éç¥" :value="'temporary'" /> |
| | | <el-option label="æ£å¼éç¥" :value="'formal'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢éç¥</el-button> |
| | | <el-button type="success" @click="openMeetingDialog">å¨çº¿ä¼è®®</el-button> |
| | | <el-button type="warning" @click="openFileShareDialog">æä»¶å
񄧮</el-button> |
| | | <!-- <el-button type="info" @click="refreshEmployees">å·æ°åå·¥</el-button> --> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾éç¥å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éç¥æ é¢" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请è¾å
¥éç¥æ é¢" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éç¥ç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©éç¥ç±»å" style="width: 100%"> |
| | | <el-option label="æ¾åéç¥" value="holiday" /> |
| | | <el-option label="å¤ç½éç¥" value="penalty" /> |
| | | <el-option label="å¼ä¼éç¥" value="meeting" /> |
| | | <el-option label="临æ¶éç¥" value="temporary" /> |
| | | <el-option label="æ£å¼éç¥" value="formal" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼å
级" prop="priority"> |
| | | <el-select v-model="form.priority" placeholder="è¯·éæ©ä¼å
级" style="width: 100%"> |
| | | <el-option label="æ®é" value="low" /> |
| | | <el-option label="éè¦" value="medium" /> |
| | | <el-option label="ç´§æ¥" value="high" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³" prop="expireDate"> |
| | | <el-date-picker |
| | | v-model="form.expireDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æææ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="æ¥æ¶é¨é¨" prop="departments"> |
| | | <el-select |
| | | v-model="form.departments" |
| | | multiple |
| | | placeholder="è¯·éæ©æ¥æ¶é¨é¨" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="忥æ¹å¼" prop="syncMethods"> |
| | | <el-checkbox-group v-model="form.syncMethods"> |
| | | <el-checkbox |
| | | v-for="method in syncMethods" |
| | | :key="method.value" |
| | | :label="method.value" |
| | | > |
| | | {{ method.label }} |
| | | </el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="éç¥å
容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥éç¥å
容" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- å¨çº¿ä¼è®®å¼¹çª --> |
| | | <el-dialog |
| | | v-model="meetingDialogVisible" |
| | | title="å建å¨çº¿ä¼è®®" |
| | | width="700px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="meetingFormRef" :model="meetingForm" :rules="meetingRules" label-width="120px"> |
| | | <el-form-item label="ä¼è®®æ é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®æ é¢" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime"> |
| | | <el-date-picker |
| | | v-model="meetingForm.startTime" |
| | | type="datetime" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®æ¶é¿" prop="duration"> |
| | | <el-input-number |
| | | v-model="meetingForm.duration" |
| | | :min="15" |
| | | :max="480" |
| | | :step="15" |
| | | style="width: 100%" |
| | | /> |
| | | <span style="margin-left: 10px">åé</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="ä¼è®®å¹³å°" prop="platform"> |
| | | <el-select v-model="meetingForm.platform" placeholder="è¯·éæ©ä¼è®®å¹³å°" style="width: 100%"> |
| | | <el-option |
| | | v-for="platform in meetingPlatforms" |
| | | :key="platform.value" |
| | | :label="platform.label" |
| | | :value="platform.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | remote |
| | | :remote-method="filterEmployees" |
| | | :loading="employeesLoading" |
| | | placeholder="è¯·éæ©åä¼äººå" |
| | | style="width: 100%" |
| | | > |
| | | <el-option-group |
| | | v-for="group in employeeGroups" |
| | | :key="group.label" |
| | | :label="group.label" |
| | | > |
| | | <el-option |
| | | v-for="employee in group.options" |
| | | :key="employee.value" |
| | | :label="`${employee.label} (${employee.dept})`" |
| | | :value="employee.value" |
| | | > |
| | | <div style="display: flex; justify-content: space-between; align-items: center;"> |
| | | <div> |
| | | <div style="font-weight: 500;">{{ employee.label }}</div> |
| | | <div style="color: #909399; font-size: 12px;">{{ employee.dept }}</div> |
| | | </div> |
| | | <div style="text-align: right; font-size: 12px; color: #909399;"> |
| | | <div v-if="employee.phone">{{ employee.phone }}</div> |
| | | <div v-if="employee.email">{{ employee.email }}</div> |
| | | </div> |
| | | </div> |
| | | </el-option> |
| | | </el-option-group> |
| | | </el-select> |
| | | <div style="margin-top: 8px; color: #909399; font-size: 12px;"> |
| | | 已鿩 {{ meetingForm.participants.length }} 人 |
| | | </div> |
| | | <!-- å·²éæ©äººå详æ
--> |
| | | <div v-if="meetingForm.participants.length > 0" style="margin-top: 10px;"> |
| | | <el-tag |
| | | v-for="participantId in meetingForm.participants" |
| | | :key="participantId" |
| | | closable |
| | | @close="removeParticipant(participantId)" |
| | | style="margin-right: 8px; margin-bottom: 8px;" |
| | | > |
| | | {{ getEmployeeName(participantId) }} |
| | | </el-tag> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥ä¼è®®æè¿°" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="meetingDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="createMeeting">å建ä¼è®®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æä»¶å
±äº«å¼¹çª --> |
| | | <el-dialog |
| | | v-model="fileShareDialogVisible" |
| | | title="æä»¶å
񄧮" |
| | | width="700px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="fileShareFormRef" :model="fileShareForm" :rules="fileShareRules" label-width="120px"> |
| | | <el-form-item label="å
±äº«æ é¢" prop="title"> |
| | | <el-input v-model="fileShareForm.title" placeholder="请è¾å
¥å
±äº«æ é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="å
±äº«æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="fileShareForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å
±äº«æè¿°" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æ¶é¨é¨" prop="departments"> |
| | | <el-select |
| | | v-model="fileShareForm.departments" |
| | | multiple |
| | | placeholder="è¯·éæ©æ¥æ¶é¨é¨" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¸ä¼ æä»¶" prop="files"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :auto-upload="false" |
| | | :on-change="handleFileChange" |
| | | :on-remove="removeFile" |
| | | :file-list="fileList" |
| | | multiple |
| | | :limit="10" |
| | | accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.txt,.jpg,.jpeg,.png,.gif" |
| | | > |
| | | <el-button type="primary">éæ©æä»¶</el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | æ¯æä¸ä¼ ææ¡£ãå¾ççæ ¼å¼ï¼å个æä»¶ä¸è¶
è¿10MBï¼æå¤10个æä»¶ |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="fileShareDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="shareFiles">å
±äº«æä»¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥éç¥æ é¢", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | { required: true, message: "è¯·éæ©éç¥ç±»å", trigger: "change" } |
| | | ], |
| | | content: [ |
| | | { required: true, message: "请è¾å
¥éç¥å
容", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | const meetingRules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥ä¼è®®æ é¢", trigger: "blur" } |
| | | ], |
| | | startTime: [ |
| | | { required: true, message: "è¯·éæ©ä¼è®®å¼å§æ¶é´", trigger: "change" } |
| | | ], |
| | | participants: [ |
| | | { required: true, message: "è¯·éæ©åä¼äººå", trigger: "change" } |
| | | ] |
| | | }; |
| | | |
| | | const fileShareRules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥å
±äº«æ é¢", trigger: "blur" } |
| | | ], |
| | | description: [ |
| | | { required: true, message: "请è¾å
¥å
±äº«æè¿°", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | status: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | // æ°å¢éç¥ç¸å
³ |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | priority: "medium", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [] |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | // å¨çº¿ä¼è®®ç¸å
³ |
| | | meetingDialogVisible: false, |
| | | meetingForm: { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat" |
| | | }, |
| | | // æä»¶å
±äº«ç¸å
³ |
| | | fileShareDialogVisible: false, |
| | | fileShareForm: { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [] |
| | | }, |
| | | fileList: [] |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | meetingDialogVisible, |
| | | meetingForm, |
| | | fileShareDialogVisible, |
| | | fileShareForm, |
| | | fileList |
| | | } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | const meetingFormRef = ref(); |
| | | const fileShareFormRef = ref(); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "éç¥æ é¢", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "éç¥ç±»å", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | holiday: "æ¾åéç¥", |
| | | penalty: "å¤ç½éç¥", |
| | | meeting: "å¼ä¼éç¥", |
| | | temporary: "临æ¶éç¥", |
| | | formal: "æ£å¼éç¥" |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | holiday: "success", |
| | | penalty: "danger", |
| | | meeting: "warning", |
| | | temporary: "info", |
| | | formal: "primary" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ä¼å
级", |
| | | prop: "priority", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const priorityMap = { |
| | | low: "æ®é", |
| | | medium: "éè¦", |
| | | high: "ç´§æ¥" |
| | | }; |
| | | return priorityMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | low: "info", |
| | | medium: "warning", |
| | | high: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const statusMap = { |
| | | draft: "è稿", |
| | | published: "å·²åå¸", |
| | | expired: "å·²è¿æ" |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | draft: "info", |
| | | published: "success", |
| | | expired: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "æ¥æ¶é¨é¨", |
| | | prop: "departments", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | formatData: (params) => { |
| | | if (!params || params.length === 0) return "å
¨é¨é¨é¨"; |
| | | return params.join(", "); |
| | | } |
| | | }, |
| | | { |
| | | label: "æææè³", |
| | | prop: "expireDate", |
| | | width: 150, |
| | | formatData: (params) => { |
| | | if (!params) return "æ°¸ä¹
ææ"; |
| | | return params; |
| | | } |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "åå¸", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | publishNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status === "published" |
| | | }, |
| | | { |
| | | name: "æ¤å", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | revokeNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status !== "published" |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | let mockData = [ |
| | | { |
| | | id: "1", |
| | | title: "2024å¹´æ¥èæ¾åéç¥", |
| | | type: "holiday", |
| | | priority: "high", |
| | | status: "published", |
| | | content: "æ ¹æ®å½å®¶è§å®ï¼ç»åå
¬å¸å®é
æ
åµï¼ç°å°2024å¹´æ¥èæ¾å宿éç¥å¦ä¸...", |
| | | departments: ["ææ¯é¨", "éå®é¨", "人äºé¨", "è´¢å¡é¨", "è¿è¥é¨", "å¸åºé¨", "客æé¨"], |
| | | expireDate: "2024-02-15", |
| | | syncMethods: ["wechat", "dingtalk", "email"], |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | title: "ææ¯é¨å¨ä¾ä¼éç¥", |
| | | type: "meeting", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: "ææ¯é¨å®äºæ¯å¨äºä¸å2ç¹å¬å¼å¨ä¾ä¼ï¼è¯·åä½åäºåæ¶åå ...", |
| | | departments: ["ææ¯é¨"], |
| | | expireDate: "2024-01-20", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | title: "åå·¥è¡ä¸ºè§èå¤ç½éç¥", |
| | | type: "penalty", |
| | | priority: "high", |
| | | status: "draft", |
| | | content: "为维æ¤å
¬å¸æ£å¸¸ç§©åºï¼è§èåå·¥è¡ä¸ºï¼ç°å¯¹è¿åå
¬å¸è§å®çè¡ä¸ºè¿è¡å¤ç½...", |
| | | departments: ["人äºé¨", "ææ¯é¨", "éå®é¨"], |
| | | expireDate: "2024-02-13", |
| | | syncMethods: ["wechat", "email"], |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // éç¥æ 颿¨¡æ¿ |
| | | const titleTemplates = [ |
| | | "å
³äº{year}å¹´{holiday}æ¾å宿çéç¥", |
| | | "{dept}é¨é¨{meeting}ä¼è®®éç¥", |
| | | "åå·¥{behavior}è¡ä¸ºè§èæé", |
| | | "{company}éè¦äºé¡¹éç¥", |
| | | "{dept}é¨é¨å·¥ä½å®æéç¥", |
| | | "å
³äº{project}项ç®è¿åº¦çéç¥", |
| | | "{dept}é¨é¨äººåè°æ´éç¥", |
| | | "å
¬å¸{policy}æ¿çæ´æ°éç¥" |
| | | ]; |
| | | |
| | | // éç¥ç±»åé
ç½® |
| | | const notificationTypes = [ |
| | | { type: "holiday", label: "æ¾åéç¥", priority: "high" }, |
| | | { type: "meeting", label: "å¼ä¼éç¥", priority: "medium" }, |
| | | { type: "penalty", label: "å¤ç½éç¥", priority: "high" }, |
| | | { type: "temporary", label: "临æ¶éç¥", priority: "low" }, |
| | | { type: "formal", label: "æ£å¼éç¥", priority: "medium" } |
| | | ]; |
| | | |
| | | // é¨é¨å表 |
| | | const departments = ["ææ¯é¨", "éå®é¨", "人äºé¨", "è´¢å¡é¨", "è¿è¥é¨", "å¸åºé¨", "客æé¨"]; |
| | | |
| | | // 人åå表 |
| | | const employees = ref([]); |
| | | const employeesLoading = ref(false); |
| | | |
| | | // è·åå¨èåå·¥å表 |
| | | const getEmployeesList = async () => { |
| | | try { |
| | | employeesLoading.value = true; |
| | | // ä¼å
使ç¨ç³»ç»ç¨æ·æ¥å£ï¼æç§æ·è·åï¼ |
| | | const userResponse = await userListNoPageByTenantId(); |
| | | |
| | | if (userResponse.data) { |
| | | employees.value = userResponse.data.map(user => ({ |
| | | label: user.nickName || user.userName || 'æªç¥å§å', |
| | | value: user.userId || user.id, |
| | | dept: user.dept?.deptName || 'æªç¥é¨é¨', |
| | | phone: user.phonenumber || '', |
| | | email: user.email || '', |
| | | status: user.status || '0' |
| | | })).filter(user => user.status === '0'); // åªæ¾ç¤ºæ£å¸¸ç¶æçç¨æ· |
| | | } else { |
| | | // å¦æç³»ç»ç¨æ·æ¥å£å¤±è´¥ï¼ä½¿ç¨åå·¥å°è´¦æ¥å£ |
| | | const response = await staffOnJobListPage({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | staffState: 1 // å¨èç¶æ |
| | | }); |
| | | |
| | | if (response.data && response.data.records) { |
| | | employees.value = response.data.records.map(employee => ({ |
| | | label: employee.staffName || employee.name || 'æªç¥å§å', |
| | | value: employee.staffNo || employee.id || employee.staffId, |
| | | dept: employee.deptName || employee.department || 'æªç¥é¨é¨', |
| | | phone: employee.phone || employee.mobile || '', |
| | | email: employee.email || '', |
| | | status: '0' |
| | | })); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('è·ååå·¥å表失败:', error); |
| | | // 妿æ¥å£é½å¤±è´¥ï¼ä½¿ç¨é»è®¤æ°æ® |
| | | employees.value = [ |
| | | { label: "å¼ ä¸", value: "001", dept: "ææ¯é¨", phone: "13800138001", email: "zhangsan@company.com", status: "0" }, |
| | | { label: "æå", value: "002", dept: "éå®é¨", phone: "13800138002", email: "lisi@company.com", status: "0" }, |
| | | { label: "çäº", value: "003", dept: "人äºé¨", phone: "13800138003", email: "wangwu@company.com", status: "0" } |
| | | ]; |
| | | } finally { |
| | | employeesLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // åå·¥åç» |
| | | const employeeGroups = computed(() => { |
| | | const groups = {}; |
| | | employees.value.forEach(employee => { |
| | | const dept = employee.dept || 'å
¶ä»é¨é¨'; |
| | | if (!groups[dept]) { |
| | | groups[dept] = []; |
| | | } |
| | | groups[dept].push(employee); |
| | | }); |
| | | |
| | | // æé¨é¨åç§°æåºï¼ç¡®ä¿æ¾ç¤ºé¡ºåºä¸è´ |
| | | return Object.keys(groups) |
| | | .sort() |
| | | .map(dept => ({ |
| | | label: dept, |
| | | options: groups[dept].sort((a, b) => a.label.localeCompare(b.label, 'zh-CN')) |
| | | })); |
| | | }); |
| | | |
| | | // è¿æ»¤åå·¥ï¼è¿ç¨æç´¢ï¼ |
| | | const filterEmployees = (query) => { |
| | | if (query !== '') { |
| | | const lowerQuery = query.toLowerCase(); |
| | | return employees.value.filter(employee => |
| | | employee.label.toLowerCase().includes(lowerQuery) || |
| | | employee.dept.toLowerCase().includes(lowerQuery) || |
| | | (employee.phone && employee.phone.includes(query)) || |
| | | (employee.email && employee.email.toLowerCase().includes(lowerQuery)) |
| | | ); |
| | | } else { |
| | | return employees.value; |
| | | } |
| | | }; |
| | | |
| | | // å·æ°åå·¥å表 |
| | | const refreshEmployees = async () => { |
| | | ElMessage.info("æ£å¨å·æ°åå·¥å表..."); |
| | | await getEmployeesList(); |
| | | |
| | | // ç»è®¡åé¨é¨äººæ° |
| | | const deptStats = {}; |
| | | employees.value.forEach(emp => { |
| | | const dept = emp.dept || 'å
¶ä»é¨é¨'; |
| | | deptStats[dept] = (deptStats[dept] || 0) + 1; |
| | | }); |
| | | |
| | | const deptInfo = Object.entries(deptStats) |
| | | .map(([dept, count]) => `${dept}: ${count}人`) |
| | | .join(', '); |
| | | |
| | | ElMessage.success(`åå·¥åè¡¨å·æ°å®æï¼å
± ${employees.value.length} 人 (${deptInfo})`); |
| | | }; |
| | | |
| | | // è·ååå·¥å§å |
| | | const getEmployeeName = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | return employee ? employee.label : 'æªç¥äººå'; |
| | | }; |
| | | |
| | | // è·åå工详ç»ä¿¡æ¯ |
| | | const getEmployeeInfo = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | if (!employee) return null; |
| | | |
| | | return { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email |
| | | }; |
| | | }; |
| | | |
| | | // ç§»é¤åä¼äººå |
| | | const removeParticipant = (participantId) => { |
| | | const index = meetingForm.value.participants.indexOf(participantId); |
| | | if (index > -1) { |
| | | meetingForm.value.participants.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // 忥æ¹å¼é项 |
| | | const syncMethods = [ |
| | | { label: "ä¼ä¸å¾®ä¿¡", value: "wechat" }, |
| | | { label: "éé", value: "dingtalk" }, |
| | | { label: "é®ä»¶", value: "email" }, |
| | | { label: "çä¿¡", value: "sms" } |
| | | ]; |
| | | |
| | | // ä¼è®®å¹³å°é项 |
| | | const meetingPlatforms = [ |
| | | { label: "ä¼ä¸å¾®ä¿¡ä¼è®®", value: "wechat" }, |
| | | { label: "ééä¼è®®", value: "dingtalk" }, |
| | | { label: "è
¾è®¯ä¼è®®", value: "tencent" }, |
| | | { label: "Zoom", value: "zoom" } |
| | | ]; |
| | | |
| | | // èªå¨çææ°æ°æ® |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = notificationTypes[Math.floor(Math.random() * notificationTypes.length)]; |
| | | const randomDept = departments[Math.floor(Math.random() * departments.length)]; |
| | | |
| | | // çæéæºæ é¢ |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace('{year}', now.getFullYear()) |
| | | .replace('{holiday}', ['æ¥è', 'å½åº', 'ä¸ç§', 'å
æ¦'][Math.floor(Math.random() * 4)]) |
| | | .replace('{dept}', randomDept) |
| | | .replace('{meeting}', ['å¨ä¾ä¼', 'æåº¦æ»ç»', '项ç®è¯å®¡', 'å¹è®ä¼è®®'][Math.floor(Math.random() * 4)]) |
| | | .replace('{behavior}', ['èå¤', 'çè£
', 'å·¥ä½æåº¦', 'å¢éåä½'][Math.floor(Math.random() * 4)]) |
| | | .replace('{company}', ['å
¬å¸', 'éå¢', 'æ»é¨'][Math.floor(Math.random() * 4)]) |
| | | .replace('{project}', ['æ°åå转å', '产åå级', 'å¸åºæå±', '人æå¹å
»'][Math.floor(Math.random() * 4)]) |
| | | .replace('{policy}', ['èå¤', 'èªé
¬', 'ç¦å©', 'æå'][Math.floor(Math.random() * 4)]); |
| | | |
| | | // éæºç¶æ |
| | | const statuses = ['draft', 'published']; |
| | | const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; |
| | | |
| | | // éæºä¼å
级 |
| | | const priorities = ['low', 'medium', 'high']; |
| | | const randomPriority = priorities[Math.floor(Math.random() * priorities.length)]; |
| | | |
| | | const newNotification = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | priority: randomPriority, |
| | | status: randomStatus, |
| | | content: `è¿æ¯${title}ç详ç»å
容ï¼è¯·ç¸å
³äººåæ³¨ææ¥ç...`, |
| | | departments: [randomDept], |
| | | expireDate: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 30天åè¿æ |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: now.toLocaleString() |
| | | }; |
| | | |
| | | // æ·»å å°æ°æ®å¼å¤´ |
| | | mockData.unshift(newNotification); |
| | | |
| | | // ä¿ææ°æ®éå¨åçèå´å
ï¼æå¤ä¿ç20æ¡ï¼ |
| | | if (mockData.length > 20) { |
| | | mockData = mockData.slice(0, 20); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] èªå¨çææ°éç¥: ${title}`); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | getEmployeesList(); // è·ååå·¥å表 |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10åéå·æ°ä¸æ¬¡ (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | if (searchForm.value.title) { |
| | | filteredData = filteredData.filter(item => |
| | | item.title.toLowerCase().includes(searchForm.value.title.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.type) { |
| | | filteredData = filteredData.filter(item => item.type === searchForm.value.type); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ°å¢éç¥"; |
| | | // é置表å |
| | | Object.assign(form.value, { |
| | | title: "", |
| | | type: "", |
| | | priority: "medium", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [] |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "ç¼è¾éç¥"; |
| | | Object.assign(form.value, { |
| | | title: row.title, |
| | | type: row.type, |
| | | priority: row.priority, |
| | | content: row.content || "", |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | syncMethods: row.syncMethods || [] |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æå¼å¨çº¿ä¼è®®å¼¹çª |
| | | const openMeetingDialog = () => { |
| | | // é置表å |
| | | Object.assign(meetingForm.value, { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat" |
| | | }); |
| | | meetingDialogVisible.value = true; |
| | | }; |
| | | |
| | | // æå¼æä»¶å
±äº«å¼¹çª |
| | | const openFileShareDialog = () => { |
| | | // é置表å |
| | | Object.assign(fileShareForm.value, { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [] |
| | | }); |
| | | fileList.value = []; |
| | | fileShareDialogVisible.value = true; |
| | | }; |
| | | |
| | | // æå¨å·æ°æ°æ® |
| | | const manualRefresh = () => { |
| | | generateNewData(); |
| | | getList(); |
| | | ElMessage.success("æå¨å·æ°å®æï¼å·²çææ°éç¥"); |
| | | }; |
| | | |
| | | // æäº¤éç¥è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢éç¥ |
| | | const newNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | priority: form.value.priority, |
| | | status: "draft", |
| | | content: form.value.content, |
| | | departments: form.value.departments, |
| | | expireDate: form.value.expireDate, |
| | | syncMethods: form.value.syncMethods, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(newNotification); |
| | | ElMessage.success("éç¥å建æå"); |
| | | } else { |
| | | // ç¼è¾éç¥ |
| | | const index = mockData.findIndex(item => item.id === selectedIds.value[0]); |
| | | if (index !== -1) { |
| | | Object.assign(mockData[index], { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | priority: form.value.priority, |
| | | content: form.value.content, |
| | | departments: form.value.departments, |
| | | expireDate: form.value.expireDate, |
| | | syncMethods: form.value.syncMethods |
| | | }); |
| | | ElMessage.success("éç¥æ´æ°æå"); |
| | | } |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å建ä¼è®® |
| | | const createMeeting = async () => { |
| | | try { |
| | | await meetingFormRef.value.validate(); |
| | | |
| | | // 模æå建ä¼è®® |
| | | const meetingInfo = { |
| | | title: meetingForm.value.title, |
| | | startTime: meetingForm.value.startTime, |
| | | duration: meetingForm.value.duration, |
| | | participants: meetingForm.value.participants, |
| | | description: meetingForm.value.description, |
| | | platform: meetingForm.value.platform, |
| | | meetingId: `MTG${Date.now()}` |
| | | }; |
| | | |
| | | // 模æåéå°ä¼ä¸å¾®ä¿¡/éé |
| | | const platformName = meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "æªç¥å¹³å°"; |
| | | |
| | | ElMessage.success(`ä¼è®®å建æåï¼ä¼è®®ID: ${meetingInfo.meetingId}ï¼å°éè¿${platformName}åééç¥`); |
| | | meetingDialogVisible.value = false; |
| | | |
| | | // è·ååä¼äººåä¿¡æ¯ |
| | | const participantNames = meetingForm.value.participants.map(participantId => { |
| | | const employee = employees.value.find(emp => emp.value === participantId); |
| | | return employee ? employee.label : 'æªç¥äººå'; |
| | | }).join('ã'); |
| | | |
| | | // è·ååä¼äººå详ç»ä¿¡æ¯ |
| | | const participantDetails = meetingForm.value.participants.map(participantId => { |
| | | const employee = employees.value.find(emp => emp.value === participantId); |
| | | return employee ? { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email |
| | | } : null; |
| | | }).filter(Boolean); |
| | | |
| | | // å°ä¼è®®ä¿¡æ¯æ·»å å°éç¥å表 |
| | | const meetingNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: `[ä¼è®®éç¥] ${meetingInfo.title}`, |
| | | type: "meeting", |
| | | priority: "high", |
| | | status: "published", |
| | | content: `ä¼è®®æ¶é´: ${meetingInfo.startTime}ï¼æ¶é¿: ${meetingInfo.duration}åéï¼å¹³å°: ${meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "æªç¥å¹³å°"}ï¼åä¼äººå: ${participantNames}ï¼å
±${participantDetails.length}人`, |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [meetingForm.value.platform], |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(meetingNotification); |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("ä¼è®®è¡¨åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // æä»¶ä¸ä¼ å¤ç |
| | | const handleFileChange = (file) => { |
| | | const isLt10M = file.size / 1024 / 1024 < 10; |
| | | if (!isLt10M) { |
| | | ElMessage.error("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿ 10MB!"); |
| | | return false; |
| | | } |
| | | |
| | | const fileInfo = { |
| | | name: file.name, |
| | | size: file.size, |
| | | type: file.type, |
| | | uid: file.uid |
| | | }; |
| | | |
| | | fileList.value.push(fileInfo); |
| | | fileShareForm.value.files.push(fileInfo); |
| | | return false; // 黿¢èªå¨ä¸ä¼ |
| | | }; |
| | | |
| | | // ç§»é¤æä»¶ |
| | | const removeFile = (file) => { |
| | | const index = fileList.value.findIndex(item => item.uid === file.uid); |
| | | if (index !== -1) { |
| | | const index2 = fileShareForm.value.files.findIndex(item => item.uid === file.uid); |
| | | if (index2 !== -1) { |
| | | fileShareForm.value.files.splice(index2, 1); |
| | | } |
| | | fileList.value.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // å
±äº«æä»¶ |
| | | const shareFiles = async () => { |
| | | try { |
| | | await fileShareFormRef.value.validate(); |
| | | |
| | | if (fileShareForm.value.files.length === 0) { |
| | | ElMessage.warning("请è³å°éæ©ä¸ä¸ªæä»¶"); |
| | | return; |
| | | } |
| | | |
| | | // 模ææä»¶å
񄧮 |
| | | const shareInfo = { |
| | | title: fileShareForm.value.title, |
| | | description: fileShareForm.value.description, |
| | | departments: fileShareForm.value.departments, |
| | | files: fileShareForm.value.files, |
| | | shareId: `FILE${Date.now()}` |
| | | }; |
| | | |
| | | ElMessage.success(`æä»¶å
±äº«æåï¼å
±äº«ID: ${shareInfo.shareId}ï¼å·²éç¥ç¸å
³é¨é¨`); |
| | | fileShareDialogVisible.value = false; |
| | | |
| | | // å°æä»¶å
±äº«ä¿¡æ¯æ·»å å°éç¥å表 |
| | | const fileShareNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: `[æä»¶å
񄧮] ${shareInfo.title}`, |
| | | type: "temporary", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: `å
±äº«æè¿°: ${shareInfo.description}ï¼æä»¶æ°é: ${shareInfo.files.length}个`, |
| | | departments: shareInfo.departments, |
| | | expireDate: "", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(fileShareNotification); |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("æä»¶å
±äº«è¡¨åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // åå¸éç¥ |
| | | const publishNotification = (row) => { |
| | | row.status = "published"; |
| | | ElMessage.success("éç¥å叿å"); |
| | | getList(); |
| | | }; |
| | | |
| | | // æ¤åéç¥ |
| | | const revokeNotification = (row) => { |
| | | row.status = "draft"; |
| | | ElMessage.success("éç¥å·²æ¤å"); |
| | | getList(); |
| | | }; |
| | | |
| | | // å é¤éç¥ |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çéç¥"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .el-upload__tip { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .el-checkbox-group { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .el-checkbox { |
| | | margin-right: 0; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ç¨åºåï¼</span> |
| | | <el-input |
| | | v-model="searchForm.programName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ç¨åºåæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">æ§è¡ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="è¿è¡ä¸" :value="'running'" /> |
| | | <el-option label="已忢" :value="'stopped'" /> |
| | | <el-option label="å¼å¸¸" :value="'error'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- RPA表åå¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="500px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-form-item label="ç¨åºå" prop="programName"> |
| | | <el-input |
| | | v-model="form.programName" |
| | | placeholder="请è¾å
¥ç¨åºå" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ§è¡ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©æ§è¡ç¶æ" style="width: 100%"> |
| | | <el-option label="è¿è¡ä¸" value="running" /> |
| | | <el-option label="已忢" value="stopped" /> |
| | | <el-option label="å¼å¸¸" value="error" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="form.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥RPAç¨åºæè¿°" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | programName: "", |
| | | status: "", |
| | | }, |
| | | form: { |
| | | id: "", |
| | | programName: "", |
| | | status: "stopped", |
| | | description: "", |
| | | createTime: "", |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | selectedIds: [], |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | }); |
| | | |
| | | const { searchForm, form, dialogVisible, dialogTitle, dialogType, selectedIds, tableLoading, page, tableData } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | programName: [ |
| | | { required: true, message: "请è¾å
¥ç¨åºå", trigger: "blur" } |
| | | ], |
| | | status: [ |
| | | { required: true, message: "è¯·éæ©æ§è¡ç¶æ", trigger: "change" } |
| | | ] |
| | | }; |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¨åºå", |
| | | prop: "programName", |
| | | // width: 200, |
| | | }, |
| | | { |
| | | label: "æ§è¡ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | // width: 120, |
| | | formatData: (params) => { |
| | | const statusMap = { |
| | | running: "è¿è¡ä¸", |
| | | stopped: "已忢", |
| | | error: "å¼å¸¸" |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | running: "success", |
| | | stopped: "info", |
| | | error: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "æè¿°", |
| | | prop: "description", |
| | | // width: 300, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | // width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 230, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "å¼å§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleStart(row); |
| | | }, |
| | | disabled: (row) => row.status !== 'stopped' |
| | | }, |
| | | { |
| | | name: "忢", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleStop(row); |
| | | }, |
| | | disabled: (row) => row.status === 'stopped' |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const mockData = [ |
| | | { |
| | | id: "1", |
| | | programName: "订åå¤çRPA", |
| | | status: "running", |
| | | description: "èªå¨å¤ç客æ·è®¢åï¼å
æ¬éªè¯ãåé
å确认", |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | programName: "åºå忥RPA", |
| | | status: "stopped", |
| | | description: "忥å¤ä¸ªä»åºçåºåæ°æ®ï¼ç¡®ä¿æ°æ®ä¸è´æ§", |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | programName: "æ¥è¡¨çæRPA", |
| | | status: "error", |
| | | description: "èªå¨çææ¯æ¥é宿¥è¡¨ååºåæ¥è¡¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | // 模æAPIè°ç¨å»¶è¿ |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | // æ ¹æ®æç´¢æ¡ä»¶è¿æ»¤æ°æ® |
| | | if (searchForm.value.programName) { |
| | | filteredData = filteredData.filter(item => |
| | | item.programName.toLowerCase().includes(searchForm.value.programName.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.status) { |
| | | filteredData = filteredData.filter(item => item.status === searchForm.value.status); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row) => { |
| | | dialogType.value = type; |
| | | dialogVisible.value = true; |
| | | |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ·»å RPA"; |
| | | form.value = { |
| | | id: "", |
| | | programName: "", |
| | | status: "stopped", |
| | | description: "", |
| | | createTime: "", |
| | | }; |
| | | } else { |
| | | dialogTitle.value = "ç¼è¾RPA"; |
| | | form.value = { ...row }; |
| | | } |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ·»å æ°RPA |
| | | const newRPA = { |
| | | id: Date.now().toString(), |
| | | programName: form.value.programName, |
| | | status: form.value.status, |
| | | description: form.value.description, |
| | | createTime: new Date().toLocaleString(), |
| | | }; |
| | | |
| | | mockData.unshift(newRPA); |
| | | ElMessage.success("RPAæ·»å æå"); |
| | | } else { |
| | | // ç¼è¾RPA |
| | | const index = mockData.findIndex(item => item.id === form.value.id); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...form.value }; |
| | | ElMessage.success("RPAæ´æ°æå"); |
| | | } |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å¼å§RPA |
| | | const handleStart = (row) => { |
| | | ElMessageBox.confirm(`ç¡®å®è¦å¯å¨RPAç¨åº"${row.programName}"åï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | row.status = "running"; |
| | | ElMessage.success("RPAå¯å¨æå"); |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // 忢RPA |
| | | const handleStop = (row) => { |
| | | ElMessageBox.confirm(`ç¡®å®è¦åæ¢RPAç¨åº"${row.programName}"åï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | row.status = "stopped"; |
| | | ElMessage.success("RPA忢æå"); |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // å é¤RPA |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedIds.value.length > 0) { |
| | | ids = selectedIds.value.map((item) => item.id); |
| | | } else { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çRPA"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // 仿¨¡ææ°æ®ä¸å é¤éä¸ç项 |
| | | ids.forEach(id => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped></style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>çµè¡¨éé管ç</span> |
| | | <el-button style="float: right; padding: 3px 0" link @click="refreshData"> |
| | | <i class="el-icon-refresh"></i> å·æ° |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æµè¯æé® --> |
| | | <el-row :gutter="20" style="margin-bottom: 15px;"> |
| | | <el-col :span="24"> |
| | | <el-button @click="addTestData" type="primary" size="small">æ·»å æµè¯æ°æ®</el-button> |
| | | <el-button @click="clearData" type="danger" size="small">æ¸
ç©ºæ°æ®</el-button> |
| | | <el-button @click="testChart" type="success" size="small">æµè¯å¾è¡¨</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.meterNo" |
| | | placeholder="请è¾å
¥çµè¡¨ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleSearch" |
| | | > |
| | | <i slot="prefix" class="el-input__icon el-icon-search"></i> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.location" placeholder="è¯·éæ©ä½ç½®" clearable> |
| | | <el-option label="ç产车é´A" value="车é´A"></el-option> |
| | | <el-option label="ç产车é´B" value="车é´B"></el-option> |
| | | <el-option label="åå
¬åºå" value="åå
¬åº"></el-option> |
| | | <el-option label="é
çµå®¤" value="é
çµå®¤"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="yyyy-MM-dd" |
| | | value-format="yyyy-MM-dd" |
| | | /> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- çµè¡¨å表 --> |
| | | <el-table |
| | | :data="meterList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="meterNo" label="çµè¡¨ç¼å·" width="120" /> |
| | | <el-table-column prop="location" label="å®è£
ä½ç½®" width="120" /> |
| | | <el-table-column prop="meterType" label="çµè¡¨ç±»å" width="120" /> |
| | | <el-table-column prop="voltage" label="çµåç级" width="100" /> |
| | | <el-table-column prop="currentReading" label="å½å读æ°(kWh)" width="140" /> |
| | | <el-table-column prop="lastReading" label="䏿¬¡è¯»æ°(kWh)" width="140" /> |
| | | <el-table-column prop="consumption" label="ç¨çµé(kWh)" width="120" /> |
| | | <el-table-column prop="power" label="åç(kW)" width="100" /> |
| | | <el-table-column prop="powerFactor" label="åçå æ°" width="100" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'æ£å¸¸' ? 'success' : 'danger'"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="lastUpdateTime" label="æåæ´æ°æ¶é´" width="160" /> |
| | | <el-table-column label="æä½" width="180" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link @click="viewDetails(scope.row)"> |
| | | æ¥ç详æ
|
| | | </el-button> |
| | | <el-button link @click="manualCollection(scope.row)"> |
| | | æå¨éé |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- 详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="çµè¡¨è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="60%" |
| | | @opened="onDialogOpened" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>çµè¡¨ç¼å·:</label> |
| | | <span>{{ currentMeter.meterNo }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>å®è£
ä½ç½®:</label> |
| | | <span>{{ currentMeter.location }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>çµè¡¨ç±»å:</label> |
| | | <span>{{ currentMeter.meterType }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>çµåç级:</label> |
| | | <span>{{ currentMeter.voltage }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>å½å读æ°:</label> |
| | | <span>{{ currentMeter.currentReading }} kWh</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>䏿¬¡è¯»æ°:</label> |
| | | <span>{{ currentMeter.lastReading }} kWh</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>ç¨çµé:</label> |
| | | <span>{{ currentMeter.consumption }} kWh</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>åç:</label> |
| | | <span>{{ currentMeter.power }} kW</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>åçå æ°:</label> |
| | | <span>{{ currentMeter.powerFactor }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>ç¶æ:</label> |
| | | <el-tag :type="currentMeter.status === 'æ£å¸¸' ? 'success' : 'danger'"> |
| | | {{ currentMeter.status }} |
| | | </el-tag> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>æåæ´æ°æ¶é´:</label> |
| | | <span>{{ currentMeter.lastUpdateTime }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- ç¨çµè¶å¿å¾ --> |
| | | <div style="margin-top: 20px;"> |
| | | <h4>24å°æ¶ç¨çµè¶å¿</h4> |
| | | <div ref="chartContainer" style="height: 300px;"></div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from 'echarts' |
| | | |
| | | export default { |
| | | name: 'MeterCollection', |
| | | data() { |
| | | return { |
| | | loading: false, |
| | | searchForm: { |
| | | meterNo: '', |
| | | location: '', |
| | | dateRange: [] |
| | | }, |
| | | meterList: [], |
| | | pagination: { |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0 |
| | | }, |
| | | detailDialogVisible: false, |
| | | currentMeter: {}, |
| | | chart: null |
| | | } |
| | | }, |
| | | created() { |
| | | // ç«å³çæä¸äºæµè¯æ°æ® |
| | | this.meterList = [ |
| | | { |
| | | id: 1, |
| | | meterNo: 'M001', |
| | | location: '车é´A', |
| | | meterType: 'æºè½çµè¡¨', |
| | | voltage: '380V', |
| | | currentReading: 8500, |
| | | lastReading: 8400, |
| | | consumption: 100, |
| | | power: '75.5', |
| | | powerFactor: '0.85', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 10:30:00' |
| | | }, |
| | | { |
| | | id: 2, |
| | | meterNo: 'M002', |
| | | location: '车é´B', |
| | | meterType: 'å¤åè½çµè¡¨', |
| | | voltage: '220V', |
| | | currentReading: 6200, |
| | | lastReading: 6100, |
| | | consumption: 100, |
| | | power: '45.2', |
| | | powerFactor: '0.92', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 10:25:00' |
| | | } |
| | | ] |
| | | this.pagination.total = this.meterList.length |
| | | }, |
| | | mounted() { |
| | | // å»¶è¿ä¸ç¹æ¶é´åè°ç¨ï¼ç¡®ä¿DOMå·²ç»æ¸²æ |
| | | this.$nextTick(() => { |
| | | this.getMeterList() |
| | | }) |
| | | }, |
| | | watch: { |
| | | meterList: { |
| | | handler(newVal) { |
| | | console.log('meterListæ°æ®åå:', newVal) |
| | | }, |
| | | deep: true, |
| | | immediate: true |
| | | } |
| | | }, |
| | | methods: { |
| | | // è·åçµè¡¨å表 |
| | | getMeterList() { |
| | | this.loading = true |
| | | // 模æAPIè°ç¨ |
| | | setTimeout(() => { |
| | | const mockData = this.generateMockData() |
| | | this.meterList = mockData |
| | | this.pagination.total = this.meterList.length |
| | | this.loading = false |
| | | }, 500) |
| | | }, |
| | | |
| | | // çææ¨¡ææ°æ® |
| | | generateMockData() { |
| | | const locations = ['车é´A', '车é´B', 'åå
¬åº', 'é
çµå®¤'] |
| | | const meterTypes = ['æºè½çµè¡¨', 'å¤åè½çµè¡¨', 'æ®éçµè¡¨'] |
| | | const voltages = ['220V', '380V', '10kV'] |
| | | const statuses = ['æ£å¸¸', 'å¼å¸¸'] |
| | | |
| | | const data = [] |
| | | for (let i = 1; i <= 25; i++) { |
| | | const currentReading = Math.floor(Math.random() * 10000) + 5000 |
| | | const lastReading = currentReading - Math.floor(Math.random() * 100) - 10 |
| | | const consumption = currentReading - lastReading |
| | | const power = Math.random() * 100 + 20 |
| | | const powerFactor = (Math.random() * 0.3 + 0.7).toFixed(2) |
| | | |
| | | data.push({ |
| | | id: i, |
| | | meterNo: `M${String(i).padStart(3, '0')}`, |
| | | location: locations[Math.floor(Math.random() * locations.length)], |
| | | meterType: meterTypes[Math.floor(Math.random() * meterTypes.length)], |
| | | voltage: voltages[Math.floor(Math.random() * voltages.length)], |
| | | currentReading: currentReading, |
| | | lastReading: lastReading, |
| | | consumption: consumption, |
| | | power: power.toFixed(2), |
| | | powerFactor: powerFactor, |
| | | status: statuses[Math.floor(Math.random() * statuses.length)], |
| | | lastUpdateTime: this.formatDate(new Date(Date.now() - Math.random() * 86400000)) |
| | | }) |
| | | } |
| | | return data |
| | | }, |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | formatDate(date) { |
| | | const year = date.getFullYear() |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | const hours = String(date.getHours()).padStart(2, '0') |
| | | const minutes = String(date.getMinutes()).padStart(2, '0') |
| | | return `${year}-${month}-${day} ${hours}:${minutes}` |
| | | }, |
| | | |
| | | // æç´¢ |
| | | handleSearch() { |
| | | this.pagination.currentPage = 1 |
| | | this.getMeterList() |
| | | }, |
| | | |
| | | // éç½®æç´¢ |
| | | resetSearch() { |
| | | this.searchForm = { |
| | | meterNo: '', |
| | | location: '', |
| | | dateRange: [] |
| | | } |
| | | this.handleSearch() |
| | | }, |
| | | |
| | | // æ¥ç详æ
|
| | | viewDetails(row) { |
| | | this.currentMeter = row |
| | | this.detailDialogVisible = true |
| | | }, |
| | | |
| | | // å¯¹è¯æ¡æå¼ååå§åå¾è¡¨ |
| | | onDialogOpened() { |
| | | this.$nextTick(() => { |
| | | setTimeout(() => { |
| | | this.initChart() |
| | | }, 100) |
| | | }) |
| | | }, |
| | | |
| | | // æå¨éé |
| | | manualCollection(row) { |
| | | this.$message.success(`æ£å¨ééçµè¡¨ ${row.meterNo} çæ°æ®...`) |
| | | // 模æééè¿ç¨ |
| | | setTimeout(() => { |
| | | row.currentReading = Math.floor(Math.random() * 100) + row.currentReading |
| | | row.lastUpdateTime = this.formatDate(new Date()) |
| | | this.$message.success('æ°æ®éé宿') |
| | | }, 1000) |
| | | }, |
| | | |
| | | // å·æ°æ°æ® |
| | | refreshData() { |
| | | this.getMeterList() |
| | | this.$message.success('æ°æ®å·²å·æ°') |
| | | }, |
| | | |
| | | // æ·»å æµè¯æ°æ® |
| | | addTestData() { |
| | | const testData = { |
| | | id: Date.now(), |
| | | meterNo: `M${String(this.meterList.length + 1).padStart(3, '0')}`, |
| | | location: 'æµè¯ä½ç½®', |
| | | meterType: 'æµè¯çµè¡¨', |
| | | voltage: '220V', |
| | | currentReading: Math.floor(Math.random() * 10000) + 1000, |
| | | lastReading: Math.floor(Math.random() * 5000) + 500, |
| | | consumption: Math.floor(Math.random() * 100) + 10, |
| | | power: (Math.random() * 100 + 10).toFixed(2), |
| | | powerFactor: (Math.random() * 0.3 + 0.7).toFixed(2), |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: this.formatDate(new Date()) |
| | | } |
| | | this.meterList.push(testData) |
| | | this.pagination.total = this.meterList.length |
| | | this.$message.success('æµè¯æ°æ®å·²æ·»å ') |
| | | }, |
| | | |
| | | // æ¸
ç©ºæ°æ® |
| | | clearData() { |
| | | this.meterList = [] |
| | | this.pagination.total = 0 |
| | | this.$message.success('æ°æ®å·²æ¸
空') |
| | | }, |
| | | |
| | | // æµè¯å¾è¡¨ |
| | | testChart() { |
| | | this.$message.info('å¾è¡¨æµè¯åè½') |
| | | // å建ä¸ä¸ªæµè¯å¯¹è¯æ¡æ¥æµè¯å¾è¡¨ |
| | | this.currentMeter = { |
| | | meterNo: 'TEST001', |
| | | location: 'æµè¯ä½ç½®', |
| | | meterType: 'æµè¯çµè¡¨', |
| | | voltage: '220V', |
| | | currentReading: 1000, |
| | | lastReading: 900, |
| | | consumption: 100, |
| | | power: '50.0', |
| | | powerFactor: '0.85', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 12:00:00' |
| | | } |
| | | this.detailDialogVisible = true |
| | | }, |
| | | |
| | | // å页大尿¹å |
| | | handleSizeChange(val) { |
| | | this.pagination.pageSize = val |
| | | this.getMeterList() |
| | | }, |
| | | |
| | | // å½å页æ¹å |
| | | handleCurrentChange(val) { |
| | | this.pagination.pageSize = val.limit |
| | | this.pagination.currentPage = val.page |
| | | this.getMeterList() |
| | | }, |
| | | |
| | | // åå§åå¾è¡¨ |
| | | initChart() { |
| | | try { |
| | | if (this.chart) { |
| | | this.chart.dispose() |
| | | this.chart = null |
| | | } |
| | | |
| | | // ç¡®ä¿DOMå
ç´ åå¨ |
| | | if (!this.$refs.chartContainer) { |
| | | console.error('å¾è¡¨å®¹å¨ä¸åå¨ï¼çå¾
DOMæ´æ°...') |
| | | // 妿容å¨ä¸åå¨ï¼çå¾
ä¸ä¸åè¯ |
| | | setTimeout(() => { |
| | | this.initChart() |
| | | }, 100) |
| | | return |
| | | } |
| | | |
| | | // æ£æ¥å®¹å¨å°ºå¯¸ |
| | | const container = this.$refs.chartContainer |
| | | if (container.offsetWidth === 0 || container.offsetHeight === 0) { |
| | | setTimeout(() => { |
| | | this.initChart() |
| | | }, 100) |
| | | return |
| | | } |
| | | this.chart = echarts.init(container) |
| | | |
| | | // çæ24å°æ¶æ¨¡ææ°æ® |
| | | const hours = [] |
| | | const consumption = [] |
| | | for (let i = 0; i < 24; i++) { |
| | | hours.push(`${i}:00`) |
| | | consumption.push(Math.floor(Math.random() * 50) + 20) |
| | | } |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '24å°æ¶ç¨çµéè¶å¿', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | formatter: '{b}<br/>ç¨çµé: {c} kWh' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: hours, |
| | | axisLabel: { |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'ç¨çµé (kWh)' |
| | | }, |
| | | series: [{ |
| | | data: consumption, |
| | | type: 'line', |
| | | smooth: true, |
| | | areaStyle: { |
| | | opacity: 0.3 |
| | | }, |
| | | itemStyle: { |
| | | color: '#409EFF' |
| | | } |
| | | }] |
| | | } |
| | | |
| | | this.chart.setOption(option) |
| | | } catch (error) { |
| | | console.error('å¾è¡¨åå§å失败:', error) |
| | | this.$message.error('å¾è¡¨åå§å失败: ' + error.message) |
| | | } |
| | | } |
| | | }, |
| | | |
| | | beforeUnmount() { |
| | | if (this.chart) { |
| | | try { |
| | | this.chart.dispose() |
| | | this.chart = null |
| | | } catch (error) { |
| | | console.error('æ¸
çå¾è¡¨å¤±è´¥:', error) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .pagination { |
| | | margin-top: 20px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .el-table { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .detail-item { |
| | | margin-bottom: 15px; |
| | | padding: 10px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 4px; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | .detail-item label { |
| | | font-weight: bold; |
| | | color: #606266; |
| | | margin-right: 10px; |
| | | min-width: 100px; |
| | | display: inline-block; |
| | | } |
| | | |
| | | .detail-item span { |
| | | color: #303133; |
| | | } |
| | | |
| | | .detail-item .el-tag { |
| | | margin-left: 0; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container iot-monitor"> |
| | | <div class="header"> |
| | | <div class="title">宿¶å·¥åµçæ§ï¼IoTï¼</div> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="toggleCollecting">{{ collecting ? 'æåéé' : 'å¯å¨éé' }}</el-button> |
| | | <el-button @click="resetAll">éç½®</el-button> |
| | | <span class="ts">䏿¬¡æ´æ°æ¶é´ï¼{{ lastUpdatedDisplay }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- <el-alert--> |
| | | <!-- title="è¾¹ç¼é¢è¦è§åï¼è½´æ¿ç£¨æ-æ¯å¨å¼å离åºçº¿Â±5%触ååè¦ï¼æ¸©åº¦/ååè¶ç触åæé"--> |
| | | <!-- type="info"--> |
| | | <!-- :closable="false"--> |
| | | <!-- show-icon--> |
| | | <!-- class="rule-alert"--> |
| | | <!-- />--> |
| | | |
| | | <el-row :gutter="16"> |
| | | <el-col v-for="dev in devices" :key="dev.id" :span="12"> |
| | | <el-card :class="['device-card', dev.hasAlert ? 'is-alert' : '']"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <span class="device-name">{{ dev.name }}</span> |
| | | <el-tag :type="dev.hasAlert ? 'danger' : 'success'" size="small">{{ dev.hasAlert ? 'åè¦' : 'æ£å¸¸' }}</el-tag> |
| | | </div> |
| | | <div class="meta">ç±»åï¼{{ dev.type }}ï½åºçº¿æ¯å¨ï¼{{ dev.baseline.vibration.toFixed(2) }} mm/s</div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="metrics"> |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.vibration }"> |
| | | <div class="metric-head"> |
| | | <span>æ¯å¨(mm/s)</span> |
| | | <el-tag :type="dev.alerts.vibration ? 'danger' : 'info'" size="small">{{ dev.alerts.vibration ? '±5%è¶ç' : 'åºçº¿Â±5%' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.vibration).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'mm/s' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.vibration }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#409EFF']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.temperature }"> |
| | | <div class="metric-head"> |
| | | <span>温度(°C)</span> |
| | | <el-tag :type="dev.alerts.temperature ? 'warning' : 'info'" size="small">{{ dev.alerts.temperature ? 'è¶ç' : '20~80' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.temperature).toFixed(1) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: '°C' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.temperature }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#E6A23C']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.pressure }"> |
| | | <div class="metric-head"> |
| | | <span>åå(MPa)</span> |
| | | <el-tag :type="dev.alerts.pressure ? 'warning' : 'info'" size="small">{{ dev.alerts.pressure ? 'è¶ç' : '0.2~1.5' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.pressure).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'MPa' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.pressure }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#67C23A']" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ElNotification } from 'element-plus' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | defineOptions({ name: 'IoTMonitor' }) |
| | | |
| | | const windowSize = 30 |
| | | const collecting = ref(true) |
| | | const lastUpdated = ref(Date.now()) |
| | | const lastUpdatedDisplay = computed(() => new Date(lastUpdated.value).toLocaleTimeString()) |
| | | |
| | | const xAxisLabels = ref(Array.from({ length: windowSize }, (_, i) => i - (windowSize - 1)).map(n => `${n}s`)) |
| | | |
| | | function makeSeries(fill, decimals = 2) { |
| | | return Array.from({ length: windowSize }, () => Number(fill.toFixed(decimals))) |
| | | } |
| | | |
| | | const devices = reactive([ |
| | | { |
| | | id: 'hydrocyclone-desander', |
| | | name: 'ææµé¤ç å¨', |
| | | type: 'å离设å¤', |
| | | baseline: { vibration: 8 }, |
| | | initial: { temperature: 35, pressure: 0.85 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(8), |
| | | temperature: makeSeries(35, 1), |
| | | pressure: makeSeries(0.85, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'high-pressure-separator', |
| | | name: 'é«ååç¦»å¨æ¬', |
| | | type: 'å离设å¤', |
| | | baseline: { vibration: 6 }, |
| | | initial: { temperature: 45, pressure: 1.20 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(6), |
| | | temperature: makeSeries(45, 1), |
| | | pressure: makeSeries(1.2, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'heating-throttle-pressure', |
| | | name: 'ç»åå¼å çèæµè°å', |
| | | type: 'è°å设å¤', |
| | | baseline: { vibration: 10 }, |
| | | initial: { temperature: 75, pressure: 1.80 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(10), |
| | | temperature: makeSeries(75, 1), |
| | | pressure: makeSeries(1.8, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'three-phase-separator', |
| | | name: 'ä¸ç¸å离å¨', |
| | | type: 'å离设å¤', |
| | | baseline: { vibration: 7 }, |
| | | initial: { temperature: 38, pressure: 0.95 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(7), |
| | | temperature: makeSeries(38, 1), |
| | | pressure: makeSeries(0.95, 2), |
| | | }, |
| | | }, |
| | | ]) |
| | | |
| | | function currentValue(arr) { |
| | | return arr[arr.length - 1] ?? 0 |
| | | } |
| | | |
| | | function pushWindow(arr, val) { |
| | | if (arr.length >= windowSize) arr.shift() |
| | | arr.push(val) |
| | | } |
| | | |
| | | function clamp(val, min, max) { return Math.max(min, Math.min(max, val)) } |
| | | |
| | | function tickDevice(dev) { |
| | | const vibBase = dev.baseline.vibration |
| | | // æ¯å¨ï¼åºçº¿Â±2%éæºæ³¢å¨ï¼5%æ¦ç触å8%~12%å°å³°æ¨¡æåè¦ |
| | | const spike = Math.random() < 0.05 |
| | | const vibNoise = vibBase * (spike ? (1 + (Math.random() * 0.08 + 0.04) * (Math.random() < 0.5 ? -1 : 1)) : (1 + (Math.random() - 0.5) * 0.04)) |
| | | const vibVal = Number(vibNoise.toFixed(2)) |
| | | pushWindow(dev.series.vibration, vibVal) |
| | | |
| | | // 温度ï¼ç¼æ
¢éæºæ¸¸èµ°ï¼å¹¶æ·»å å¶å髿¸©åç§» |
| | | const tPrev = currentValue(dev.series.temperature) |
| | | const tDrift = tPrev + (Math.random() - 0.5) * 0.8 + (Math.random() < 0.02 ? 6 : 0) |
| | | const tVal = Number(clamp(tDrift, 15, 95).toFixed(1)) |
| | | pushWindow(dev.series.temperature, tVal) |
| | | |
| | | // ååï¼å°å¹
æ³¢å¨ï¼å¶åä½å/é«å |
| | | const pPrev = currentValue(dev.series.pressure) |
| | | const pDrift = pPrev + (Math.random() - 0.5) * 0.05 + (Math.random() < 0.02 ? (Math.random() < 0.5 ? -0.3 : 0.3) : 0) |
| | | const pVal = Number(clamp(pDrift, 0.05, 2.0).toFixed(2)) |
| | | pushWindow(dev.series.pressure, pVal) |
| | | |
| | | // è¾¹ç¼è®¡ç®éå¼å¤æ |
| | | const vibDelta = Math.abs(vibVal - vibBase) / vibBase |
| | | const vibAlert = vibDelta > 0.05 |
| | | const tAlert = tVal < 20 || tVal > 80 |
| | | const pAlert = pVal < 0.2 || pVal > 1.5 |
| | | |
| | | const prevHasAlert = dev.hasAlert |
| | | dev.alerts.vibration = vibAlert |
| | | dev.alerts.temperature = tAlert |
| | | dev.alerts.pressure = pAlert |
| | | dev.hasAlert = vibAlert || tAlert || pAlert |
| | | |
| | | if (dev.hasAlert && !prevHasAlert) { |
| | | const reasons = [] |
| | | if (vibAlert) reasons.push(`æ¯å¨å离±5% (å½å ${vibVal} / åºçº¿ ${vibBase})`) |
| | | if (tAlert) reasons.push(`温度è¶ç (å½å ${tVal}°C, ææ 20~80°C) `) |
| | | if (pAlert) reasons.push(`ååè¶ç (å½å ${pVal}MPa, ææ 0.2~1.5MPa) `) |
| | | ElNotification({ |
| | | title: `${dev.name} åè¦`, |
| | | message: reasons.join('ï¼'), |
| | | type: vibAlert ? 'error' : 'warning', |
| | | duration: 5000, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | let timer = null |
| | | function start() { |
| | | if (timer) return |
| | | timer = setInterval(() => { |
| | | if (!collecting.value) return |
| | | devices.forEach(tickDevice) |
| | | lastUpdated.value = Date.now() |
| | | }, 10000) |
| | | } |
| | | |
| | | function stop() { |
| | | if (timer) { |
| | | clearInterval(timer) |
| | | timer = null |
| | | } |
| | | } |
| | | |
| | | function toggleCollecting() { collecting.value = !collecting.value } |
| | | |
| | | function resetAll() { |
| | | devices.forEach(dev => { |
| | | dev.series.vibration = makeSeries(dev.baseline.vibration) |
| | | const t0 = dev.initial?.temperature ?? 45 |
| | | const p0 = dev.initial?.pressure ?? 0.8 |
| | | dev.series.temperature = makeSeries(t0, 1) |
| | | dev.series.pressure = makeSeries(p0, 2) |
| | | dev.alerts.vibration = false |
| | | dev.alerts.temperature = false |
| | | dev.alerts.pressure = false |
| | | dev.hasAlert = false |
| | | }) |
| | | lastUpdated.value = Date.now() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | start() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | stop() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .iot-monitor { |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | .title { font-size: 18px; font-weight: 600; } |
| | | .actions { display: flex; align-items: center; gap: 8px; } |
| | | .ts { color: #909399; font-size: 12px; } |
| | | } |
| | | .rule-alert { margin-bottom: 12px; } |
| | | } |
| | | |
| | | .device-card { |
| | | margin-bottom: 16px; |
| | | transition: border-color 0.2s ease, box-shadow 0.2s ease; |
| | | &.is-alert { border-color: #F56C6C; box-shadow: 0 0 0 2px rgba(245,108,108,0.2) inset; } |
| | | .card-header { |
| | | display: flex; flex-direction: column; gap: 4px; |
| | | .card-title { display: flex; align-items: center; gap: 8px; font-weight: 600; } |
| | | .meta { color: #909399; font-size: 12px; } |
| | | } |
| | | .metrics { |
| | | display: grid; |
| | | grid-template-columns: 1fr; |
| | | gap: 12px; |
| | | } |
| | | } |
| | | |
| | | .metric { |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | padding: 8px 8px 0 8px; |
| | | &-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 13px; color: #606266; } |
| | | &-value { font-size: 20px; font-weight: 600; margin: 2px 0 6px 0; } |
| | | } |
| | | |
| | | .metric-alert { |
| | | border-color: #F56C6C; |
| | | background: #FFF6F6; |
| | | } |
| | | |
| | | @media (min-width: 1200px) { |
| | | .device-card .metrics { grid-template-columns: 1fr 1fr 1fr; } |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | <div class="table_list"> |
| | | <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢å°è´¦</el-button> |
| | | <el-button type="success" @click="openScanAddDialog">æ«ç æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | |
| | | @click="showQRCode(scope.row)" |
| | | >çæäºç»´ç </el-button |
| | | > |
| | | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <el-button type="primary" @click="downloadQRCode">ä¸è½½äºç»´ç å¾ç</el-button> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- æ«ç æ°å¢å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="scanAddDialogVisible" |
| | | title="æ«ç æ°å¢éè´å°è´¦" |
| | | width="70%" |
| | | @close="closeScanAddDialog" |
| | | > |
| | | <el-form |
| | | :model="scanAddForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="scanAddRules" |
| | | ref="scanAddFormRef" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ«ç å
容ï¼"> |
| | | <el-input |
| | | v-model="scanAddForm.scanContent" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="è¯·æ«æäºç»´ç ææå¨è¾å
¥éè´ååä¿¡æ¯" |
| | | @input="parseScanContent" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ååå·ï¼" prop="purchaseContractNumber"> |
| | | <el-input |
| | | v-model="scanAddForm.purchaseContractNumber" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°ï¼" prop="supplierName"> |
| | | <el-input |
| | | v-model="scanAddForm.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectName"> |
| | | <el-input |
| | | v-model="scanAddForm.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååéé¢(å
)ï¼" prop="contractAmount"> |
| | | <el-input-number |
| | | v-model="scanAddForm.contractAmount" |
| | | :precision="2" |
| | | :step="0.1" |
| | | clearable |
| | | style="width: 100%" |
| | | placeholder="请è¾å
¥" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼ï¼"> |
| | | <el-input |
| | | v-model="scanAddForm.paymentMethod" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼"> |
| | | <el-input v-model="scanAddForm.recorderName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼"> |
| | | <el-input |
| | | v-model="scanAddForm.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitScanAdd">确认æ°å¢</el-button> |
| | | <el-button @click="closeScanAddDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ«ç ç»è®°å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="scanDialogVisible" |
| | | title="æ«ç ç»è®°" |
| | | width="60%" |
| | | @close="closeScanDialog" |
| | | > |
| | | <el-form |
| | | :model="scanForm" |
| | | label-width="120px" |
| | | label-position="left" |
| | | :rules="scanRules" |
| | | ref="scanFormRef" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ååå·ï¼"> |
| | | <el-input v-model="scanForm.purchaseContractNumber" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="scanForm.supplierName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼"> |
| | | <el-input v-model="scanForm.projectName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ«ç æ¶é´ï¼"> |
| | | <el-input v-model="scanForm.scanTime" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ«ç 人ï¼"> |
| | | <el-input v-model="scanForm.scannerName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ«ç ç¶æï¼"> |
| | | <el-tag :type="scanForm.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> |
| | | {{ scanForm.scanStatus }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ«ç 夿³¨ï¼"> |
| | | <el-input |
| | | v-model="scanForm.scanRemark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥æ«ç 夿³¨ä¿¡æ¯" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ«ç è®°å½ï¼"> |
| | | <el-table :data="scanRecords" border style="width: 100%"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column label="æ«ç æ¶é´" prop="scanTime" width="180" /> |
| | | <el-table-column label="æ«ç 人" prop="scannerName" width="120" /> |
| | | <el-table-column label="æ«ç ç¶æ" prop="scanStatus" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> |
| | | {{ scope.row.scanStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" prop="scanRemark" /> |
| | | </el-table> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitScan">确认æ«ç </el-button> |
| | | <el-button @click="closeScanDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | }; |
| | | |
| | | // æ«ç æ°å¢å¯¹è¯æ¡ç¸å
³åé |
| | | const scanAddDialogVisible = ref(false); |
| | | const scanAddForm = reactive({ |
| | | scanContent: "", |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | contractAmount: "", |
| | | paymentMethod: "", |
| | | recorderName: "", |
| | | scanRemark: "", |
| | | }); |
| | | const scanAddRules = { |
| | | purchaseContractNumber: [{ required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }], |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }], |
| | | projectName: [{ required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | | // æ«ç ç»è®°å¯¹è¯æ¡ç¸å
³åé |
| | | const scanDialogVisible = ref(false); |
| | | const scanForm = reactive({ |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | scanTime: "", |
| | | scannerName: "", |
| | | scanStatus: "æªæ«ç ", |
| | | scanRemark: "", |
| | | }); |
| | | const scanRules = { |
| | | scanRemark: [{ required: true, message: "请è¾å
¥æ«ç 夿³¨", trigger: "blur" }], |
| | | }; |
| | | const scanRecords = ref([]); |
| | | |
| | | // æå¼æ«ç æ°å¢å¯¹è¯æ¡ |
| | | const openScanAddDialog = () => { |
| | | scanAddForm.scanContent = ""; |
| | | scanAddForm.purchaseContractNumber = ""; |
| | | scanAddForm.supplierName = ""; |
| | | scanAddForm.projectName = ""; |
| | | scanAddForm.contractAmount = ""; |
| | | scanAddForm.paymentMethod = ""; |
| | | scanAddForm.recorderName = userStore.nickName; |
| | | scanAddForm.scanRemark = ""; |
| | | scanAddDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è§£ææ«ç å
å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ |
| | | const parseScanContent = (content) => { |
| | | if (!content) return; |
| | | |
| | | // 模æè§£æäºç»´ç å
容ï¼è¿éå¯ä»¥æ ¹æ®å®é
éæ±è°æ´è§£æé»è¾ |
| | | // å设æ«ç å
å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ |
| | | const parts = content.split('|'); |
| | | if (parts.length >= 3) { |
| | | scanAddForm.purchaseContractNumber = parts[0] || ""; |
| | | scanAddForm.supplierName = parts[1] || ""; |
| | | scanAddForm.projectName = parts[2] || ""; |
| | | scanAddForm.contractAmount = parts[3] || ""; |
| | | scanAddForm.paymentMethod = parts[4] || ""; |
| | | } |
| | | }; |
| | | |
| | | // å
³éæ«ç æ°å¢å¯¹è¯æ¡ |
| | | const closeScanAddDialog = () => { |
| | | scanAddDialogVisible.value = false; |
| | | proxy.resetForm("scanAddFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç æ°å¢ |
| | | const submitScanAdd = () => { |
| | | proxy.$refs["scanAddFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æå»ºæ°å¢æ°æ® |
| | | const newData = { |
| | | purchaseContractNumber: scanAddForm.purchaseContractNumber, |
| | | supplierName: scanAddForm.supplierName, |
| | | projectName: scanAddForm.projectName, |
| | | contractAmount: scanAddForm.contractAmount, |
| | | paymentMethod: scanAddForm.paymentMethod, |
| | | recorderName: scanAddForm.recorderName, |
| | | entryDate: getCurrentDate(), |
| | | remark: scanAddForm.scanRemark, |
| | | type: 2 |
| | | }; |
| | | |
| | | // æ¨¡ææ°å¢æå |
| | | proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); |
| | | closeScanAddDialog(); |
| | | |
| | | // å¯ä»¥éæ©æ¯å¦å·æ°å表 |
| | | // getList(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æå¼æ«ç ç»è®°å¯¹è¯æ¡ |
| | | const openScanDialog = (row) => { |
| | | scanForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | scanForm.supplierName = row.supplierName; |
| | | scanForm.projectName = row.projectName; |
| | | scanForm.scanTime = getCurrentDateTime(); |
| | | scanForm.scannerName = userStore.nickName; |
| | | scanForm.scanStatus = "æªæ«ç "; |
| | | scanForm.scanRemark = ""; |
| | | scanRecords.value = []; |
| | | scanDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ«ç ç»è®°å¯¹è¯æ¡ |
| | | const closeScanDialog = () => { |
| | | scanDialogVisible.value = false; |
| | | proxy.resetForm("scanFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç ç»è®° |
| | | const submitScan = () => { |
| | | proxy.$refs["scanFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æ·»å æ«ç è®°å½ |
| | | scanRecords.value.push({ |
| | | ...scanForm, |
| | | id: Date.now(), // 模æID |
| | | scanTime: getCurrentDateTime(), |
| | | }); |
| | | scanForm.scanStatus = "å·²æ«ç "; |
| | | scanForm.scanRemark = scanForm.scanRemark || "æ "; |
| | | proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); |
| | | closeScanDialog(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥ææ¶é´ |
| | | function getCurrentDateTime() { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = String(now.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(now.getDate()).padStart(2, "0"); |
| | | const hours = String(now.getHours()).padStart(2, "0"); |
| | | const minutes = String(now.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(now.getSeconds()).padStart(2, "0"); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | |
| | | <template>
|
| | | <div class="app-container">
|
| | | <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
|
| | | <el-form-item label="é¨é¨åç§°" prop="deptName">
|
| | | <el-form-item label="å
¬å¸åç§°" prop="deptName">
|
| | | <el-input
|
| | | v-model="queryParams.deptName"
|
| | | placeholder="请è¾å
¥é¨é¨åç§°"
|
| | | placeholder="请è¾å
¥å
¬å¸åç§°"
|
| | | clearable
|
| | | style="width: 200px"
|
| | | @keyup.enter="handleQuery"
|
| | | />
|
| | | </el-form-item>
|
| | | <el-form-item label="ç¶æ" prop="status">
|
| | | <el-select v-model="queryParams.status" placeholder="é¨é¨ç¶æ" clearable style="width: 200px">
|
| | | <el-select v-model="queryParams.status" placeholder="å
¬å¸ç¶æ" clearable style="width: 200px">
|
| | | <el-option
|
| | | v-for="dict in sys_normal_disable"
|
| | | :key="dict.value"
|
| | |
| | | :default-expand-all="isExpandAll"
|
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
| | | >
|
| | | <el-table-column prop="deptName" label="é¨é¨åç§°" width="260"></el-table-column>
|
| | | <el-table-column prop="deptName" label="å
¬å¸åç§°" width="260"></el-table-column>
|
| | | <el-table-column prop="orderNum" label="æåº" width="200"></el-table-column>
|
| | | <el-table-column prop="status" label="ç¶æ" width="100">
|
| | | <template #default="scope">
|
| | |
| | | </el-table-column>
|
| | | </el-table>
|
| | |
|
| | | <!-- æ·»å æä¿®æ¹é¨é¨å¯¹è¯æ¡ -->
|
| | | <!-- æ·»å æä¿®æ¹å
¬å¸å¯¹è¯æ¡ -->
|
| | | <el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
| | | <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
|
| | | <el-row>
|
| | | <el-col :span="24" v-if="form.parentId !== 0">
|
| | | <el-form-item label="ä¸çº§é¨é¨" prop="parentId">
|
| | | <el-form-item label="ä¸çº§å
¬å¸" prop="parentId">
|
| | | <el-tree-select
|
| | | v-model="form.parentId"
|
| | | :data="deptOptions"
|
| | | :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
|
| | | value-key="deptId"
|
| | | placeholder="éæ©ä¸çº§é¨é¨"
|
| | | placeholder="éæ©ä¸çº§å
¬å¸"
|
| | | check-strictly
|
| | | />
|
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | | <el-form-item label="é¨é¨åç§°" prop="deptName">
|
| | | <el-input v-model="form.deptName" placeholder="请è¾å
¥é¨é¨åç§°" />
|
| | | <el-form-item label="å
¬å¸åç§°" prop="deptName">
|
| | | <el-input v-model="form.deptName" placeholder="请è¾å
¥å
¬å¸åç§°" />
|
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | |
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | | <el-form-item label="é¨é¨ç¶æ">
|
| | | <el-form-item label="å
¬å¸ç¶æ">
|
| | | <el-radio-group v-model="form.status">
|
| | | <el-radio
|
| | | v-for="dict in sys_normal_disable"
|
| | |
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | | <el-form-item label="é¨é¨ç¼å·" prop="deptNick">
|
| | | <el-input v-model="form.deptNick" placeholder="请è¾å
¥é¨é¨ç¼å·" maxlength="50" />
|
| | | <el-form-item label="å
¬å¸ç¼å·" prop="deptNick">
|
| | | <el-input v-model="form.deptNick" placeholder="请è¾å
¥å
¬å¸ç¼å·" maxlength="50" />
|
| | | </el-form-item>
|
| | | </el-col>
|
| | | </el-row>
|
| | |
| | | status: undefined
|
| | | },
|
| | | rules: {
|
| | | parentId: [{ required: true, message: "ä¸çº§é¨é¨ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | deptName: [{ required: true, message: "é¨é¨åç§°ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | parentId: [{ required: true, message: "ä¸çº§å
¬å¸ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | deptName: [{ required: true, message: "å
¬å¸åç§°ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | orderNum: [{ required: true, message: "æ¾ç¤ºæåºä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | email: [{ type: "email", message: "请è¾å
¥æ£ç¡®çé®ç®±å°å", trigger: ["blur", "change"] }],
|
| | | phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请è¾å
¥æ£ç¡®çææºå·ç ", trigger: "blur" }],
|
| | | deptNick: [{ required: true, message: "é¨é¨ç¼å·ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | deptNick: [{ required: true, message: "å
¬å¸ç¼å·ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | },
|
| | | })
|
| | |
|
| | | const { queryParams, form, rules } = toRefs(data)
|
| | |
|
| | | /** æ¥è¯¢é¨é¨å表 */
|
| | | /** æ¥è¯¢å
¬å¸å表 */
|
| | | function getList() {
|
| | | loading.value = true
|
| | | listDept(queryParams.value).then(response => {
|
| | |
| | | form.value.parentId = row.deptId
|
| | | }
|
| | | open.value = true
|
| | | title.value = "æ·»å é¨é¨"
|
| | | title.value = "æ·»å å
¬å¸"
|
| | | }
|
| | |
|
| | | /** å±å¼/æå æä½ */
|
| | |
| | | getDept(row.deptId).then(response => {
|
| | | form.value = response.data
|
| | | open.value = true
|
| | | title.value = "ä¿®æ¹é¨é¨"
|
| | | title.value = "ä¿®æ¹å
¬å¸"
|
| | | })
|
| | | }
|
| | |
|