| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665 |
- <template>
- <ContentWrap>
- <div class="fixed-save-bar">
- <el-button type="warning" size="default" :loading="submitting" @click="handleSubmit">
- 保存设置
- </el-button>
- </div>
- <el-form
- ref="formRef"
- :model="formData"
- :rules="formRules"
- label-width="120px"
- class="setting-form"
- >
- <el-row :gutter="20" class="main-row">
- <el-col :span="12" class="col-left">
- <div class="card card-top-left">
- <div class="card-header">
- <el-icon class="card-icon icon-blue"><Setting /></el-icon>
- <span class="card-title">基础设置</span>
- <div class="master-switch">
- <span class="switch-label">预约总开关</span>
- <el-switch
- v-model="formData.appointmentEnabled"
- active-text="开启"
- inactive-text="关闭"
- />
- </div>
- </div>
- <div class="card-body">
- <el-form-item label="放号规则">
- <el-radio-group v-model="formData.ruleType" :disabled="isDisabled">
- <el-radio :value="1">时间循环预约</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item v-show="false" label="开始预约日" prop="startDay">
- <el-select
- v-model="formData.startDay"
- placeholder="请选择"
- style="width: 100%"
- :disabled="isDisabled"
- >
- <el-option label="当天" :value="0" />
- <el-option label="未来1天" :value="1" />
- <el-option label="未来2天" :value="2" />
- <el-option label="未来3天" :value="3" />
- <el-option label="未来4天" :value="4" />
- <el-option label="未来5天" :value="5" />
- <el-option label="未来6天" :value="6" />
- <el-option label="未来7天" :value="7" />
- <el-option label="未来8天" :value="8" />
- <el-option label="未来9天" :value="9" />
- <el-option label="未来10天" :value="10" />
- <el-option label="未来11天" :value="11" />
- <el-option label="未来12天" :value="12" />
- <el-option label="未来13天" :value="13" />
- <el-option label="未来14天" :value="14" />
- <el-option label="未来15天" :value="15" />
- </el-select>
- </el-form-item>
- <el-form-item label="提前预约" prop="advanceDays">
- <el-input-number
- v-model="formData.advanceDays"
- :min="1"
- :max="30"
- style="width: 100%"
- :disabled="isDisabled"
- >
- <template #prefix>
- <span>可提前</span>
- </template>
- <template #suffix>
- <span>天预约</span>
- </template>
- </el-input-number>
- </el-form-item>
- </div>
- </div>
- <div class="card card-bottom-left">
- <div class="card-header">
- <el-icon class="card-icon icon-green"><Calendar /></el-icon>
- <span class="card-title">预约组</span>
- <el-button
- type="primary"
- size="small"
- :disabled="isDisabled"
- @click="addAppointmentGroup"
- >
- <el-icon><Plus /></el-icon>
- 添加预约组
- </el-button>
- </div>
- <div class="card-body card-body-groups">
- <div v-if="appointmentGroups.length === 0" class="empty-groups">
- <el-empty description="暂无预约组,点击右上角添加" :image-size="60" />
- </div>
- <div v-else class="group-list">
- <div
- v-for="(group, groupIndex) in appointmentGroups"
- :key="group.id"
- class="group-card"
- >
- <div class="group-header">
- <span class="group-label">预约日期:</span>
- <div class="cycle-buttons">
- <el-button
- :type="group.cycleType === 'daily' ? 'success' : 'default'"
- size="small"
- :disabled="isDisabled || group.enable == 0"
- @click="setCycleType(group, 'daily')"
- >
- 每天
- </el-button>
- <el-button
- :type="group.cycleType === 'weekly' ? 'success' : 'default'"
- size="small"
- :disabled="isDisabled || group.enable == 0"
- @click="openWeeklyDialog(group)"
- >
- 每周
- </el-button>
- <el-button
- :type="group.cycleType === 'monthly' ? 'success' : 'default'"
- size="small"
- :disabled="isDisabled || group.enable == 0"
- @click="openMonthlyDialog(group)"
- >
- 每月
- </el-button>
- <el-button
- :type="group.cycleType === 'custom' ? 'success' : 'default'"
- size="small"
- :disabled="isDisabled || group.enable == 0"
- @click="openCustomDialog(group)"
- >
- 自定义
- </el-button>
- </div>
- <el-switch
- v-model="group.enable"
- active-text="启用"
- size="small"
- :disabled="isDisabled"
- :active-value="1"
- :inactive-value="0"
- style="margin-right: 1vw"
- inactive-text="关闭"
- />
- <el-button
- type="danger"
- link
- size="small"
- :disabled="isDisabled || group.enable == 0"
- @click="removeGroup(groupIndex)"
- >
- <el-icon><Delete /></el-icon>
- 删除组
- </el-button>
- </div>
- <div class="group-dates">
- <template v-if="group.cycleType === 'daily'">
- <el-tag type="success" size="small">每天都可预约</el-tag>
- </template>
- <template v-else-if="group.cycleType === 'weekly'">
- <span class="dates-label">每周可预约:</span>
- <el-tag v-if="group.weeklyDays.length === 0" type="info" size="small"
- >未设置</el-tag
- >
- <el-tag
- v-for="day in group.weeklyDays"
- :key="day"
- type="success"
- size="small"
- class="date-tag"
- >
- {{ getWeekDayLabel(day) }}
- </el-tag>
- </template>
- <template v-else-if="group.cycleType === 'monthly'">
- <span class="dates-label">每月可预约:</span>
- <el-tag v-if="group.monthlyDays.length === 0" type="info" size="small"
- >未设置</el-tag
- >
- <el-tag
- v-for="day in group.monthlyDays"
- :key="day"
- type="success"
- size="small"
- class="date-tag"
- >
- {{ day }}号
- </el-tag>
- </template>
- <template v-else-if="group.cycleType === 'custom'">
- <span class="dates-label">自定义日期:</span>
- <el-tag v-if="group.customDates.length === 0" type="info" size="small"
- >未设置</el-tag
- >
- <el-tag
- v-for="date in group.customDates.slice(0, 5)"
- :key="date"
- type="success"
- size="small"
- class="date-tag"
- >
- {{ date }}
- </el-tag>
- <el-tooltip
- v-if="group.customDates.length > 5"
- :content="group.customDates.join('、')"
- placement="top"
- >
- <el-tag type="info" size="small" class="date-tag">
- +{{ group.customDates.length - 5 }}
- </el-tag>
- </el-tooltip>
- </template>
- </div>
- <div class="group-items-section">
- <div class="items-header">
- <span class="items-title">预约项</span>
- <span class="items-sub">(多项时间段不可重叠)</span>
- </div>
- <div v-if="group.items.length === 0" class="empty-items">
- 暂无预约项,点击下方按钮添加
- </div>
- <div v-else class="items-list">
- <div v-for="(item, itemIndex) in group.items" :key="item.id" class="item-row">
- <div
- class="item-delete-btn"
- :class="{ disabled: isDisabled || group.enable == 0 }"
- @click="removeItem(group, itemIndex)"
- >
- <el-icon><Delete /></el-icon>
- </div>
- <div class="item-fields">
- <div class="field-block">
- <span class="field-label">时间段</span>
- <el-time-picker
- v-model="item.startTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="开始时间"
- size="small"
- style="width: 110px"
- :disabled="isDisabled || group.enable == 0"
- @change="checkTimeOverlap(group, itemIndex)"
- />
- <span class="time-sep">至</span>
- <el-time-picker
- v-model="item.endTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="结束时间"
- size="small"
- style="width: 110px"
- :disabled="isDisabled || group.enable == 0"
- @change="checkTimeOverlap(group, itemIndex)"
- />
- </div>
- <div class="field-block">
- <span class="field-label">限制人数</span>
- <el-input-number
- v-model="item.limitCount"
- :min="1"
- :max="9999"
- size="small"
- style="width: 110px"
- :disabled="isDisabled || group.enable == 0"
- />
- </div>
- </div>
- </div>
- </div>
- <div class="add-item-row">
- <el-button
- type="success"
- size="small"
- :disabled="isDisabled || group.enable == 0"
- @click="addItem(group)"
- >
- <el-icon><Plus /></el-icon>
- 添加预约项
- </el-button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </el-col>
- <el-col :span="12" class="col-right">
- <div class="card card-mid-right">
- <div class="card-header">
- <el-icon class="card-icon icon-green"><Tickets /></el-icon>
- <span class="card-title">放号时间段(每天可开始预约时间)</span>
- </div>
- <div class="card-body card-body-limit" style="padding: 40px 30px">
- <span class="field-label">放号开始时间:</span>
- <el-time-picker
- v-model="formData.startTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="开始时间"
- style="width: 150px"
- :disabled="isDisabled"
- />
- <span class="field-label" style="margin-left: 3vw">放号结束时间:</span>
- <el-time-picker
- v-model="formData.endTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="结束时间"
- style="width: 150px"
- :disabled="isDisabled"
- />
- </div>
- </div>
- <div class="card card-bottom-right">
- <div class="card-header">
- <el-icon class="card-icon icon-purple"><User /></el-icon>
- <span class="card-title">单人名额限制(按自然周和月)</span>
- <el-button
- type="primary"
- size="small"
- :disabled="isDisabled || personLimits.length >= 3"
- @click="openPersonLimitDialog"
- >
- <el-icon><Plus /></el-icon>
- 添加
- </el-button>
- </div>
- <div class="card-body card-body-limit">
- <div v-if="personLimits.length === 0" class="empty-limits-inline">
- <el-empty description="暂无限制,点击右上角添加" :image-size="40" />
- </div>
- <div v-else class="limit-list-inline">
- <div v-for="(limit, index) in personLimits" :key="index" class="limit-item-inline">
- <span class="limit-type-badge">{{ getLimitTypeLabel(limit.type) }}</span>
- <span class="limit-count-num">{{ limit.count }} <em>次</em></span>
- <el-button
- type="danger"
- link
- size="small"
- :disabled="isDisabled"
- @click="removePersonLimit(index)"
- >
- <el-icon><Delete /></el-icon>
- </el-button>
- </div>
- </div>
- </div>
- </div>
- </el-col>
- </el-row>
- </el-form>
- <el-dialog v-model="totalLimitDialogVisible" title="添加总名额限制" width="420px" align-center>
- <el-form :model="totalLimitForm" label-width="100px" style="margin-bottom: 20px">
- <el-form-item label="限制类型" required>
- <el-select v-model="totalLimitForm.type" placeholder="请选择" style="width: 100%">
- <el-option
- v-for="item in availableTotalTypes"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="限制次数" required>
- <el-input-number
- v-model="totalLimitForm.count"
- :min="1"
- :max="9999"
- style="width: 100%"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="totalLimitDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmTotalLimit">确定</el-button>
- </template>
- </el-dialog>
- <el-dialog
- v-model="personLimitDialogVisible"
- title="添加单人名额限制"
- width="420px"
- align-center
- >
- <el-form :model="personLimitForm" label-width="100px" style="margin-bottom: 20px">
- <el-form-item label="限制类型" required>
- <el-select v-model="personLimitForm.type" placeholder="请选择" style="width: 100%">
- <el-option
- v-for="item in availablePersonTypes"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="限制次数" required>
- <el-input-number
- v-model="personLimitForm.count"
- :min="1"
- :max="9999"
- style="width: 100%"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="personLimitDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmPersonLimit">确定</el-button>
- </template>
- </el-dialog>
- <el-dialog
- v-model="weeklyDialogVisible"
- title="选择每周可预约日期"
- width="480px"
- align-center
- class="date-selector-dialog"
- :append-to-body="false"
- >
- <div class="date-selector-body">
- <div class="selector-title">请选择每周可预约的日期</div>
- <div class="week-days-grid">
- <div
- v-for="day in weekDays"
- :key="day.value"
- class="day-cell"
- :class="{ active: weeklySelectedDays.includes(day.value) }"
- @click="toggleWeekDay(day.value)"
- >
- {{ day.label }}
- </div>
- </div>
- </div>
- <template #footer>
- <el-button @click="weeklyDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmWeekly">确定</el-button>
- </template>
- </el-dialog>
- <el-dialog
- v-model="monthlyDialogVisible"
- title="选择每月可预约日期"
- width="560px"
- align-center
- class="date-selector-dialog"
- :append-to-body="false"
- >
- <div class="date-selector-body">
- <div class="selector-title">请选择每月可预约的日期</div>
- <div class="month-days-grid">
- <div
- v-for="day in 31"
- :key="day"
- class="day-cell"
- :class="{ active: monthlySelectedDays.includes(day) }"
- @click="toggleMonthDay(day)"
- >
- {{ day }}
- </div>
- </div>
- </div>
- <template #footer>
- <el-button @click="monthlyDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmMonthly">确定</el-button>
- </template>
- </el-dialog>
- <el-dialog
- v-model="customDialogVisible"
- title="选择自定义日期"
- width="420px"
- align-center
- :append-to-body="false"
- >
- <div class="date-selector-body">
- <div class="selector-title">请选择可预约的日期</div>
- <el-date-picker
- v-model="customSelectedDates"
- type="dates"
- placeholder="选择日期"
- value-format="YYYY-MM-DD"
- style="width: 100%"
- />
- </div>
- <template #footer>
- <el-button @click="customDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmCustom">确定</el-button>
- </template>
- </el-dialog>
- </ContentWrap>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, computed } from 'vue'
- import { Setting, Calendar, User, Plus, Delete, Tickets, Minus } from '@element-plus/icons-vue'
- import type { FormInstance, FormRules } from 'element-plus'
- import { ElMessageBox } from 'element-plus'
- import { useUserStore } from '@/store/modules/user'
- import {
- appointmentConfigADD,
- appointmentConfigGetByTenant,
- appointmentConfigUpdate
- } from '@/api/elderly/elder/outwardRegustration'
- const userStore = useUserStore()
- const message = useMessage()
- const formRef = ref<FormInstance>()
- const submitting = ref(false)
- const tenantIds = ref<number[]>([])
- let organizationId = 0
- let groupIdCounter = 0
- let itemIdCounter = 0
- const configId = ref<number | undefined>(undefined)
- type LimitItem = { type: number; count: number }
- type CycleType = 'daily' | 'weekly' | 'monthly' | 'custom'
- type AppointmentItem = {
- id: number
- // 后端返回的真实 id,新增的 item 没有此字段;提交时回传给后端,删除时进入 deleteIds
- serverId?: number
- startTime: string
- endTime: string
- limitCount: number
- }
- type AppointmentGroup = {
- id: number
- cycleType: CycleType
- enable: number
- weeklyDays: number[]
- monthlyDays: number[]
- customDates: string[]
- items: AppointmentItem[]
- }
- type AppointmentConfigDO = {
- id?: number
- advanceDays?: number
- beginBookDays?: number
- enable?: number
- personDailyLimit?: number
- personMonthlyLimit?: number
- personWeeklyLimit?: number
- tenantId?: number
- totalDailyLimit?: number
- totalMonthlyLimit?: number
- totalWeeklyLimit?: number
- [property: string]: any
- }
- type AppointmentGroupDO = {
- id?: number
- beginTime?: string
- customDates?: string
- dateType?: string
- endTime?: string
- enable?: number
- limitCount?: number
- monthDays?: string
- tenantId?: number
- weekDays?: string
- }
- const limitTypeOptions = [
- { label: '每天', value: 1 },
- { label: '每周', value: 2 },
- { label: '每月', value: 3 }
- ]
- const weekDays = [
- { label: '周一', value: 1 },
- { label: '周二', value: 2 },
- { label: '周三', value: 3 },
- { label: '周四', value: 4 },
- { label: '周五', value: 5 },
- { label: '周六', value: 6 },
- { label: '周日', value: 7 }
- ]
- const limitTypeToFieldMap: Record<number, { total: string; person: string }> = {
- 1: { total: 'totalDailyLimit', person: 'personDailyLimit' },
- 2: { total: 'totalWeeklyLimit', person: 'personWeeklyLimit' },
- 3: { total: 'totalMonthlyLimit', person: 'personMonthlyLimit' }
- }
- const cycleTypeToDateType = (cycleType: CycleType): string => cycleType.toUpperCase()
- const dateTypeToCycleType = (dateType: string): CycleType => {
- const map: Record<string, CycleType> = {
- DAILY: 'daily',
- WEEKLY: 'weekly',
- MONTHLY: 'monthly',
- CUSTOM: 'custom'
- }
- return map[dateType] || 'daily'
- }
- const totalLimits = ref<LimitItem[]>([])
- const personLimits = ref<LimitItem[]>([])
- const appointmentGroups = ref<AppointmentGroup[]>([])
- // 被删除的旧 item 后端 id 列表,提交时一并传给后端做物理删除
- const deleteIds = ref<number[]>([])
- const totalLimitDialogVisible = ref(false)
- const personLimitDialogVisible = ref(false)
- const weeklyDialogVisible = ref(false)
- const monthlyDialogVisible = ref(false)
- const customDialogVisible = ref(false)
- const totalLimitForm = reactive<LimitItem>({ type: 1, count: 1 })
- const personLimitForm = reactive<LimitItem>({ type: 1, count: 1 })
- const currentEditGroup = ref<AppointmentGroup | null>(null)
- const weeklySelectedDays = ref<number[]>([])
- const monthlySelectedDays = ref<number[]>([])
- const customSelectedDates = ref<string[]>([])
- const availableTotalTypes = computed(() => {
- const usedTypes = totalLimits.value.map((item) => item.type)
- return limitTypeOptions.filter((item) => !usedTypes.includes(item.value))
- })
- const availablePersonTypes = computed(() => {
- const usedTypes = personLimits.value.map((item) => item.type)
- return limitTypeOptions.filter((item) => !usedTypes.includes(item.value))
- })
- const isDisabled = computed(() => !formData.appointmentEnabled)
- const getLimitTypeLabel = (type: number) => {
- const found = limitTypeOptions.find((item) => item.value === type)
- return found ? found.label : ''
- }
- const getWeekDayLabel = (day: number) => {
- const found = weekDays.find((item) => item.value === day)
- return found ? found.label : ''
- }
- const defaultFormData = () => ({
- ruleType: 1,
- startDay: 0,
- advanceDays: 6,
- startTime: '08:00',
- endTime: '17:30',
- appointmentEnabled: true
- })
- const formData = reactive(defaultFormData())
- const timeToMinutes = (time: string): number => {
- if (!time) return -1
- const [h, m] = time.split(':').map(Number)
- return h * 60 + m
- }
- const isOverlap = (item1: AppointmentItem, item2: AppointmentItem): boolean => {
- if (!item1.startTime || !item1.endTime || !item2.startTime || !item2.endTime) return false
- const s1 = timeToMinutes(item1.startTime)
- const e1 = timeToMinutes(item1.endTime)
- const s2 = timeToMinutes(item2.startTime)
- const e2 = timeToMinutes(item2.endTime)
- if (s1 < 0 || e1 < 0 || s2 < 0 || e2 < 0) return false
- if (s1 >= e1 || s2 >= e2) return false
- return s1 < e2 && s2 < e1
- }
- const getDateDayOfMonth = (dateStr: string): number => {
- const parts = dateStr.split('-')
- if (parts.length !== 3) return -1
- return Number(parts[2])
- }
- const getDateWeekDay = (dateStr: string): number => {
- const parts = dateStr.split('-').map(Number)
- if (parts.length !== 3) return -1
- const date = new Date(parts[0], parts[1] - 1, parts[2])
- const day = date.getDay()
- return day === 0 ? 7 : day
- }
- const checkTimeOverlap = (group: AppointmentGroup, changedIndex: number) => {
- const changedItem = group.items[changedIndex]
- if (!changedItem.startTime || !changedItem.endTime) return
- if (timeToMinutes(changedItem.startTime) >= timeToMinutes(changedItem.endTime)) {
- message.warning('结束时间必须晚于开始时间')
- return
- }
- for (let i = 0; i < group.items.length; i++) {
- if (i === changedIndex) continue
- if (isOverlap(changedItem, group.items[i])) {
- message.warning('时间段与其他预约项重叠,请调整时间')
- return
- }
- }
- }
- const addAppointmentGroup = () => {
- groupIdCounter++
- itemIdCounter++
- appointmentGroups.value.push({
- id: groupIdCounter,
- cycleType: 'daily',
- enable: 1,
- weeklyDays: [],
- monthlyDays: [],
- customDates: [],
- items: [
- {
- id: itemIdCounter,
- startTime: '08:00',
- endTime: '09:00',
- limitCount: 20
- }
- ]
- })
- }
- const removeGroup = (index: number) => {
- const group = appointmentGroups.value[index]
- // 该组下所有带 serverId 的 item 都是后端旧数据,删除时记录 id 交给后端物理删除
- group.items.forEach((item) => {
- if (item.serverId) {
- deleteIds.value.push(item.serverId)
- }
- })
- appointmentGroups.value.splice(index, 1)
- }
- const addItem = (group: AppointmentGroup) => {
- let lastEnd = '08:00'
- if (group.items.length > 0) {
- const lastItem = group.items[group.items.length - 1]
- if (lastItem.endTime) {
- lastEnd = lastItem.endTime
- }
- }
- const lastEndMin = timeToMinutes(lastEnd)
- let newStart = lastEnd
- let newEnd = ''
- if (lastEndMin >= 0) {
- const newStartMin = Math.min(lastEndMin + 60, 23 * 60)
- const newEndMin = Math.min(newStartMin + 60, 24 * 60)
- const sh = Math.floor(newStartMin / 60)
- .toString()
- .padStart(2, '0')
- const sm = (newStartMin % 60).toString().padStart(2, '0')
- const eh = Math.floor(newEndMin / 60)
- .toString()
- .padStart(2, '0')
- const em = (newEndMin % 60).toString().padStart(2, '0')
- newStart = `${sh}:${sm}`
- newEnd = `${eh}:${em}`
- }
- itemIdCounter++
- group.items.push({
- id: itemIdCounter,
- startTime: newStart,
- endTime: newEnd,
- limitCount: 20
- })
- }
- const removeItem = (group: AppointmentGroup, index: number) => {
- const item = group.items[index]
- // 带 serverId 表示是后端旧数据,删除时记录 id 交给后端物理删除
- if (item.serverId) {
- deleteIds.value.push(item.serverId)
- }
- group.items.splice(index, 1)
- }
- const setCycleType = (group: AppointmentGroup, cycleType: CycleType) => {
- if (cycleType === 'daily') {
- group.cycleType = 'daily'
- }
- }
- const openWeeklyDialog = (group: AppointmentGroup) => {
- currentEditGroup.value = group
- weeklySelectedDays.value = [...group.weeklyDays]
- weeklyDialogVisible.value = true
- }
- const toggleWeekDay = (day: number) => {
- const idx = weeklySelectedDays.value.indexOf(day)
- if (idx > -1) {
- weeklySelectedDays.value.splice(idx, 1)
- } else {
- weeklySelectedDays.value.push(day)
- weeklySelectedDays.value.sort((a, b) => a - b)
- }
- }
- const confirmWeekly = () => {
- if (currentEditGroup.value) {
- currentEditGroup.value.weeklyDays = [...weeklySelectedDays.value]
- currentEditGroup.value.cycleType = 'weekly'
- }
- weeklyDialogVisible.value = false
- currentEditGroup.value = null
- weeklySelectedDays.value = []
- }
- const openMonthlyDialog = (group: AppointmentGroup) => {
- currentEditGroup.value = group
- monthlySelectedDays.value = [...group.monthlyDays]
- monthlyDialogVisible.value = true
- }
- const toggleMonthDay = (day: number) => {
- const idx = monthlySelectedDays.value.indexOf(day)
- if (idx > -1) {
- monthlySelectedDays.value.splice(idx, 1)
- } else {
- monthlySelectedDays.value.push(day)
- monthlySelectedDays.value.sort((a, b) => a - b)
- }
- }
- const confirmMonthly = () => {
- if (currentEditGroup.value) {
- currentEditGroup.value.monthlyDays = [...monthlySelectedDays.value]
- currentEditGroup.value.cycleType = 'monthly'
- }
- monthlyDialogVisible.value = false
- currentEditGroup.value = null
- monthlySelectedDays.value = []
- }
- const openCustomDialog = (group: AppointmentGroup) => {
- currentEditGroup.value = group
- customSelectedDates.value = [...group.customDates]
- customDialogVisible.value = true
- }
- const confirmCustom = () => {
- if (currentEditGroup.value) {
- currentEditGroup.value.customDates = [...customSelectedDates.value]
- currentEditGroup.value.cycleType = 'custom'
- }
- customDialogVisible.value = false
- currentEditGroup.value = null
- customSelectedDates.value = []
- }
- const openTotalLimitDialog = () => {
- if (availableTotalTypes.value.length === 0) return
- totalLimitForm.type = availableTotalTypes.value[0].value
- totalLimitForm.count = 1
- totalLimitDialogVisible.value = true
- }
- const confirmTotalLimit = () => {
- if (!totalLimitForm.type || !totalLimitForm.count) {
- message.warning('请填写完整信息')
- return
- }
- totalLimits.value.push({ type: totalLimitForm.type, count: totalLimitForm.count })
- totalLimitDialogVisible.value = false
- }
- const removeTotalLimit = (index: number) => {
- totalLimits.value.splice(index, 1)
- }
- const openPersonLimitDialog = () => {
- if (availablePersonTypes.value.length === 0) return
- personLimitForm.type = availablePersonTypes.value[0].value
- personLimitForm.count = 1
- personLimitDialogVisible.value = true
- }
- const confirmPersonLimit = () => {
- if (!personLimitForm.type || !personLimitForm.count) {
- message.warning('请填写完整信息')
- return
- }
- personLimits.value.push({ type: personLimitForm.type, count: personLimitForm.count })
- personLimitDialogVisible.value = false
- }
- const removePersonLimit = (index: number) => {
- personLimits.value.splice(index, 1)
- }
- const validatePersonLimits = (): string | null => {
- const daily = personLimits.value.find((item) => item.type === 1)
- const weekly = personLimits.value.find((item) => item.type === 2)
- const monthly = personLimits.value.find((item) => item.type === 3)
- if (daily && daily.count <= 0) {
- return '单人每日名额限制必须大于0'
- }
- if (weekly && weekly.count <= 0) {
- return '单人每周名额限制必须大于0'
- }
- if (monthly && monthly.count <= 0) {
- return '单人每月名额限制必须大于0'
- }
- if (monthly && weekly && monthly.count <= weekly.count) {
- return '单人每月名额限制必须大于每周名额限制'
- }
- if (weekly && daily && weekly.count <= daily.count) {
- return '单人每周名额限制必须大于每日名额限制'
- }
- return null
- }
- const validateAdvanceDays = (_rule: any, value: any, callback: any) => {
- if (!value || value < 1) {
- callback(new Error('提前预约天数最少为1'))
- } else {
- callback()
- }
- }
- const formRules: FormRules = reactive({
- ruleType: [{ required: true, message: '请选择放号规则', trigger: 'change' }],
- startDay: [{ required: true, message: '请选择开始预约日', trigger: 'change' }],
- advanceDays: [
- { required: true, message: '请输入提前预约天数', trigger: 'change' },
- { validator: validateAdvanceDays, trigger: 'change' }
- ]
- })
- const changeTen = (i: any[]) => {
- if (i.length > 1) {
- message.error('只能选择一个机构!')
- if (i[0].length > 1) {
- tenantIds.value = [i[0][1]]
- } else {
- tenantIds.value = []
- }
- }
- if (tenantIds.value.length > 0) {
- organizationId = tenantIds.value[0]
- getSetting()
- }
- }
- const parseGroups = (groups: AppointmentGroupDO[]): AppointmentGroup[] => {
- const map = new Map<string, AppointmentGroup>()
- groups.forEach((g) => {
- const cycleType = dateTypeToCycleType(g.dateType || 'DAILY')
- const weekDays = g.weekDays || ''
- const monthDays = g.monthDays || ''
- const customDates = g.customDates || ''
- const enable = g.enable ?? 1
- const key = `${cycleType}_${weekDays}_${monthDays}_${customDates}`
- if (!map.has(key)) {
- groupIdCounter++
- map.set(key, {
- id: groupIdCounter,
- cycleType,
- enable,
- weeklyDays: weekDays ? weekDays.split(',').map(Number) : [],
- monthlyDays: monthDays ? monthDays.split(',').map(Number) : [],
- customDates: customDates ? customDates.split(',') : [],
- items: []
- })
- }
- const group = map.get(key)!
- itemIdCounter++
- group.items.push({
- id: itemIdCounter,
- // 保留后端真实 id,用于更新时回传和删除时入 deleteIds
- serverId: g.id,
- startTime: g.beginTime || '',
- endTime: g.endTime || '',
- limitCount: g.limitCount || 100
- })
- })
- return Array.from(map.values())
- }
- const getSetting = async () => {
- try {
- const res = await appointmentConfigGetByTenant({ tenantId: organizationId })
- if (res && res.config) {
- const config = res.config
- configId.value = config.id
- let startTime = defaultFormData().startTime
- let endTime = defaultFormData().endTime
- if (config.openTimeSlot && typeof config.openTimeSlot === 'string') {
- const [s, e] = config.openTimeSlot.split('-')
- if (s && e) {
- startTime = s
- endTime = e
- }
- }
- Object.assign(formData, {
- ruleType: 1,
- startDay: config.beginBookDays ?? 0,
- advanceDays: config.advanceDays ?? 0,
- appointmentEnabled: config.enabled === 1,
- startTime,
- endTime
- })
- totalLimits.value = []
- personLimits.value = []
- ;[1, 2, 3].forEach((type) => {
- const field = limitTypeToFieldMap[type]
- const totalVal = config[field.total]
- if (totalVal !== undefined && totalVal !== null && totalVal > 0) {
- totalLimits.value.push({ type, count: totalVal })
- }
- const personVal = config[field.person]
- if (personVal !== undefined && personVal !== null && personVal > 0) {
- personLimits.value.push({ type, count: personVal })
- }
- })
- groupIdCounter = 0
- itemIdCounter = 0
- // 重新拉取配置后,本地 deleteIds 已无意义,清空避免脏数据
- deleteIds.value = []
- appointmentGroups.value = parseGroups(res.group || [])
- if (appointmentGroups.value.length > 0) {
- groupIdCounter = Math.max(...appointmentGroups.value.map((g) => g.id))
- let maxItemId = 0
- appointmentGroups.value.forEach((g) => {
- if (g.items && g.items.length > 0) {
- maxItemId = Math.max(maxItemId, ...g.items.map((it) => it.id))
- }
- })
- itemIdCounter = maxItemId
- }
- } else {
- configId.value = undefined
- Object.assign(formData, defaultFormData())
- totalLimits.value = []
- personLimits.value = []
- appointmentGroups.value = []
- }
- } catch (e) {
- console.log('获取设置失败', e)
- }
- }
- const validateGroups = (): string | null => {
- if (appointmentGroups.value.length === 0) {
- return '请至少添加一个预约组'
- }
- const dailyGroups = appointmentGroups.value.filter((g) => g.cycleType === 'daily')
- if (dailyGroups.length > 0 && appointmentGroups.value.length > 1) {
- return '选择每天时,只能有一个预约组'
- }
- const weeklyGroups = appointmentGroups.value.filter((g) => g.cycleType === 'weekly')
- const monthlyGroups = appointmentGroups.value.filter((g) => g.cycleType === 'monthly')
- if (weeklyGroups.length > 0 && monthlyGroups.length > 0) {
- return '每周和每月预约组不能同时存在'
- }
- if (weeklyGroups.length > 1) {
- const usedDays = new Set<number>()
- for (const group of weeklyGroups) {
- for (const day of group.weeklyDays) {
- if (usedDays.has(day)) {
- return '不同每周预约组存在重复的星期'
- }
- usedDays.add(day)
- }
- }
- }
- if (monthlyGroups.length > 1) {
- const usedDays = new Set<number>()
- for (const group of monthlyGroups) {
- for (const day of group.monthlyDays) {
- if (usedDays.has(day)) {
- return '不同每月预约组存在重复的日期'
- }
- usedDays.add(day)
- }
- }
- }
- const customGroups = appointmentGroups.value.filter((g) => g.cycleType === 'custom')
- if (customGroups.length > 1) {
- const usedDates = new Set<string>()
- for (const group of customGroups) {
- for (const date of group.customDates) {
- if (usedDates.has(date)) {
- return '不同自定义预约组存在重复的日期'
- }
- usedDates.add(date)
- }
- }
- }
- if (monthlyGroups.length > 0 && customGroups.length > 0) {
- const usedDays = new Set<number>()
- for (const group of monthlyGroups) {
- for (const day of group.monthlyDays) {
- usedDays.add(day)
- }
- }
- for (const group of customGroups) {
- for (const date of group.customDates) {
- const day = getDateDayOfMonth(date)
- if (day > 0 && usedDays.has(day)) {
- return `自定义日期 ${date} 与每月预约的 ${day}号冲突`
- }
- }
- }
- }
- if (weeklyGroups.length > 0 && customGroups.length > 0) {
- const usedDays = new Set<number>()
- for (const group of weeklyGroups) {
- for (const day of group.weeklyDays) {
- usedDays.add(day)
- }
- }
- for (const group of customGroups) {
- for (const date of group.customDates) {
- const day = getDateWeekDay(date)
- if (day > 0 && usedDays.has(day)) {
- return `自定义日期 ${date} 与每周预约冲突`
- }
- }
- }
- }
- for (const group of appointmentGroups.value) {
- if (group.cycleType === 'weekly' && group.weeklyDays.length === 0) {
- return '存在未设置每周可预约日期的预约组'
- }
- if (group.cycleType === 'monthly' && group.monthlyDays.length === 0) {
- return '存在未设置每月可预约日期的预约组'
- }
- if (group.cycleType === 'custom' && group.customDates.length === 0) {
- return '存在未设置自定义日期的预约组'
- }
- }
- return null
- }
- const handleSubmit = async () => {
- if (!formRef.value) return
- if (!organizationId) {
- await ElMessageBox.alert('请选择机构!', '警告', { type: 'warning' })
- return
- }
- if (!formData.advanceDays || formData.advanceDays < 1) {
- await ElMessageBox.alert('提前预约天数最少为1', '警告', { type: 'warning' })
- return
- }
- if (
- formData.startTime &&
- formData.endTime &&
- timeToMinutes(formData.startTime) >= timeToMinutes(formData.endTime)
- ) {
- await ElMessageBox.alert('放号开始时间不能晚于或等于结束时间', '警告', { type: 'warning' })
- return
- }
- const groupError = validateGroups()
- if (groupError) {
- await ElMessageBox.alert(groupError, '警告', { type: 'warning' })
- return
- }
- const personLimitError = validatePersonLimits()
- if (personLimitError) {
- await ElMessageBox.alert(personLimitError, '警告', { type: 'warning' })
- return
- }
- for (const group of appointmentGroups.value) {
- for (let i = 0; i < group.items.length; i++) {
- const item = group.items[i]
- if (!item.limitCount || item.limitCount <= 0) {
- await ElMessageBox.alert('限制人数必须大于0', '警告', { type: 'warning' })
- return
- }
- if (
- item.startTime &&
- item.endTime &&
- timeToMinutes(item.startTime) >= timeToMinutes(item.endTime)
- ) {
- await ElMessageBox.alert('存在开始时间晚于或等于结束时间的预约项,请检查', '警告', {
- type: 'warning'
- })
- return
- }
- for (let j = i + 1; j < group.items.length; j++) {
- if (isOverlap(item, group.items[j])) {
- await ElMessageBox.alert('同一预约组内存在时间段重叠的预约项,请检查', '警告', {
- type: 'warning'
- })
- return
- }
- }
- }
- }
- try {
- await formRef.value.validate()
- submitting.value = true
- const config: AppointmentConfigDO = {
- id: configId.value,
- tenantId: organizationId,
- advanceDays: formData.advanceDays,
- beginBookDays: formData.startDay,
- enabled: formData.appointmentEnabled ? 1 : 0,
- openTimeSlot:
- formData.startTime && formData.endTime
- ? `${formData.startTime}-${formData.endTime}`
- : undefined
- }
- totalLimits.value.forEach((limit) => {
- const field = limitTypeToFieldMap[limit.type]
- if (field) {
- config[field.total] = limit.count
- }
- })
- // 单人名额:未设置或删除时传 -1(后端表示无限制)
- ;[1, 2, 3].forEach((type) => {
- const field = limitTypeToFieldMap[type]
- if (field) {
- const limit = personLimits.value.find((item) => item.type === type)
- config[field.person] = limit ? limit.count : -1
- }
- })
- const group: AppointmentGroupDO[] = []
- appointmentGroups.value.forEach((g) => {
- const dateType = cycleTypeToDateType(g.cycleType)
- const weekDays = g.weeklyDays.join(',') || undefined
- const monthDays = g.monthlyDays.join(',') || undefined
- const customDates = g.customDates.join(',') || undefined
- const enable = g.enable || 0
- g.items.forEach((item) => {
- group.push({
- // 旧数据带回后端 id 用于更新;新增数据不带 id,后端按新增处理
- ...(item.serverId ? { id: item.serverId } : {}),
- dateType,
- weekDays,
- monthDays,
- customDates,
- beginTime: item.startTime,
- enable: enable,
- endTime: item.endTime,
- limitCount: item.limitCount,
- tenantId: organizationId
- })
- })
- })
- const params = { config, group, deleteIds: deleteIds.value }
- const res = configId.value
- ? await appointmentConfigUpdate(params)
- : await appointmentConfigADD(params)
- if (res) {
- message.success('设置成功!')
- await getSetting()
- }
- } catch (error: any) {
- if (error?.message) {
- await ElMessageBox.alert(error.message, '警告', { type: 'warning' })
- }
- } finally {
- submitting.value = false
- }
- }
- onMounted(() => {
- if (userStore.orgTenantId.length > 0) {
- tenantIds.value = [userStore.orgTenantId[0]] as number[]
- organizationId = tenantIds.value[0]
- getSetting()
- }
- })
- </script>
- <style scoped lang="scss">
- .setting-form {
- padding-top: 16px;
- .main-row {
- display: flex;
- align-items: stretch;
- }
- .col-left,
- .col-right {
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
- .card {
- background-color: #fff;
- border-radius: 8px;
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
- display: flex;
- flex-direction: column;
- overflow: hidden;
- &-top-left,
- &-mid-right {
- flex: 0 0 auto;
- }
- &-bottom-left,
- &-bottom-right {
- flex: 1 1 auto;
- min-height: 0;
- }
- }
- .card-header {
- display: flex;
- align-items: center;
- padding: 16px 20px;
- border-bottom: 1px solid #f0f0f0;
- background: linear-gradient(90deg, #fafbfc 0%, #ffffff 100%);
- .card-icon {
- width: 28px;
- height: 28px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 6px;
- margin-right: 10px;
- color: #fff;
- font-size: 16px;
- &.icon-blue {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- }
- &.icon-green {
- background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
- }
- &.icon-orange {
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
- }
- &.icon-purple {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- }
- }
- .card-title {
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- flex: 1;
- }
- .master-switch {
- display: flex;
- align-items: center;
- gap: 8px;
- .switch-label {
- font-size: 14px;
- color: #606266;
- }
- }
- }
- .card-body {
- padding: 20px;
- flex: 1;
- }
- .empty-groups {
- padding: 30px 0;
- }
- .card-body-groups {
- padding: 16px 20px;
- max-height: 560px;
- overflow-y: auto;
- }
- .group-list {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .group-card {
- background-color: #fff;
- border: 1px solid #e4e7ed;
- border-radius: 8px;
- padding: 16px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
- }
- .group-header {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 12px;
- flex-wrap: wrap;
- .group-label {
- font-size: 14px;
- font-weight: 600;
- color: #303133;
- }
- .cycle-buttons {
- display: flex;
- gap: 6px;
- flex: 1;
- flex-wrap: wrap;
- }
- }
- .group-dates {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 6px;
- padding: 12px 0;
- border-top: 1px dashed #ebeef5;
- border-bottom: 1px dashed #ebeef5;
- .dates-label {
- font-size: 13px;
- color: #606266;
- margin-right: 4px;
- }
- .date-tag {
- margin: 2px 0;
- }
- }
- .group-items-section {
- margin-top: 14px;
- }
- .items-header {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- .items-title {
- font-size: 14px;
- font-weight: 600;
- color: #303133;
- }
- .items-sub {
- font-size: 12px;
- color: #909399;
- margin-left: 8px;
- }
- }
- .empty-items {
- text-align: center;
- padding: 20px 0;
- font-size: 13px;
- color: #c0c4cc;
- background-color: #fafafa;
- border-radius: 6px;
- border: 1px dashed #dcdfe6;
- }
- .items-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
- margin-bottom: 12px;
- }
- .item-row {
- display: flex;
- align-items: center;
- gap: 10px;
- background-color: #f8f9fa;
- border: 1px solid #e9ecef;
- border-radius: 6px;
- padding: 10px 12px;
- }
- .item-delete-btn {
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- color: #f56c6c;
- cursor: pointer;
- transition: all 0.2s;
- flex-shrink: 0;
- margin-top: 6px;
- &:hover {
- background-color: #fef0f0;
- }
- &.disabled {
- pointer-events: none;
- opacity: 0.4;
- cursor: not-allowed;
- }
- }
- .item-fields {
- flex: 1;
- display: flex;
- flex-wrap: wrap;
- gap: 14px;
- }
- .field-block {
- display: flex;
- align-items: center;
- gap: 6px;
- .field-label {
- font-size: 13px;
- color: #606266;
- white-space: nowrap;
- }
- .time-sep {
- font-size: 13px;
- color: #909399;
- }
- }
- .add-item-row {
- display: flex;
- justify-content: center;
- }
- }
- .fixed-save-bar {
- width: 100%;
- display: flex;
- justify-content: flex-end;
- }
- :deep(.el-form-item) {
- margin-bottom: 18px;
- &:last-child {
- margin-bottom: 0;
- }
- }
- :deep(.el-form-item__content) {
- display: flex;
- align-items: center;
- }
- :deep(.date-selector-dialog) {
- .date-selector-body {
- padding: 6px 0;
- .selector-title {
- font-size: 14px;
- color: #606266;
- margin-bottom: 16px;
- }
- .week-days-grid {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: 10px;
- }
- .month-days-grid {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: 8px;
- }
- .day-cell {
- height: 42px;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 2px solid #e4e7ed;
- border-radius: 8px;
- cursor: pointer;
- font-size: 14px;
- color: #606266;
- background-color: #fff;
- transition: all 0.2s ease;
- user-select: none;
- &:hover {
- border-color: #67c23a;
- color: #67c23a;
- background-color: #f0f9eb;
- }
- &.active {
- border-color: #67c23a;
- background-color: #67c23a;
- color: #fff;
- font-weight: 500;
- box-shadow: 0 2px 6px rgba(103, 194, 58, 0.3);
- }
- }
- }
- }
- .limit-list-inline {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .limit-item-inline {
- display: flex;
- align-items: center;
- padding: 10px 14px;
- background-color: #f8f9fa;
- border-radius: 6px;
- border: 1px solid #e9ecef;
- .limit-type-badge {
- font-size: 12px;
- font-weight: 600;
- color: #fff;
- padding: 3px 10px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 4px;
- margin-right: 16px;
- }
- .limit-count-num {
- flex: 1;
- font-size: 16px;
- font-weight: 600;
- color: #f56c6c;
- em {
- font-style: normal;
- font-size: 13px;
- font-weight: 400;
- color: #909399;
- margin-left: 2px;
- }
- }
- }
- </style>
|