feat(duty): make duty time range a single-select dict like work plans
Add duty_time_range dictionary with day/night presets, parse typed ranges on ensure, and replace time pickers with allow-create select in the schedule dialog. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8d9f0b259e
commit
b9ff9c9d67
9 files changed
+319
-70
No files matched your search
@@ -88,6 +88,7 @@ def ensure_duty_dict(
|
||||
dict_type=body.dictType,
|
||||
label=body.label,
|
||||
user_id=user_id,
|
||||
preferred_value=body.preferredValue,
|
||||
)
|
||||
db.commit()
|
||||
return success_response(data, message="已保存")
|
||||
|
||||
@@ -15,10 +15,14 @@ SHIFT_LABELS = {
|
||||
}
|
||||
DICT_SHIFT_TYPE = "duty_shift_type"
|
||||
DICT_WORK_PLAN = "duty_work_plan"
|
||||
DICT_TIME_RANGE = "duty_time_range"
|
||||
DEFAULT_SHIFT_TIMES = {
|
||||
SHIFT_MORNING: ("08:00", "18:00", 0),
|
||||
SHIFT_EVENING: ("18:00", "07:00", 1),
|
||||
}
|
||||
# 预置时段 dict_value:start|end|cross_day
|
||||
TIME_RANGE_DAY = "08:00|18:00|0"
|
||||
TIME_RANGE_NIGHT = "18:00|07:00|1"
|
||||
LINK_OPEN = "open"
|
||||
LINK_CLOSED = "closed"
|
||||
SOURCE_ASSIGN = "assign"
|
||||
|
||||
@@ -52,5 +52,6 @@ class DutyGrabBody(BaseModel):
|
||||
|
||||
|
||||
class DutyDictEnsureBody(BaseModel):
|
||||
dictType: Literal["duty_shift_type", "duty_work_plan"]
|
||||
dictType: Literal["duty_shift_type", "duty_work_plan", "duty_time_range"]
|
||||
label: str = Field(min_length=1, max_length=100)
|
||||
preferredValue: Optional[str] = Field(default=None, max_length=64)
|
||||
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar
|
||||
import re
|
||||
import secrets
|
||||
from datetime import date, datetime, time
|
||||
from typing import Any, Dict, List, Optional, Sequence
|
||||
@@ -16,6 +17,7 @@ from app.models.duty_models import (
|
||||
DUTY_STAFF_ROLE_ID,
|
||||
DUTY_STAFF_ROLE_NAME,
|
||||
DICT_SHIFT_TYPE,
|
||||
DICT_TIME_RANGE,
|
||||
DICT_WORK_PLAN,
|
||||
LINK_CLOSED,
|
||||
LINK_OPEN,
|
||||
@@ -94,6 +96,44 @@ def build_dict_value_from_label(label: str) -> str:
|
||||
return base
|
||||
|
||||
|
||||
def _pad_hhmm(h: int, m: int) -> str:
|
||||
return f"{h:02d}:{m:02d}"
|
||||
|
||||
|
||||
def parse_time_range_input(raw: str) -> Dict[str, Any]:
|
||||
"""解析「09:00-17:00」「18:00-次日07:00」等,返回 start/end/crossDay/label/value。"""
|
||||
text = (raw or "").strip().replace("—", "-").replace("–", "-").replace("~", "-")
|
||||
text = re.sub(r"\s+", "", text)
|
||||
if not text:
|
||||
raise DutyBizError("时段不能为空")
|
||||
m = re.match(
|
||||
r"^(\d{1,2}):(\d{2})[-至到](次日)?(\d{1,2}):(\d{2})$",
|
||||
text,
|
||||
)
|
||||
if not m:
|
||||
raise DutyBizError("时段格式须为 09:00-17:00 或 18:00-次日07:00")
|
||||
sh, sm, cross_flag, eh, em = m.groups()
|
||||
try:
|
||||
start_h, start_m = int(sh), int(sm)
|
||||
end_h, end_m = int(eh), int(em)
|
||||
except ValueError as exc:
|
||||
raise DutyBizError("时段格式须为 09:00-17:00 或 18:00-次日07:00") from exc
|
||||
if not (0 <= start_h <= 23 and 0 <= start_m <= 59 and 0 <= end_h <= 23 and 0 <= end_m <= 59):
|
||||
raise DutyBizError("时段格式须为 09:00-17:00 或 18:00-次日07:00")
|
||||
start = _pad_hhmm(start_h, start_m)
|
||||
end = _pad_hhmm(end_h, end_m)
|
||||
cross = 1 if cross_flag or end < start else 0
|
||||
label = f"{start}–次日{end}" if cross else f"{start}–{end}"
|
||||
value = f"{start}|{end}|{cross}"
|
||||
return {
|
||||
"startTime": start,
|
||||
"endTime": end,
|
||||
"crossDay": bool(cross),
|
||||
"label": label,
|
||||
"value": value,
|
||||
}
|
||||
|
||||
|
||||
def ensure_dict_data(
|
||||
db: Session,
|
||||
*,
|
||||
@@ -106,9 +146,15 @@ def ensure_dict_data(
|
||||
label = (label or "").strip()
|
||||
if not label:
|
||||
raise DutyBizError("字典标签不能为空")
|
||||
if dict_type not in (DICT_SHIFT_TYPE, DICT_WORK_PLAN):
|
||||
if dict_type not in (DICT_SHIFT_TYPE, DICT_WORK_PLAN, DICT_TIME_RANGE):
|
||||
raise DutyBizError("不支持的字典类型")
|
||||
|
||||
preferred = (preferred_value or "").strip() or None
|
||||
if dict_type == DICT_TIME_RANGE:
|
||||
parsed = parse_time_range_input(label)
|
||||
label = parsed["label"]
|
||||
preferred = preferred or parsed["value"]
|
||||
|
||||
existing_label = (
|
||||
db.query(SysDictData)
|
||||
.filter(
|
||||
@@ -122,7 +168,21 @@ def ensure_dict_data(
|
||||
if existing_label:
|
||||
return {"label": existing_label.dict_label, "value": existing_label.dict_value}
|
||||
|
||||
value = (preferred_value or "").strip() or build_dict_value_from_label(label)
|
||||
if preferred:
|
||||
by_value = (
|
||||
db.query(SysDictData)
|
||||
.filter(
|
||||
SysDictData.tenant_no == tenant_no,
|
||||
SysDictData.dict_type == dict_type,
|
||||
SysDictData.dict_value == preferred,
|
||||
SysDictData.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if by_value:
|
||||
return {"label": by_value.dict_label, "value": by_value.dict_value}
|
||||
|
||||
value = preferred or build_dict_value_from_label(label)
|
||||
for _ in range(8):
|
||||
clash = (
|
||||
db.query(SysDictData)
|
||||
@@ -850,6 +910,7 @@ __all__ = [
|
||||
"SHIFT_LABELS",
|
||||
"DutyBizError",
|
||||
"parse_hhmm",
|
||||
"parse_time_range_input",
|
||||
"format_hhmm",
|
||||
"format_time_label",
|
||||
"split_work_plans",
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
-- backend/scripts/migrate_20260820_duty_time_range_dict.sql
|
||||
-- 值班排班:值班时段字典(可单选 + allow-create)
|
||||
-- 可重复执行
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO `sys_dict_type` (
|
||||
`id`, `dict_name`, `dict_type`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
'DUTYDICTTYPE00000000000000000003',
|
||||
'值班时段',
|
||||
'duty_time_range',
|
||||
'shanxia2.0',
|
||||
92,
|
||||
'值班排班时段选项',
|
||||
'',
|
||||
'',
|
||||
NOW(),
|
||||
'',
|
||||
NOW(),
|
||||
0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_dict_type`
|
||||
WHERE `dict_type` = 'duty_time_range'
|
||||
AND `tenant_no` = 'shanxia2.0'
|
||||
AND `del_flag` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `sys_dict_data` (
|
||||
`id`, `dict_label`, `dict_value`, `tag_type`, `dict_type`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT * FROM (
|
||||
SELECT
|
||||
'DUTYDICTDATA0000000000000000006' AS id,
|
||||
'08:00–18:00' AS dict_label,
|
||||
'08:00|18:00|0' AS dict_value,
|
||||
'' AS tag_type,
|
||||
'duty_time_range' AS dict_type,
|
||||
'shanxia2.0' AS tenant_no,
|
||||
1 AS order_num,
|
||||
NULL AS remark,
|
||||
'' AS create_by,
|
||||
'' AS create_dept_id,
|
||||
NOW() AS create_time,
|
||||
'' AS update_by,
|
||||
NOW() AS update_time,
|
||||
0 AS del_flag
|
||||
UNION ALL SELECT
|
||||
'DUTYDICTDATA0000000000000000007',
|
||||
'18:00–次日07:00',
|
||||
'18:00|07:00|1',
|
||||
'',
|
||||
'duty_time_range',
|
||||
'shanxia2.0',
|
||||
2,
|
||||
NULL,
|
||||
'',
|
||||
'',
|
||||
NOW(),
|
||||
'',
|
||||
NOW(),
|
||||
0
|
||||
) AS t
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_dict_data` d
|
||||
WHERE d.tenant_no = t.tenant_no
|
||||
AND d.dict_type = t.dict_type
|
||||
AND d.dict_value = t.dict_value
|
||||
AND d.del_flag = 0
|
||||
);
|
||||
@@ -47,6 +47,28 @@ def test_parse_hhmm_invalid_raises():
|
||||
parse_hhmm("08:xx")
|
||||
|
||||
|
||||
def test_parse_time_range_input_day():
|
||||
from app.services.duty_schedule import parse_time_range_input
|
||||
|
||||
r = parse_time_range_input("09:00-17:00")
|
||||
assert r["startTime"] == "09:00"
|
||||
assert r["endTime"] == "17:00"
|
||||
assert r["crossDay"] is False
|
||||
assert r["value"] == "09:00|17:00|0"
|
||||
assert r["label"] == "09:00–17:00"
|
||||
|
||||
|
||||
def test_parse_time_range_input_cross_day():
|
||||
from app.services.duty_schedule import parse_time_range_input
|
||||
|
||||
r = parse_time_range_input("18:00-次日07:00")
|
||||
assert r["startTime"] == "18:00"
|
||||
assert r["endTime"] == "07:00"
|
||||
assert r["crossDay"] is True
|
||||
assert r["value"] == "18:00|07:00|1"
|
||||
assert "次日" in r["label"]
|
||||
|
||||
|
||||
def test_format_time_label_same_day():
|
||||
assert format_time_label(parse_hhmm("08:00"), parse_hhmm("18:00"), False) == "08:00–18:00"
|
||||
|
||||
|
||||
@@ -91,8 +91,9 @@ export function fetchDutyUserOptions() {
|
||||
}
|
||||
|
||||
export function ensureDutyDict(data: {
|
||||
dictType: 'duty_shift_type' | 'duty_work_plan'
|
||||
dictType: 'duty_shift_type' | 'duty_work_plan' | 'duty_time_range'
|
||||
label: string
|
||||
preferredValue?: string
|
||||
}) {
|
||||
return request.post<{ label: string; value: string }>({
|
||||
url: '/web/admin/duty/dict/ensure',
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
v-model="dialogVisible"
|
||||
class="dialog-form-layout duty-day-dialog"
|
||||
:title="`配置 · ${editingDate}`"
|
||||
width="880px"
|
||||
width="960px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@@ -221,63 +221,29 @@
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="slot.enabled">
|
||||
<div class="form-grid-2 slot-fields-row">
|
||||
<el-form-item label="开始时间">
|
||||
<el-time-picker
|
||||
v-model="slot.startTime"
|
||||
value-format="HH:mm"
|
||||
format="HH:mm"
|
||||
placeholder="可选"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@change="() => syncCrossDay(slot)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间">
|
||||
<el-time-picker
|
||||
v-model="slot.endTime"
|
||||
value-format="HH:mm"
|
||||
format="HH:mm"
|
||||
placeholder="可选"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@change="() => syncCrossDay(slot)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="跨日">
|
||||
<el-checkbox v-model="slot.crossDay">结束时间跨日</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="工作安排" class="form-col-span-2">
|
||||
<div class="slot-fields-row">
|
||||
<el-form-item label="值班时段">
|
||||
<el-select
|
||||
:model-value="slot.workPlans"
|
||||
multiple
|
||||
:model-value="slot.timeRangeValue"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
placeholder="选择或输入工作安排"
|
||||
placeholder="选择或输入,如 09:00-17:00 / 18:00-次日07:00"
|
||||
style="width: 100%"
|
||||
@change="(v) => onWorkPlansChange(slot, v as string[])"
|
||||
@change="(v) => onTimeRangeChange(slot, v as string)"
|
||||
>
|
||||
<el-option
|
||||
v-for="o in workPlanOptions"
|
||||
v-for="o in timeRangeOptions"
|
||||
:key="o.value"
|
||||
:label="o.label"
|
||||
:value="o.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="slot.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="可选,填写备注(与工作安排分离)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="值班安排" class="form-col-span-2">
|
||||
</div>
|
||||
<div class="form-grid-3 slot-fields-row">
|
||||
<el-form-item label="值班安排">
|
||||
<el-select
|
||||
v-model="slot.pendingUserId"
|
||||
filterable
|
||||
@@ -294,6 +260,34 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="工作安排">
|
||||
<el-select
|
||||
:model-value="slot.workPlans"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
placeholder="选择或输入"
|
||||
style="width: 100%"
|
||||
@change="(v) => onWorkPlansChange(slot, v as string[])"
|
||||
>
|
||||
<el-option
|
||||
v-for="o in workPlanOptions"
|
||||
:key="o.value"
|
||||
:label="o.label"
|
||||
:value="o.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="slot.remark"
|
||||
maxlength="500"
|
||||
clearable
|
||||
placeholder="可选备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="person-tags">
|
||||
<el-tag
|
||||
@@ -421,14 +415,19 @@
|
||||
const userOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const shiftTypeOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const workPlanOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const timeRangeOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const addShiftDraft = ref('')
|
||||
|
||||
const TIME_RANGE_DAY = '08:00|18:00|0'
|
||||
const TIME_RANGE_NIGHT = '18:00|07:00|1'
|
||||
|
||||
type FormSlot = {
|
||||
shiftType: string
|
||||
enabled: boolean
|
||||
capacity: number
|
||||
workPlans: string[]
|
||||
remark: string
|
||||
timeRangeValue: string | null
|
||||
startTime: string | null
|
||||
endTime: string | null
|
||||
crossDay: boolean
|
||||
@@ -436,12 +435,60 @@
|
||||
assignments: DutySlotItem['assignments']
|
||||
}
|
||||
|
||||
function defaultTimesFor(shiftType: string) {
|
||||
if (shiftType === 'morning') return { startTime: '08:00', endTime: '18:00', crossDay: false }
|
||||
if (shiftType === 'evening') return { startTime: '18:00', endTime: '07:00', crossDay: true }
|
||||
function encodeTimeRange(
|
||||
start: string | null | undefined,
|
||||
end: string | null | undefined,
|
||||
crossDay: boolean
|
||||
) {
|
||||
if (!start || !end) return null
|
||||
return `${start}|${end}|${crossDay ? 1 : 0}`
|
||||
}
|
||||
|
||||
function decodeTimeRange(value: string | null | undefined) {
|
||||
const raw = String(value || '').trim()
|
||||
if (!raw) {
|
||||
return { startTime: null as string | null, endTime: null as string | null, crossDay: false }
|
||||
}
|
||||
const parts = raw.split('|')
|
||||
if (parts.length >= 3) {
|
||||
return {
|
||||
startTime: parts[0] || null,
|
||||
endTime: parts[1] || null,
|
||||
crossDay: parts[2] === '1'
|
||||
}
|
||||
}
|
||||
return { startTime: null as string | null, endTime: null as string | null, crossDay: false }
|
||||
}
|
||||
|
||||
function formatTimeRangeLabel(start: string, end: string, crossDay: boolean) {
|
||||
return crossDay ? `${start}–次日${end}` : `${start}–${end}`
|
||||
}
|
||||
|
||||
function defaultTimesFor(shiftType: string) {
|
||||
if (shiftType === 'morning') {
|
||||
return {
|
||||
timeRangeValue: TIME_RANGE_DAY,
|
||||
startTime: '08:00',
|
||||
endTime: '18:00',
|
||||
crossDay: false
|
||||
}
|
||||
}
|
||||
if (shiftType === 'evening') {
|
||||
return {
|
||||
timeRangeValue: TIME_RANGE_NIGHT,
|
||||
startTime: '18:00',
|
||||
endTime: '07:00',
|
||||
crossDay: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
timeRangeValue: null as string | null,
|
||||
startTime: null as string | null,
|
||||
endTime: null as string | null,
|
||||
crossDay: false
|
||||
}
|
||||
}
|
||||
|
||||
function blankFormSlots(): FormSlot[] {
|
||||
return PRESET_TYPES.map((t) => ({
|
||||
shiftType: t,
|
||||
@@ -714,9 +761,10 @@
|
||||
|
||||
async function loadDictOptions() {
|
||||
try {
|
||||
const [shifts, plans] = await Promise.all([
|
||||
const [shifts, plans, times] = await Promise.all([
|
||||
fetchDictDataOptions('duty_shift_type'),
|
||||
fetchDictDataOptions('duty_work_plan')
|
||||
fetchDictDataOptions('duty_work_plan'),
|
||||
fetchDictDataOptions('duty_time_range')
|
||||
])
|
||||
shiftTypeOptions.value = (shifts || []).map((x) => ({
|
||||
label: x.label,
|
||||
@@ -726,9 +774,14 @@
|
||||
label: x.label,
|
||||
value: x.value
|
||||
}))
|
||||
timeRangeOptions.value = (times || []).map((x) => ({
|
||||
label: x.label,
|
||||
value: x.value
|
||||
}))
|
||||
} catch {
|
||||
shiftTypeOptions.value = []
|
||||
workPlanOptions.value = []
|
||||
timeRangeOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +829,16 @@
|
||||
fs.startTime = found.startTime || null
|
||||
fs.endTime = found.endTime || null
|
||||
fs.crossDay = !!found.crossDay
|
||||
const encoded = encodeTimeRange(fs.startTime, fs.endTime, fs.crossDay)
|
||||
fs.timeRangeValue = encoded
|
||||
if (encoded && !timeRangeOptions.value.some((o) => o.value === encoded)) {
|
||||
timeRangeOptions.value.push({
|
||||
label:
|
||||
found.timeLabel ||
|
||||
formatTimeRangeLabel(fs.startTime || '', fs.endTime || '', fs.crossDay),
|
||||
value: encoded
|
||||
})
|
||||
}
|
||||
fs.pendingUserId = ''
|
||||
fs.assignments = found ? [...(found.assignments || [])] : []
|
||||
}
|
||||
@@ -809,28 +872,47 @@
|
||||
addShiftDraft.value = ''
|
||||
}
|
||||
|
||||
function syncCrossDay(slot: FormSlot) {
|
||||
const start = slot.startTime || ''
|
||||
const end = slot.endTime || ''
|
||||
if (start && end && end < start) {
|
||||
slot.crossDay = true
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveDictValue(
|
||||
dictType: 'duty_shift_type' | 'duty_work_plan',
|
||||
dictType: 'duty_shift_type' | 'duty_work_plan' | 'duty_time_range',
|
||||
raw: string,
|
||||
options: Array<{ label: string; value: string }>
|
||||
options: Array<{ label: string; value: string }>,
|
||||
preferredValue?: string
|
||||
) {
|
||||
const existing = options.find((o) => o.value === raw || o.label === raw)
|
||||
if (existing) return existing.value
|
||||
const res = await ensureDutyDict({ dictType, label: raw })
|
||||
const res = await ensureDutyDict({
|
||||
dictType,
|
||||
label: raw,
|
||||
preferredValue
|
||||
})
|
||||
if (!options.some((o) => o.value === res.value)) {
|
||||
options.push({ label: res.label || raw, value: res.value })
|
||||
}
|
||||
return res.value
|
||||
}
|
||||
|
||||
async function onTimeRangeChange(slot: FormSlot, raw: string) {
|
||||
const text = String(raw || '').trim()
|
||||
if (!text) {
|
||||
slot.timeRangeValue = null
|
||||
slot.startTime = null
|
||||
slot.endTime = null
|
||||
slot.crossDay = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
const value = await resolveDictValue('duty_time_range', text, timeRangeOptions.value)
|
||||
slot.timeRangeValue = value
|
||||
const decoded = decodeTimeRange(value)
|
||||
slot.startTime = decoded.startTime
|
||||
slot.endTime = decoded.endTime
|
||||
slot.crossDay = decoded.crossDay
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '时段无效,请输入如 09:00-17:00 或 18:00-次日07:00')
|
||||
slot.timeRangeValue = null
|
||||
}
|
||||
}
|
||||
|
||||
async function onWorkPlansChange(slot: FormSlot, values: string[]) {
|
||||
const next: string[] = []
|
||||
for (const raw of values || []) {
|
||||
@@ -1442,11 +1524,14 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.slot-fields-row {
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 10px;
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.slot-fields-row + .slot-fields-row {
|
||||
margin-top: 0;
|
||||
}
|
||||
.cap-field {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -76,7 +76,9 @@
|
||||
<p v-if="slot.workPlanLabels?.length" class="slot-remark">
|
||||
工作安排:<strong>{{ slot.workPlanLabels.join('、') }}</strong>
|
||||
</p>
|
||||
<p v-if="slot.remark" class="slot-note">备注:{{ slot.remark }}</p>
|
||||
<p v-if="slot.remark" class="slot-note">
|
||||
备注:<strong>{{ slot.remark }}</strong>
|
||||
</p>
|
||||
<div class="bar">
|
||||
<i :style="{ width: barWidth(slot) }" :class="dotClass(slot.shiftType)"></i>
|
||||
</div>
|
||||
@@ -421,13 +423,11 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
.slot-time strong,
|
||||
.slot-remark strong {
|
||||
.slot-remark strong,
|
||||
.slot-note strong {
|
||||
font-weight: 600;
|
||||
color: #1f2a26;
|
||||
}
|
||||
.slot-note {
|
||||
color: #8a9a93;
|
||||
}
|
||||
.bar {
|
||||
height: 7px;
|
||||
margin-top: 8px;
|
||||
|
||||
Reference in new issue
Block a user