From 8f13054d6885f50f5c135366da6d6648dd9e6ada Mon Sep 17 00:00:00 2001
From: sheng <905537351@qq.com>
Date: Thu, 25 Jun 2026 22:33:17 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=8F=AD=E7=BB=84=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E5=8A=9F=E8=83=BD=E8=BF=81=E7=A7=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../shift-management.js | 7 ++-
.../production-master-data/team-management.js | 7 ++-
src/locales/en.json | 4 ++
src/locales/zh-chs.json | 4 ++
.../team-model/shift-management/index.vue | 50 ++++++++++++---
.../team-model/team-management/index.vue | 61 +++++++++++++++++--
6 files changed, 118 insertions(+), 15 deletions(-)
diff --git a/src/api/production-master-data/shift-management.js b/src/api/production-master-data/shift-management.js
index 4c4a12a7..4e68c526 100644
--- a/src/api/production-master-data/shift-management.js
+++ b/src/api/production-master-data/shift-management.js
@@ -1,4 +1,5 @@
import { request } from '@/api/_service'
+import qs from 'qs'
const BASE = 'system_settings/organization/production_shift_management/'
@@ -6,8 +7,12 @@ function params (method, data = {}) {
return { method: `system_settings_organization_production_shift_management_${method}`, platform: 'background', ...data }
}
+function stringifyParams (method, data = {}) {
+ return qs.stringify(params(method, data), { arrayFormat: 'brackets', allowDots: true, encode: false })
+}
+
export function getShiftAll (data) { return request({ url: BASE + 'all', method: 'get', params: params('all', data) }) }
-export function getShiftList (data) { return request({ url: BASE + 'list', method: 'get', params: params('list', data) }) }
+export function getShiftList (data) { return request({ url: BASE + 'list?' + stringifyParams('list', data), method: 'get', params: {} }) }
export function createShift (data) { return request({ url: BASE + 'create', method: 'post', data: params('create', data) }) }
export function editShift (data) { return request({ url: BASE + 'edit', method: 'put', data: params('edit', data) }) }
export function deleteShift (data) { return request({ url: BASE + 'delete', method: 'delete', data: params('delete', data) }) }
diff --git a/src/api/production-master-data/team-management.js b/src/api/production-master-data/team-management.js
index f88868aa..95cb27f2 100644
--- a/src/api/production-master-data/team-management.js
+++ b/src/api/production-master-data/team-management.js
@@ -1,4 +1,5 @@
import { request } from '@/api/_service'
+import qs from 'qs'
const BASE = 'system_settings/organization/production_team_manage/'
const MEMBER_BASE = 'system_settings/organization/production_members_manage/'
@@ -11,8 +12,12 @@ function memberParams (method, data = {}) {
return { method: `system_settings_organization_production_members_manage_${method}`, platform: 'background', ...data }
}
+function stringifyParams (method, data = {}) {
+ return qs.stringify(params(method, data), { arrayFormat: 'brackets', allowDots: true, encode: false })
+}
+
export function getTeamAll (data) { return request({ url: BASE + 'all', method: 'get', params: params('all', data) }) }
-export function getTeamList (data) { return request({ url: BASE + 'list', method: 'get', params: params('list', data) }) }
+export function getTeamList (data) { return request({ url: BASE + 'list?' + stringifyParams('list', data), method: 'get', params: {} }) }
export function createTeam (data) { return request({ url: BASE + 'create', method: 'post', data: params('create', data) }) }
export function editTeam (data) { return request({ url: BASE + 'edit', method: 'put', data: params('edit', data) }) }
export function deleteTeam (data) { return request({ url: BASE + 'delete', method: 'delete', data: params('delete', data) }) }
diff --git a/src/locales/en.json b/src/locales/en.json
index 849195e4..21fbc664 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -641,6 +641,10 @@
"select_shift_end_time": "Please select shift end time",
"production_team_binding": "Team Binding",
"production_team_can_only_bind_one_shift": "One team can only bind one shift",
+ "rotation_count": "Rotation Count",
+ "rotation_unit": "Rotation Unit",
+ "please_select_valid_time": "Please select a valid time",
+ "shift_time_conflict": "Shift time range conflicts",
"please_enter_shift_plan_name": "Please enter shift plan name",
"please_enter_shift_plan_code": "Please enter shift plan code",
"please_enter_shift_name_row": "Please enter shift name, row: ",
diff --git a/src/locales/zh-chs.json b/src/locales/zh-chs.json
index b829815f..43f7318c 100644
--- a/src/locales/zh-chs.json
+++ b/src/locales/zh-chs.json
@@ -641,6 +641,10 @@
"select_shift_end_time": "请选择班次结束时间",
"production_team_binding": "生产班组绑定",
"production_team_can_only_bind_one_shift": "一个生产班组只能绑定一个班次",
+ "rotation_count": "轮转次数",
+ "rotation_unit": "轮转单位",
+ "please_select_valid_time": "请选择有效时间",
+ "shift_time_conflict": "班次时间段存在冲突",
"please_enter_shift_plan_name": "请输入班次计划名称",
"please_enter_shift_plan_code": "请输入班次计划编码",
"please_enter_shift_name_row": "请输入班次名称,行号:",
diff --git a/src/views/production-master-data/team-model/shift-management/index.vue b/src/views/production-master-data/team-model/shift-management/index.vue
index cac53884..539db76c 100644
--- a/src/views/production-master-data/team-model/shift-management/index.vue
+++ b/src/views/production-master-data/team-model/shift-management/index.vue
@@ -38,8 +38,8 @@
{{ $t(key('add_shift')) }}
-
-
+ changeShiftTime(val, scope.$index, 'start_time')" />
+ changeShiftTime(val, scope.$index, 'finish_time')" />
changeProductionTeam(val, scope.$index)">
{{ $t(key('delete')) }}
@@ -49,7 +49,7 @@
{{ $t(key('select_file')) }}{{ $t(key('download_template')) }}
-
+
{{ $t(key('cancel')) }}{{ $t(key('confirm')) }}
@@ -122,7 +122,12 @@ export default {
methods: {
defaultFormData () { return { name: '', code: '', time_range: '', status: 1, remark: '', weekly_rest_days: [6, 7], cycle: { unit: 'day', length: 0 }, productionTeamIds: [] } },
async loadTeams () { const res = await getTeamAll({}); this.teamOptions = (res && res.data) || res || [] },
- async fetchData () { this.loading = true; try { const res = await getShiftList({ ...this.search, page_no: this.pagination.current, page_size: this.pagination.size }); const { list, total } = readPageData(res); this.tableData = list; this.pagination.total = total } finally { this.loading = false } },
+ buildSearchParams () {
+ const params = { ...this.search }
+ if (!Array.isArray(params.create_time) || !params.create_time.length) delete params.create_time
+ return params
+ },
+ async fetchData () { this.loading = true; try { const res = await getShiftList({ ...this.buildSearchParams(), page_no: this.pagination.current, page_size: this.pagination.size }); const { list, total } = readPageData(res); this.tableData = list; this.pagination.total = total } finally { this.loading = false } },
onSearch () { this.pagination.current = 1; this.fetchData() },
onReset () { this.search = { name: '', code: '', create_time: '' }; this.pagination.current = 1; this.fetchData() },
onPageChange (page) { this.pagination.current = page.current; this.pagination.size = page.size; this.fetchData() },
@@ -130,16 +135,47 @@ export default {
openEdit (row) { this.handleType = 'edit'; this.dialogTitle = this.key('edit_shift_plan'); this.editId = row.id; this.formData = { name: row.name, code: row.code, time_range: [row.start_time, row.finish_time], status: Number(row.status), remark: row.remark || '', weekly_rest_days: safeJson(row.weekly_rest_days, []), cycle: safeJson(row.cycle, { unit: 'day', length: 0 }), productionTeamIds: safeJson(row.production_team_ids, []) }; this.shiftsData = safeJson(row.shifts, []); this.dialogVisible = true },
addShiftDetail () { this.shiftsData.push({ name: '', start_time: '', finish_time: '', production_team_id: '' }) },
changeProductionTeam (value, index) { if (!value) return; if (this.shiftsData.some((item, i) => i !== index && item.production_team_id === value)) { this.shiftsData[index].production_team_id = ''; this.$message.warning(this.$t(this.key('production_team_can_only_bind_one_shift'))) } },
+ changeShiftTime (value, index, field) {
+ if (!value) {
+ this.$message.warning(this.$t(this.key('please_select_valid_time')))
+ this.shiftsData[index][field] = ''
+ return
+ }
+ const current = this.shiftsData[index]
+ if (!current.start_time || !current.finish_time) return
+ if (this.hasShiftTimeConflict(current, index)) {
+ this.$message.error(this.$t(this.key('shift_time_conflict')))
+ this.shiftsData[index][field] = ''
+ }
+ },
+ toMinute (value) {
+ const [hour, minute] = String(value || '').split(':').map(Number)
+ return hour * 60 + minute
+ },
+ getShiftRanges (shift) {
+ const start = this.toMinute(shift.start_time)
+ const finish = this.toMinute(shift.finish_time)
+ if (finish < start) return [[start, 24 * 60], [0, finish]]
+ return [[start, finish]]
+ },
+ hasShiftTimeConflict (current, currentIndex) {
+ const currentRanges = this.getShiftRanges(current)
+ return this.shiftsData.some((item, index) => {
+ if (index === currentIndex || !item.start_time || !item.finish_time) return false
+ const ranges = this.getShiftRanges(item)
+ return currentRanges.some(([start, finish]) => ranges.some(([itemStart, itemFinish]) => start < itemFinish && itemStart < finish))
+ })
+ },
validateShifts () { for (let i = 0; i < this.shiftsData.length; i++) { const item = this.shiftsData[i]; if (!item.name) return this.$t(this.key('please_enter_shift_name_row')) + (i + 1); if (!item.start_time) return this.$t(this.key('please_select_shift_start_time_row')) + (i + 1); if (!item.finish_time) return this.$t(this.key('please_select_shift_end_time_row')) + (i + 1) } return '' },
- submitDialog () { this.$refs.form.validate(async valid => { if (!valid) return; const error = this.validateShifts(); if (error) { this.$message.error(error); return } this.submitting = true; try { const payload = { ...this.formData, start_time: this.formData.time_range[0], finish_time: this.formData.time_range[1], is_shift: Number(this.formData.cycle.length) > 0 ? 1 : 0, rest_enabled: this.formData.weekly_rest_days.length !== 7 ? 1 : 0, shiftsData: JSON.stringify(this.shiftsData) }; if (this.handleType === 'create') await createShift(payload); else await editShift({ ...payload, id: this.editId }); this.$message.success(this.$t(this.key('operation_successful'))); this.closeDialog(); this.fetchData() } finally { this.submitting = false } }) },
+ submitDialog () { this.$refs.form.validate(async valid => { if (!valid) return; const error = this.validateShifts(); if (error) { this.$message.error(error); return } if (this.shiftsData.some((item, index) => this.hasShiftTimeConflict(item, index))) { this.$message.error(this.$t(this.key('shift_time_conflict'))); return } this.submitting = true; try { const payload = { ...this.formData, start_time: this.formData.time_range[0], finish_time: this.formData.time_range[1], is_shift: Number(this.formData.cycle.length) > 0 ? 1 : 0, rest_enabled: this.formData.weekly_rest_days.length !== 7 ? 1 : 0, shiftsData: JSON.stringify(this.shiftsData) }; if (this.handleType === 'create') await createShift(payload); else await editShift({ ...payload, id: this.editId }); this.$message.success(this.$t(this.key('operation_successful'))); this.closeDialog(); this.fetchData() } finally { this.submitting = false } }) },
closeDialog () { this.dialogVisible = false; this.formData = this.defaultFormData(); this.shiftsData = []; this.editId = '' },
async handleDelete (row) { const cancelled = await this.$confirmAction({ message: this.key('delete_department_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => deleteShift({ id: [row.id] })); if (cancelled) return; this.$message.success(this.$t(this.key('operation_successful'))); this.fetchData() },
async handleBatchDelete () { if (!this.selectedRows.length) { this.$message.error(this.$t(this.key('please_select_table_data'))); return } const cancelled = await this.$confirmAction({ message: this.key('batch_delete_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => deleteShift({ id: this.selectedRows.map(item => item.id) })); if (cancelled) return; this.$message.success(this.$t(this.key('operation_successful'))); this.fetchData() },
openImport () { this.importFileList = []; this.importRows = []; this.importVisible = true },
async downloadTemplate () { this.importLoading = true; try { const res = await getShiftImportTemplate({}); downloadRename(res, 'xlsx', this.$t(this.key('shift_plan_data_import_template'))) } finally { this.importLoading = false } },
- async onImportFileChange (file) { if (!file || !/\.(xls|xlsx)$/i.test(file.name)) { this.$message.error(this.$t(this.key('upload_format_error'))); return } this.importFileList = [file]; this.importTableLoading = true; try { const rows = await readExcel(file.raw); this.importRows = rows.map(row => ({ name: row[this.$t(this.key('shift_plan_name'))], code: row[this.$t(this.key('shift_plan_code'))], start_time: row[this.$t(this.key('start_time'))], finish_time: row[this.$t(this.key('end_time'))], status: row[this.$t(this.key('status'))], shift_name: row[this.$t(this.key('shift_name'))], shifts: { name: row[this.$t(this.key('shift_name'))], start_time: row[this.$t(this.key('shift_start_time'))], finish_time: row[this.$t(this.key('shift_end_time'))], production_team_id: row[this.$t(this.key('production_team_binding'))] } })) } finally { this.importTableLoading = false } },
+ async onImportFileChange (file) { if (!file || !/\.(xls|xlsx)$/i.test(file.name)) { this.$message.error(this.$t(this.key('upload_format_error'))); return } this.importFileList = [file]; this.importTableLoading = true; try { const rows = await readExcel(file.raw); this.importRows = rows.map(row => { const shift = { name: row[this.$t(this.key('shift_name'))], start_time: row[this.$t(this.key('shift_start_time'))], finish_time: row[this.$t(this.key('shift_end_time'))], production_team_id: row[this.$t(this.key('production_team_binding'))] }; return { name: row[this.$t(this.key('shift_plan_name'))], code: row[this.$t(this.key('shift_plan_code'))], start_time: row[this.$t(this.key('start_time'))], finish_time: row[this.$t(this.key('end_time'))], production_team_ids: row[this.$t(this.key('production_team'))], cycle: { length: row[this.$t(this.key('rotation_count'))], unit: row[this.$t(this.key('rotation_unit'))] }, cycle_length: row[this.$t(this.key('rotation_count'))], cycle_unit: row[this.$t(this.key('rotation_unit'))], weekly_rest_days: row[this.$t(this.key('rest_day_setting'))], status: row[this.$t(this.key('status'))], shift_name: shift.name, shift_start_time: shift.start_time, shift_finish_time: shift.finish_time, production_team_id: shift.production_team_id, shifts: shift } }) } finally { this.importTableLoading = false } },
async submitImport () { if (!this.importRows.length) { this.$message.error(this.$t(this.key('please_import_department_data'))); return } await importShiftData({ import_data: JSON.stringify(this.importRows) }); this.$message.success(this.$t(this.key('operation_successful'))); this.importVisible = false; this.fetchData() },
- async handleExport () { const cancelled = await this.$confirmAction({ message: this.key('export_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => exportShiftTask({ ...this.search, action: 'download' })); if (cancelled) return; this.$message.success(this.$t(this.key('download_task_created'))) }
+ async handleExport () { const cancelled = await this.$confirmAction({ message: this.key('export_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => exportShiftTask({ ...this.buildSearchParams(), action: 'download' })); if (cancelled) return; this.$message.success(this.$t(this.key('download_task_created'))) }
}
}
diff --git a/src/views/production-master-data/team-model/team-management/index.vue b/src/views/production-master-data/team-model/team-management/index.vue
index 1371e0bd..c3b40cd3 100644
--- a/src/views/production-master-data/team-model/team-management/index.vue
+++ b/src/views/production-master-data/team-model/team-management/index.vue
@@ -62,6 +62,17 @@
{{ $t(key('delete')) }}
+
{{ $t(key('cancel')) }}
{{ $t(key('confirm')) }}
@@ -134,6 +145,7 @@ export default {
leaderIndex: undefined,
formData: { name: '', area_id: '', line_id: '' },
membersData: [],
+ memberPagination: { current: 1, size: 8, total: 0 },
importVisible: false,
importFileList: [],
importRows: [],
@@ -195,10 +207,15 @@ export default {
},
onSearchAreaChange (areaId) { this.search.line_id = ''; this.loadLines(areaId, 'searchLineOptions') },
onFormAreaChange (areaId) { this.formData.line_id = ''; this.loadLines(areaId, 'formLineOptions') },
+ buildSearchParams () {
+ const params = { ...this.search }
+ if (!Array.isArray(params.create_time) || !params.create_time.length) delete params.create_time
+ return params
+ },
async fetchData () {
this.loading = true
try {
- const res = await getTeamList({ ...this.search, page_no: this.pagination.current, page_size: this.pagination.size })
+ const res = await getTeamList({ ...this.buildSearchParams(), page_no: this.pagination.current, page_size: this.pagination.size })
const { list, total } = readPageData(res)
this.tableData = list
this.pagination.total = total
@@ -212,12 +229,30 @@ export default {
this.handleType = 'edit'; this.dialogTitle = this.key('edit_team'); this.editId = row.id
this.formData = { name: row.name, area_id: row.area_id, line_id: row.line_id }
await this.loadLines(row.area_id, 'formLineOptions')
- const res = await getTeamMemberList({ production_team_id: row.id, page_no: 1, page_size: 10000 })
+ this.memberPagination.current = 1
+ await this.loadMembers()
+ this.dialogVisible = true
+ },
+ async loadMembers () {
+ const res = await getTeamMemberList({
+ production_team_id: this.editId,
+ page_no: this.memberPagination.current,
+ page_size: this.memberPagination.size
+ })
const { list } = readPageData(res)
this.membersData = list.map(item => ({ ...item, is_main: Number(item.is_main) }))
+ this.memberPagination.total = readPageData(res).total
this.leaderIndex = this.membersData.findIndex(item => Number(item.is_main) === 1)
if (this.leaderIndex < 0) this.leaderIndex = undefined
- this.dialogVisible = true
+ },
+ onMemberSizeChange (size) {
+ this.memberPagination.size = size
+ this.memberPagination.current = 1
+ if (this.editId) this.loadMembers()
+ },
+ onMemberCurrentChange (current) {
+ this.memberPagination.current = current
+ if (this.editId) this.loadMembers()
},
addMember () { this.membersData.push({ user_id: '', is_main: 0 }) },
onLeaderChange (val, index) {
@@ -228,7 +263,12 @@ export default {
else if (Number(val) === 1) this.leaderIndex = index
},
async deleteMember (row, index) {
- if (row.id) { await deleteTeamMember({ id: [row.id] }); this.$message.success(this.$t(this.key('operation_successful'))) }
+ if (row.id) {
+ await deleteTeamMember({ id: [row.id] })
+ this.$message.success(this.$t(this.key('operation_successful')))
+ await this.loadMembers()
+ return
+ }
this.membersData.splice(index, 1)
},
submitDialog () {
@@ -244,7 +284,15 @@ export default {
} finally { this.submitting = false }
})
},
- closeDialog () { this.dialogVisible = false; this.formData = { name: '', area_id: '', line_id: '' }; this.membersData = []; this.formLineOptions = []; this.leaderIndex = undefined; this.editId = '' },
+ closeDialog () {
+ this.dialogVisible = false
+ this.formData = { name: '', area_id: '', line_id: '' }
+ this.membersData = []
+ this.formLineOptions = []
+ this.memberPagination = { current: 1, size: 8, total: 0 }
+ this.leaderIndex = undefined
+ this.editId = ''
+ },
async handleDelete (row) {
const cancelled = await this.$confirmAction({ message: this.key('delete_team_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => deleteTeam({ id: [row.id] }))
if (cancelled) return
@@ -273,7 +321,7 @@ export default {
this.$message.success(this.$t(this.key('operation_successful'))); this.importVisible = false; this.fetchData()
},
async handleExport () {
- const cancelled = await this.$confirmAction({ message: this.key('export_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => exportTeamTask({ ...this.search, action: 'download' }))
+ const cancelled = await this.$confirmAction({ message: this.key('export_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => exportTeamTask({ ...this.buildSearchParams(), action: 'download' }))
if (cancelled) return
this.$message.success(this.$t(this.key('download_task_created')))
}
@@ -283,5 +331,6 @@ export default {