|
|
@@ -67,23 +67,128 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 出车准备(SOP:推进到第2步前) -->
|
|
|
- <view v-if="currentStep === 1" class="glass-card p-4">
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">出车准备(SOP)</text>
|
|
|
- <view class="gap-y-3 mb-4">
|
|
|
- <view
|
|
|
- v-for="item in checklist"
|
|
|
- :key="item.key"
|
|
|
- class="flex items-center justify-between py-2 border-b border-gray-100"
|
|
|
- @click="item.checked = !item.checked"
|
|
|
- >
|
|
|
- <text class="text-sm text-gray-700">{{ item.label }}</text>
|
|
|
- <view class="check-box" :class="item.checked ? 'check-box-on' : ''">
|
|
|
- <text v-if="item.checked" class="uni-icons uniui-checkmarkempty check-icon"></text>
|
|
|
+ <!-- 步骤业务数据表单(与 admin 前端字段对齐) -->
|
|
|
+ <view v-if="canAdvance" class="glass-card p-4">
|
|
|
+ <view v-if="nextStepCode === 'prepare'">
|
|
|
+ <text class="text-base font-semibold text-gray-800 mb-4 block">出车准备 - 车辆点检</text>
|
|
|
+ <view class="gap-y-3 mb-4">
|
|
|
+ <view
|
|
|
+ v-for="item in DEFAULT_DEPARTURE_CHECKS"
|
|
|
+ :key="item.code"
|
|
|
+ class="py-3 border-b border-gray-100"
|
|
|
+ >
|
|
|
+ <view class="flex items-center justify-between mb-2">
|
|
|
+ <view>
|
|
|
+ <text class="text-sm font-medium text-gray-700">{{ item.name }}</text>
|
|
|
+ <text class="text-xs text-gray-400 block">{{ item.description }}</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="check-box"
|
|
|
+ :class="departureChecks[item.code]?.passed ? 'check-box-on' : ''"
|
|
|
+ @click="toggleDepartureCheck(item.code)"
|
|
|
+ >
|
|
|
+ <text v-if="departureChecks[item.code]?.passed" class="uni-icons uniui-checkmarkempty check-icon"></text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <textarea
|
|
|
+ v-model="departureChecks[item.code].remark"
|
|
|
+ class="form-textarea text-xs"
|
|
|
+ style="min-height: 48px;"
|
|
|
+ placeholder="备注(如有异常请填写)"
|
|
|
+ />
|
|
|
</view>
|
|
|
</view>
|
|
|
+ <text class="text-xs text-gray-400 block">请完成全部车辆点检后方可出发</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="nextStepCode === 'arrive'">
|
|
|
+ <text class="text-base font-semibold text-gray-800 mb-4 block">现场踏勘</text>
|
|
|
+ <view class="gap-y-4">
|
|
|
+ <view class="py-2 border-b border-gray-100">
|
|
|
+ <text class="text-sm text-gray-700 mb-2 block">对接人是否已到位</text>
|
|
|
+ <view class="flex gap-4">
|
|
|
+ <view
|
|
|
+ class="flex items-center"
|
|
|
+ @click="siteSurvey.contactStatus = 'arrived'"
|
|
|
+ >
|
|
|
+ <view class="radio-circle" :class="siteSurvey.contactStatus === 'arrived' ? 'radio-circle-on' : ''"></view>
|
|
|
+ <text class="text-sm ml-2">已到位</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="flex items-center"
|
|
|
+ @click="siteSurvey.contactStatus = 'not_arrived'"
|
|
|
+ >
|
|
|
+ <view class="radio-circle" :class="siteSurvey.contactStatus === 'not_arrived' ? 'radio-circle-on' : ''"></view>
|
|
|
+ <text class="text-sm ml-2">未到位</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <textarea
|
|
|
+ v-if="siteSurvey.contactStatus === 'not_arrived'"
|
|
|
+ v-model="siteSurvey.notArrivedRemark"
|
|
|
+ class="form-textarea text-xs mt-2"
|
|
|
+ style="min-height: 48px;"
|
|
|
+ placeholder="请说明未到位情况及处理方式"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="py-2 border-b border-gray-100">
|
|
|
+ <text class="text-sm text-gray-700 mb-2 block">现场情况与工单描述是否一致</text>
|
|
|
+ <view class="flex gap-4">
|
|
|
+ <view
|
|
|
+ class="flex items-center"
|
|
|
+ @click="siteSurvey.consistencyStatus = 'consistent'"
|
|
|
+ >
|
|
|
+ <view class="radio-circle" :class="siteSurvey.consistencyStatus === 'consistent' ? 'radio-circle-on' : ''"></view>
|
|
|
+ <text class="text-sm ml-2">一致</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="flex items-center"
|
|
|
+ @click="siteSurvey.consistencyStatus = 'inconsistent'"
|
|
|
+ >
|
|
|
+ <view class="radio-circle" :class="siteSurvey.consistencyStatus === 'inconsistent' ? 'radio-circle-on' : ''"></view>
|
|
|
+ <text class="text-sm ml-2">不一致</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <textarea
|
|
|
+ v-if="siteSurvey.consistencyStatus === 'inconsistent'"
|
|
|
+ v-model="siteSurvey.inconsistencyRemark"
|
|
|
+ class="form-textarea text-xs mt-2"
|
|
|
+ style="min-height: 48px;"
|
|
|
+ placeholder="请说明不一致的具体情况及处理建议"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="py-2">
|
|
|
+ <text class="text-sm text-gray-700 mb-2 block">客户是否同意施工</text>
|
|
|
+ <view
|
|
|
+ class="switch-wrap"
|
|
|
+ :class="siteSurvey.customerAgreed ? 'switch-wrap-on' : ''"
|
|
|
+ @click="siteSurvey.customerAgreed = !siteSurvey.customerAgreed"
|
|
|
+ >
|
|
|
+ <view class="switch-dot" :class="siteSurvey.customerAgreed ? 'switch-dot-on' : ''"></view>
|
|
|
+ <text class="text-sm ml-2">{{ siteSurvey.customerAgreed ? '已同意' : '未同意' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="nextStepCode === 'work'">
|
|
|
+ <text class="text-base font-semibold text-gray-800 mb-4 block">堵塞物情况</text>
|
|
|
+ <textarea
|
|
|
+ v-model="blockageText"
|
|
|
+ class="form-textarea"
|
|
|
+ placeholder="请描述堵塞物情况"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="nextStepCode === 'photo'">
|
|
|
+ <text class="text-base font-semibold text-gray-800 mb-4 block">设施反馈</text>
|
|
|
+ <textarea
|
|
|
+ v-model="feedbackText"
|
|
|
+ class="form-textarea"
|
|
|
+ placeholder="请描述设施反馈情况"
|
|
|
+ />
|
|
|
</view>
|
|
|
- <text class="text-xs text-gray-400 block">请完成全部车辆点检后方可出发</text>
|
|
|
</view>
|
|
|
|
|
|
<!-- SOP 推进操作区 -->
|
|
|
@@ -94,7 +199,15 @@
|
|
|
</view>
|
|
|
<view class="mb-4">
|
|
|
<text class="text-sm text-gray-500 mb-2 block">现场照片(可选)</text>
|
|
|
- <ImageUploader v-model="stepPhotos" :max-count="6" tip="上传本步骤现场照片" />
|
|
|
+ <ImageUploader
|
|
|
+ v-model="stepPhotos"
|
|
|
+ :max-count="6"
|
|
|
+ type="taskPhoto"
|
|
|
+ :task-id="taskId"
|
|
|
+ :step="currentStepCode"
|
|
|
+ :phase="currentStepConfig?.phase"
|
|
|
+ tip="上传本步骤现场照片"
|
|
|
+ />
|
|
|
</view>
|
|
|
<view class="mb-4">
|
|
|
<text class="text-sm text-gray-500 mb-2 block">备注(可选)</text>
|
|
|
@@ -357,23 +470,54 @@ import StatusTag from '../../components/common/StatusTag.vue'
|
|
|
import EmptyState from '../../components/common/EmptyState.vue'
|
|
|
import ImageUploader from '../../components/common/ImageUploader.vue'
|
|
|
import { formatDate, showToast } from '../../utils'
|
|
|
-import { getTaskDetail, advanceTaskStep, updateTask } from '../../api/task'
|
|
|
+import { useAuthStore } from '../../stores/auth'
|
|
|
+import { getTaskDetail, executeSopStep, completeTask, updateTask, getSopSteps, type ExecuteSopStepData } from '../../api/task'
|
|
|
+import { getTaskPhotos, type TaskPhoto } from '../../api/file'
|
|
|
+import type { UploaderPhoto } from '../../components/common/ImageUploader.vue'
|
|
|
import { getProjectDetail } from '../../api/project'
|
|
|
import { getTeamSimpleList } from '../../api/team'
|
|
|
import { getVehicleSimpleList } from '../../api/vehicle'
|
|
|
-
|
|
|
-const STEP_LABELS = ['确认', '准备', '现场', '施工', '拍照', '清洗', '签字', '清理']
|
|
|
-const stepLabels = STEP_LABELS
|
|
|
+import {
|
|
|
+ SOP_STEPS,
|
|
|
+ SOP_STEP_TITLES,
|
|
|
+ getStepCode,
|
|
|
+ getStepConfigByIndex,
|
|
|
+ DEFAULT_DEPARTURE_CHECKS,
|
|
|
+ type DepartureCheckResult,
|
|
|
+ type SiteSurveyResult,
|
|
|
+} from '../../constants/sop'
|
|
|
+
|
|
|
+const stepLabels = SOP_STEP_TITLES
|
|
|
|
|
|
const task = ref<any>(null)
|
|
|
+const authStore = useAuthStore()
|
|
|
const project = ref<any>(null)
|
|
|
const loading = ref(false)
|
|
|
const submitting = ref(false)
|
|
|
const taskId = ref<number>(0)
|
|
|
|
|
|
-const stepPhotos = ref<string[]>([])
|
|
|
+const stepPhotos = ref<UploaderPhoto[]>([])
|
|
|
+const allPhotos = ref<TaskPhoto[]>([])
|
|
|
const stepRemark = ref('')
|
|
|
|
|
|
+// 各步骤业务数据(与 admin 前端对齐)
|
|
|
+const departureChecks = ref<Record<string, DepartureCheckResult>>({
|
|
|
+ oil: { code: 'oil', passed: false, remark: '' },
|
|
|
+ tire: { code: 'tire', passed: false, remark: '' },
|
|
|
+ light: { code: 'light', passed: false, remark: '' },
|
|
|
+ hose: { code: 'hose', passed: false, remark: '' },
|
|
|
+ tool: { code: 'tool', passed: false, remark: '' },
|
|
|
+})
|
|
|
+const siteSurvey = ref<SiteSurveyResult>({
|
|
|
+ contactStatus: undefined,
|
|
|
+ consistencyStatus: undefined,
|
|
|
+ customerAgreed: false,
|
|
|
+ notArrivedRemark: '',
|
|
|
+ inconsistencyRemark: '',
|
|
|
+})
|
|
|
+const blockageText = ref('')
|
|
|
+const feedbackText = ref('')
|
|
|
+
|
|
|
const checklist = reactive([
|
|
|
{ key: 'oil', label: '机油检查', checked: false },
|
|
|
{ key: 'tire', label: '轮胎检查', checked: false },
|
|
|
@@ -391,6 +535,9 @@ const teamOptions = ref<Array<{ value: number; label: string }>>([])
|
|
|
const vehicleOptions = ref<Array<{ value: number; label: string }>>([])
|
|
|
|
|
|
const currentStep = computed(() => Number(task.value?.currentStep) || 0)
|
|
|
+const currentStepCode = computed(() => getStepCode(currentStep.value + 1))
|
|
|
+const currentStepConfig = computed(() => getStepConfigByIndex(currentStep.value + 1))
|
|
|
+const nextStepCode = computed(() => currentStepCode.value)
|
|
|
const taskLogs = computed(() => task.value?.logList || [])
|
|
|
|
|
|
const isPendingConfirm = computed(() => task.value?.status === 'assigned' && currentStep.value === 0)
|
|
|
@@ -402,20 +549,33 @@ const canAdvance = computed(() => {
|
|
|
return currentStep.value < 8
|
|
|
})
|
|
|
|
|
|
-const advanceDisabled = computed(() => currentStep.value === 1 && !checklist.every(c => c.checked))
|
|
|
+const advanceDisabled = computed(() => {
|
|
|
+ const code = nextStepCode.value
|
|
|
+ // 出车准备步骤必须全部点检通过
|
|
|
+ if (code === 'prepare') {
|
|
|
+ return !DEFAULT_DEPARTURE_CHECKS.every((item) => departureChecks.value[item.code]?.passed)
|
|
|
+ }
|
|
|
+ // 到达现场步骤必须完成踏勘且客户同意
|
|
|
+ if (code === 'arrive') {
|
|
|
+ const s = siteSurvey.value
|
|
|
+ return !s.contactStatus || !s.consistencyStatus || !s.customerAgreed
|
|
|
+ }
|
|
|
+ return false
|
|
|
+})
|
|
|
|
|
|
const nextActionText = computed(() => {
|
|
|
- const next = currentStep.value + 1
|
|
|
- const map: Record<number, string> = {
|
|
|
- 2: '完成准备,出发',
|
|
|
- 3: '到达现场',
|
|
|
- 4: '开始施工',
|
|
|
- 5: '完成拍照记录',
|
|
|
- 6: '完成清洗',
|
|
|
- 7: '完成签字验收',
|
|
|
- 8: '完成清理,结束任务',
|
|
|
+ const nextConfig = getStepConfigByIndex(currentStep.value + 1)
|
|
|
+ if (!nextConfig) return '推进到下一步'
|
|
|
+ const actionMap: Record<string, string> = {
|
|
|
+ prepare: '完成准备,出发',
|
|
|
+ arrive: '到达现场',
|
|
|
+ work: '开始施工',
|
|
|
+ photo: '完成拍照记录',
|
|
|
+ clean: '完成清洗',
|
|
|
+ accept: '完成签字验收',
|
|
|
+ finish: '完成清理,结束任务',
|
|
|
}
|
|
|
- return map[next] || '推进到下一步'
|
|
|
+ return actionMap[nextConfig.code] || `推进到「${nextConfig.title}」`
|
|
|
})
|
|
|
|
|
|
const extra = computed(() => {
|
|
|
@@ -548,6 +708,7 @@ async function loadDetail() {
|
|
|
if (vehicleOptions.value.length === 0) {
|
|
|
getVehicleSimpleList().then(list => { vehicleOptions.value = list }).catch(() => {})
|
|
|
}
|
|
|
+ await loadPhotos()
|
|
|
} catch (e) {
|
|
|
console.error('加载任务详情失败', e)
|
|
|
showToast('加载任务详情失败')
|
|
|
@@ -556,16 +717,107 @@ async function loadDetail() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function doAdvance(step: number, successMsg: string) {
|
|
|
+async function loadPhotos() {
|
|
|
+ try {
|
|
|
+ allPhotos.value = await getTaskPhotos(taskId.value)
|
|
|
+ filterStepPhotos()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载任务照片失败', e)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function filterStepPhotos() {
|
|
|
+ const phase = currentStepConfig.value?.phase
|
|
|
+ if (!phase) {
|
|
|
+ stepPhotos.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ stepPhotos.value = allPhotos.value
|
|
|
+ .filter((p) => p.phase === phase || p.stepCode === currentStepCode.value)
|
|
|
+ .map((p) => ({ id: p.photoId, url: p.photoUrl || '' }))
|
|
|
+ .filter((p) => p.url)
|
|
|
+}
|
|
|
+
|
|
|
+/** 判断是否为本地/临时文件路径(仍在上传中) */
|
|
|
+function isTempUrl(url: string): boolean {
|
|
|
+ if (!url) return true
|
|
|
+ // H5 blob/data、小程序 wxfile、APP 沙盒路径、部分环境的 http://tmp/ 临时文件
|
|
|
+ return /^(blob|data|file|wxfile|_doc|_downloads|_user|http:\/\/tmp\/)/i.test(url)
|
|
|
+}
|
|
|
+
|
|
|
+function toggleDepartureCheck(code: string) {
|
|
|
+ const item = departureChecks.value[code]
|
|
|
+ if (item) {
|
|
|
+ item.passed = !item.passed
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function buildStepData(): string | undefined {
|
|
|
+ const code = nextStepCode.value
|
|
|
+ if (code === 'prepare') {
|
|
|
+ const results = DEFAULT_DEPARTURE_CHECKS.map((item) => departureChecks.value[item.code]).filter(Boolean)
|
|
|
+ if (results.length === 0) return undefined
|
|
|
+ // 与 admin 日志格式保持一致:{ results: CheckItemResult[] }
|
|
|
+ return JSON.stringify({ results })
|
|
|
+ }
|
|
|
+ if (code === 'arrive') {
|
|
|
+ if (!siteSurvey.value.contactStatus && !siteSurvey.value.consistencyStatus) return undefined
|
|
|
+ // 与 admin 日志格式保持一致:直接存储 SiteSurveyResult
|
|
|
+ return JSON.stringify(siteSurvey.value)
|
|
|
+ }
|
|
|
+ if (code === 'work' && blockageText.value.trim()) {
|
|
|
+ return JSON.stringify({ blockageText: blockageText.value.trim() })
|
|
|
+ }
|
|
|
+ if (code === 'photo' && feedbackText.value.trim()) {
|
|
|
+ return JSON.stringify({ feedbackText: feedbackText.value.trim() })
|
|
|
+ }
|
|
|
+ return undefined
|
|
|
+}
|
|
|
+
|
|
|
+function clearStepForm() {
|
|
|
+ departureChecks.value = {
|
|
|
+ oil: { code: 'oil', passed: false, remark: '' },
|
|
|
+ tire: { code: 'tire', passed: false, remark: '' },
|
|
|
+ light: { code: 'light', passed: false, remark: '' },
|
|
|
+ hose: { code: 'hose', passed: false, remark: '' },
|
|
|
+ tool: { code: 'tool', passed: false, remark: '' },
|
|
|
+ }
|
|
|
+ siteSurvey.value = {
|
|
|
+ contactStatus: undefined,
|
|
|
+ consistencyStatus: undefined,
|
|
|
+ customerAgreed: false,
|
|
|
+ notArrivedRemark: '',
|
|
|
+ inconsistencyRemark: '',
|
|
|
+ }
|
|
|
+ blockageText.value = ''
|
|
|
+ feedbackText.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+async function doAdvance(stepCode: string, successMsg: string) {
|
|
|
+ if (stepPhotos.value.some((p) => isTempUrl(typeof p === 'string' ? p : p.url))) {
|
|
|
+ showToast('照片正在上传,请稍候')
|
|
|
+ return
|
|
|
+ }
|
|
|
submitting.value = true
|
|
|
try {
|
|
|
- const data: Record<string, any> = {}
|
|
|
+ const data: ExecuteSopStepData = {
|
|
|
+ taskId: taskId.value,
|
|
|
+ stepCode,
|
|
|
+ action: 'execute',
|
|
|
+ operatorId: authStore.user?.id ? Number(authStore.user.id) : undefined,
|
|
|
+ operatorName: authStore.user?.name,
|
|
|
+ }
|
|
|
if (stepRemark.value.trim()) data.remark = stepRemark.value.trim()
|
|
|
- if (stepPhotos.value.length > 0) data.photos = stepPhotos.value
|
|
|
- await advanceTaskStep(taskId.value, step, Object.keys(data).length > 0 ? data : undefined)
|
|
|
+ const photos = stepPhotos.value.map((p) => (typeof p === 'string' ? p : p.url)).filter(Boolean)
|
|
|
+ if (photos.length > 0) data.photos = photos
|
|
|
+ const stepData = buildStepData()
|
|
|
+ if (stepData) data.data = stepData
|
|
|
+
|
|
|
+ await executeSopStep(data)
|
|
|
showToast(successMsg)
|
|
|
stepPhotos.value = []
|
|
|
stepRemark.value = ''
|
|
|
+ clearStepForm()
|
|
|
await loadDetail()
|
|
|
} catch (e) {
|
|
|
console.error('推进SOP步骤失败', e)
|
|
|
@@ -575,17 +827,48 @@ async function doAdvance(step: number, successMsg: string) {
|
|
|
}
|
|
|
|
|
|
function onConfirm() {
|
|
|
- doAdvance(1, '任务已确认')
|
|
|
+ doAdvance('confirm', '任务已确认')
|
|
|
+}
|
|
|
+
|
|
|
+async function doComplete() {
|
|
|
+ submitting.value = true
|
|
|
+ try {
|
|
|
+ await completeTask(taskId.value, {
|
|
|
+ taskId: taskId.value,
|
|
|
+ remark: stepRemark.value.trim() || undefined,
|
|
|
+ operatorId: authStore.user?.id ? Number(authStore.user.id) : undefined,
|
|
|
+ operatorName: authStore.user?.name,
|
|
|
+ })
|
|
|
+ showToast('任务已完成')
|
|
|
+ stepPhotos.value = []
|
|
|
+ stepRemark.value = ''
|
|
|
+ await loadDetail()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('完成任务失败', e)
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function onAdvance() {
|
|
|
if (advanceDisabled.value) {
|
|
|
- showToast('请先完成全部出车点检')
|
|
|
+ const code = nextStepCode.value
|
|
|
+ if (code === 'prepare') {
|
|
|
+ showToast('请先完成全部出车点检')
|
|
|
+ } else if (code === 'arrive') {
|
|
|
+ showToast('请完成现场踏勘并确认客户同意施工')
|
|
|
+ } else {
|
|
|
+ showToast('请完成当前步骤必填信息')
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
- const next = currentStep.value + 1
|
|
|
- if (next > 8) return
|
|
|
- doAdvance(next, next === 8 ? '任务已完成' : `已推进到「${STEP_LABELS[next - 1]}」`)
|
|
|
+ const nextConfig = getStepConfigByIndex(currentStep.value + 1)
|
|
|
+ if (!nextConfig) return
|
|
|
+ if (nextConfig.step === 8) {
|
|
|
+ doComplete()
|
|
|
+ } else {
|
|
|
+ doAdvance(nextConfig.code, `已推进到「${nextConfig.title}」`)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async function onReject() {
|
|
|
@@ -914,4 +1197,54 @@ function onFeedback() {
|
|
|
background: #fff;
|
|
|
min-height: 72px;
|
|
|
}
|
|
|
+
|
|
|
+/* 单选按钮 */
|
|
|
+.radio-circle {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2px solid #d1d5db;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+.radio-circle-on {
|
|
|
+ border-color: #368f6f;
|
|
|
+ background-color: #368f6f;
|
|
|
+}
|
|
|
+.radio-circle-on::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+}
|
|
|
+
|
|
|
+/* 开关 */
|
|
|
+.switch-wrap {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 6px 12px 6px 6px;
|
|
|
+ border-radius: 9999px;
|
|
|
+ background-color: #f3f4f6;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+.switch-wrap-on {
|
|
|
+ background-color: rgba(54, 143, 111, 0.12);
|
|
|
+ border-color: #368f6f;
|
|
|
+}
|
|
|
+.switch-dot {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #d1d5db;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+.switch-dot-on {
|
|
|
+ background-color: #368f6f;
|
|
|
+}
|
|
|
</style>
|