feat(web-admin): redesign mp transport plan detail sections.

Align mobile detail with demo IA including hazard cert slots and capped footer actions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-15 09:34:16 +08:00
1 parent 8d1d5c19b5
commit bf81d9b77c
1 file changed
+593 -78
+593 -78
View File
@@ -9,33 +9,222 @@
<el-tag :type="statusTagType" size="small">{{ statusLabel }}</el-tag>
</div>
<h1>{{ plan.planNo || '计划详情' }}</h1>
<div class="mp-wv-card__sub" style="color: rgba(255, 255, 255, 0.9)">
{{ typeLabel }} · 计划日期 {{ plan.planDate || '—' }}
</div>
<div class="mp-wv-header__sub">{{ typeLabel }} · 计划日期 {{ plan.planDate || '—' }}</div>
</header>
<section class="mp-wv-card">
<div class="mp-wv-card__meta">送货时间:{{ deliveryTimeText }}</div>
<div class="mp-wv-card__meta">车牌:{{ plate }}</div>
<div class="mp-wv-card__meta">人员:{{ people }}</div>
<div class="mp-wv-card__meta">跟进人:{{ plan.followUserName || '—' }}</div>
<div class="mp-wv-card__product">{{ product }}</div>
</section>
<div class="mp-wv-detail-body">
<section class="mp-wv-section">
<h3>基础信息</h3>
<div class="mp-wv-kv"><span>计划编号</span><span>{{ plan.planNo || '—' }}</span></div>
<div class="mp-wv-kv"><span>计划日期</span><span>{{ plan.planDate || '—' }}</span></div>
<div class="mp-wv-kv">
<span>归属公司</span><span>{{ getPlPlanCompanyLabel(plan.belongsCompany) }}</span>
</div>
<div class="mp-wv-kv"><span>单据类型</span><span>{{ typeLabel }}</span></div>
<div class="mp-wv-kv"><span>跟进人</span><span>{{ plan.followUserName || '—' }}</span></div>
</section>
<div class="mp-wv-toolbar" style="flex-wrap: wrap">
<button v-if="canArrange" class="mp-wv-btn mp-wv-btn--primary" type="button" @click="goArrange">
{{ arrangeLabel }}
<section class="mp-wv-section">
<h3>运输信息</h3>
<template v-if="isArranged">
<div v-if="arrangeDriverUrl" class="mp-wv-link-block">
<div class="mp-wv-link-label">司机链接</div>
<div class="mp-wv-link-row">
<input class="mp-wv-link-input" :value="arrangeDriverUrl" readonly />
<button class="mp-wv-btn" type="button" @click="copyText(arrangeDriverUrl, '司机链接')">
复制
</button>
</div>
</div>
<div v-if="siteLinks.length" class="mp-wv-link-block">
<div class="mp-wv-link-label">现场链接</div>
<div v-for="link in siteLinks" :key="link.siteLinkId" class="mp-wv-site-link">
<div class="mp-wv-card__meta">{{ link.productName || '现场链接' }}</div>
<div class="mp-wv-link-row">
<input class="mp-wv-link-input" :value="link.siteUrl || ''" readonly />
<button
class="mp-wv-btn"
type="button"
@click="copyText(link.siteUrl || '', link.productName || '现场链接')"
>
复制
</button>
</div>
</div>
</div>
</template>
<div v-if="hasVehicle" class="mp-wv-vehicle-box">
<div><b>{{ plate }}</b></div>
<div class="mp-wv-card__meta">挂车 {{ plan.trailerNo || '—' }}</div>
<div class="mp-wv-card__meta">司机 {{ plan.driverName || '—' }} · 押运 {{ plan.escortName || '—' }}</div>
<button class="mp-wv-btn" type="button" style="width: 100%; margin-top: 8px" @click="vehicleOpen = true">
查看车辆详情
</button>
</div>
<p v-else class="mp-wv-card__meta">暂无车辆信息</p>
<div v-if="feedbackLines.length" class="mp-wv-feedback">
<div v-for="(line, idx) in feedbackLines" :key="idx" class="mp-wv-feedback-box">
<h4>{{ line.title }}</h4>
<div>{{ line.text }}</div>
</div>
</div>
</section>
<section class="mp-wv-section">
<h3>装送货信息</h3>
<div class="mp-wv-stop">
<div class="mp-wv-stop-title">装货信息</div>
<div class="mp-wv-kv"><span>装货客户</span><span>{{ load?.customerName || '—' }}</span></div>
<div class="mp-wv-kv"><span>装货时间</span><span>{{ load?.loadTime || '—' }}</span></div>
<div class="mp-wv-kv"><span>装货地址</span><span>{{ load?.address || '—' }}</span></div>
<div class="mp-wv-kv"><span>联系方式</span><span>{{ load?.contact || '—' }}</span></div>
<div class="mp-wv-kv"><span>注意事项</span><span>{{ load?.notes || '—' }}</span></div>
<div class="mp-wv-kv"><span>装货合同</span><span>{{ loadContractText }}</span></div>
<div class="mp-wv-kv"><span>地磅单</span><span>{{ dibangText(load) }}</span></div>
<div class="mp-wv-prod-table">
<div v-if="!(load?.products || []).length" class="mp-wv-prod-empty">暂无产品</div>
<div v-for="(p, i) in load?.products || []" :key="p.id || i" class="mp-wv-prod-row">
<div class="mp-wv-prod-name">{{ p.productName }}{{ p.model ? ` · ${p.model}` : '' }}</div>
<div class="mp-wv-prod-grid">
<div>单位 <b>{{ p.unit || '—' }}</b></div>
<div>包装 <b>{{ p.packageSpec || '—' }}</b></div>
<div>计划量 <b>{{ p.planQty ?? '—' }}</b></div>
</div>
</div>
</div>
</div>
<div class="mp-wv-stop">
<div class="mp-wv-stop-title">送货信息</div>
<div v-if="deliveries.length > 1" class="mp-wv-delivery-tabs">
<button
v-for="(d, idx) in deliveries"
:key="d.id || idx"
class="mp-wv-delivery-tab"
:class="{ active: deliveryIndex === idx }"
type="button"
@click="deliveryIndex = idx"
>
送货段 {{ idx + 1 }}
</button>
</div>
<template v-if="activeDelivery">
<div class="mp-wv-kv">
<span>送货客户</span><span>{{ activeDelivery.customerName || '—' }}</span>
</div>
<div class="mp-wv-kv">
<span>送货时间</span><span>{{ activeDelivery.deliveryTime || '—' }}</span>
</div>
<div class="mp-wv-kv"><span>送货地址</span><span>{{ activeDelivery.address || '—' }}</span></div>
<div class="mp-wv-kv"><span>联系方式</span><span>{{ activeDelivery.contact || '—' }}</span></div>
<div class="mp-wv-kv"><span>注意事项</span><span>{{ activeDelivery.notes || '—' }}</span></div>
<div class="mp-wv-kv"><span>送货合同</span><span>{{ deliveryContractText }}</span></div>
<div class="mp-wv-kv"><span>地磅单</span><span>{{ dibangText(activeDelivery) }}</span></div>
<div class="mp-wv-prod-table">
<div v-if="!(activeDelivery.products || []).length" class="mp-wv-prod-empty">暂无产品</div>
<div
v-for="(p, i) in activeDelivery.products || []"
:key="p.id || i"
class="mp-wv-prod-row"
>
<div class="mp-wv-prod-name">
{{ p.productName }}{{ p.model ? ` · ${p.model}` : '' }}
</div>
<div class="mp-wv-prod-grid">
<div>单位 <b>{{ p.unit || '—' }}</b></div>
<div>包装 <b>{{ p.packageSpec || '—' }}</b></div>
<div>计划量 <b>{{ p.planQty ?? '—' }}</b></div>
<div v-if="p.deliveryMethod">送货方式 <b>{{ p.deliveryMethod }}</b></div>
</div>
</div>
</div>
</template>
<p v-else class="mp-wv-card__meta">暂无送货段</p>
</div>
<div class="mp-wv-qty">
<div>装货合计 <b>{{ loadQty }}</b></div>
<div>送货合计 <b>{{ deliveryQty }}</b></div>
<div :class="qtyBalanced ? 'is-ok' : ''">
{{ qtyBalanced ? '✓ 数量平衡' : '⚠ 装送数量不一致' }}
</div>
</div>
</section>
<section class="mp-wv-section">
<h3>危化品证书信息</h3>
<div class="mp-wv-cert-summary">
<span>{{ certSummaryText }}</span>
<el-tag :type="certReadyTagType" size="small">{{ certReadyLabel }}</el-tag>
</div>
<PlPlanHazardCertViewTable :products="hazardProducts" />
<p class="mp-wv-cert-hint">上传与齐备确认在「危化品证上传」页操作。</p>
</section>
<section class="mp-wv-section">
<h3>运费信息</h3>
<div class="mp-wv-kv">
<span>是否有运输费用</span><span>{{ getPlYesNoLabel(plan.hasFreight) }}</span>
</div>
<div class="mp-wv-kv"><span>运费金额(元)</span><span>{{ freightAmountText }}</span></div>
<div class="mp-wv-kv"><span>运费备注</span><span>{{ freightRemarkText }}</span></div>
</section>
<section class="mp-wv-section">
<h3>备注</h3>
<p class="mp-wv-remark">{{ plan.remark?.trim() || '—' }}</p>
</section>
</div>
<div class="mp-wv-fab-bar">
<button
v-if="actionSplit.more.length"
class="mp-wv-btn mp-wv-btn-more"
type="button"
aria-label="更多"
@click="moreOpen = true"
>
⋯
</button>
<button v-if="canEdit" class="mp-wv-btn" type="button" @click="goEdit">编辑计划</button>
<button class="mp-wv-btn" type="button" @click="goCopy">复制计划</button>
<button v-if="canWait" class="mp-wv-btn" type="button" :disabled="acting" @click="doWaitNotice">
等客户通知
</button>
<button class="mp-wv-btn" type="button" @click="goPrint">打印单据</button>
<button v-if="canVoid" class="mp-wv-btn" type="button" :disabled="acting" @click="doVoid">
作废
<button
v-for="(action, idx) in actionSplit.primary"
:key="action.kind"
class="mp-wv-btn"
:class="{
'mp-wv-btn--primary': idx === 0 || actionSplit.primary.length === 1,
'is-danger': action.danger
}"
type="button"
:disabled="acting"
@click="runAction(action.kind)"
>
{{ action.label }}
</button>
</div>
<MpBottomSheet v-model="moreOpen" title="更多操作" half>
<button
v-for="action in actionSplit.more"
:key="action.kind"
class="mp-wv-filter-opt"
:class="{ 'is-danger': action.danger }"
type="button"
:disabled="acting"
@click="runAction(action.kind)"
>
{{ action.label }}
</button>
</MpBottomSheet>
<MpBottomSheet v-model="vehicleOpen" title="车辆详情">
<div class="mp-wv-kv"><span>主车号</span><span>{{ plan.licensePlate || '—' }}</span></div>
<div class="mp-wv-kv"><span>挂车号</span><span>{{ plan.trailerNo || '—' }}</span></div>
<div class="mp-wv-kv"><span>司机</span><span>{{ plan.driverName || '—' }}</span></div>
<div class="mp-wv-kv"><span>押运</span><span>{{ plan.escortName || '—' }}</span></div>
<div class="mp-wv-kv"><span>人员摘要</span><span>{{ plan.personnelSummary || '—' }}</span></div>
</MpBottomSheet>
</template>
</MpWebviewShell>
</template>
@@ -45,15 +234,37 @@
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
import { planApi, type Plan } from '@/api/pl'
import MpBottomSheet from '@/components/mp/MpBottomSheet.vue'
import PlPlanHazardCertViewTable from '@/components/business/pl-plan-hazard-cert-view-table/index.vue'
import {
planApi,
type Plan,
type PlanArrangeInfo,
type PlanDelivery,
type PlanLoad,
type TransportSiteLink
} from '@/api/pl'
import { buildPlanCard } from '@/utils/mp-pl-plan-card'
import {
splitMpPlanDetailActions,
type MpDetailActionKind
} from '@/utils/mp-pl-plan-detail-actions'
import {
arrangeTransportLabel,
canArrangeTransport,
canCancelWaitNotice,
canEditPlan,
canVoidPlan,
canWaitNotice
} from '@/utils/pl-plan-action-guards'
import {
getPlPlanCompanyLabel,
getPlYesNoLabel
} from '@/utils/pl-display'
import {
isHazardCertReady,
isHazardProduct
} from '@/composables/usePlToxicProductCertCheck'
import '@/styles/mp-webview.scss'
const route = useRoute()
@@ -62,26 +273,120 @@
const acting = ref(false)
const errorText = ref('')
const plan = ref<Plan | null>(null)
const arrangeInfo = ref<PlanArrangeInfo | null>(null)
const deliveryIndex = ref(0)
const moreOpen = ref(false)
const vehicleOpen = ref(false)
const card = computed(() => (plan.value ? buildPlanCard(plan.value) : null))
const statusLabel = computed(() => card.value?.statusLabel || '—')
const statusTagType = computed(() => card.value?.statusTagType || 'info')
const typeLabel = computed(() => card.value?.typeLabel || '—')
const plate = computed(() => card.value?.plate || '—')
const people = computed(() => card.value?.people || '—')
const product = computed(() => card.value?.product || '—')
const deliveryTimeText = computed(() => {
const times = plan.value?.deliveryTimes || []
if (times.length) return times.join('、')
return plan.value?.deliveryTime || '—'
const isArranged = computed(() => plan.value?.transportStatus === 'arranged')
const hasVehicle = computed(() => {
const p = plan.value
return !!(p?.licensePlate || p?.vehicleId)
})
const canArrange = computed(() => (plan.value ? canArrangeTransport(plan.value) : false))
const canEdit = computed(() => (plan.value ? canEditPlan(plan.value) : false))
const canVoid = computed(() => (plan.value ? canVoidPlan(plan.value) : false))
const canWait = computed(() => (plan.value ? canWaitNotice(plan.value) : false))
const arrangeLabel = computed(() =>
plan.value ? arrangeTransportLabel(plan.value) : '安排运输'
const load = computed(() => plan.value?.load || null)
const deliveries = computed(() => plan.value?.deliveries || [])
const activeDelivery = computed(() => deliveries.value[deliveryIndex.value] || null)
const arrangeDriverUrl = computed(() => arrangeInfo.value?.driverUrl || '')
const siteLinks = computed<TransportSiteLink[]>(() => arrangeInfo.value?.siteRecords || [])
const loadContractText = computed(() => {
const list = load.value?.contracts || []
if (!list.length) return '—'
return list.map((c) => c.contractNo || c.contractId).filter(Boolean).join('、') || '—'
})
const deliveryContractText = computed(() => {
const list = activeDelivery.value?.contracts || []
if (!list.length) return '—'
return list.map((c) => c.contractNo || c.contractId).filter(Boolean).join('、') || '—'
})
function sumProducts(products?: { planQty?: number }[]) {
return (products || []).reduce((sum, row) => sum + Number(row.planQty || 0), 0)
}
const loadQty = computed(() => sumProducts(load.value?.products))
const deliveryQty = computed(() =>
deliveries.value.reduce((sum, d) => sum + sumProducts(d.products), 0)
)
const qtyBalanced = computed(() => Math.abs(loadQty.value - deliveryQty.value) < 1e-6)
const hazardProducts = computed(() => (plan.value?.products || []).filter((p) => isHazardProduct(p)))
const certReady = computed(() => isHazardCertReady(plan.value))
const certReadyLabel = computed(() => {
if (!hazardProducts.value.length) return '无需证书'
return certReady.value ? '已齐备' : '待确认'
})
const certReadyTagType = computed(() => {
if (!hazardProducts.value.length) return 'info'
return certReady.value ? 'success' : 'warning'
})
const certSummaryText = computed(() => {
if (!hazardProducts.value.length) return '本计划无易制毒/易制爆产品'
return `${hazardProducts.value.length} 个危化产品`
})
const freightAmountText = computed(() => {
if (plan.value?.hasFreight !== 1) return '—'
const n = Number(plan.value?.freightAmount)
return Number.isFinite(n) ? n.toFixed(2) : '—'
})
const freightRemarkText = computed(() => {
if (plan.value?.hasFreight !== 1) return '—'
return plan.value?.freightRemark?.trim() || '—'
})
const feedbackLines = computed(() => {
const lines: { title: string; text: string }[] = []
for (const link of siteLinks.value) {
const remark = String(link.siteFeedbackRemark || '').trim()
if (remark) {
lines.push({
title: `现场回传 · ${link.productName || '现场'}`,
text: remark
})
}
}
return lines
})
const actionSplit = computed(() => {
const p = plan.value
if (!p) return { primary: [], more: [] }
return splitMpPlanDetailActions({
canEdit: canEditPlan(p),
canArrange: canArrangeTransport(p),
arrangeLabel: arrangeTransportLabel(p),
canWait: canWaitNotice(p),
canCancelWait: canCancelWaitNotice(p),
canVoid: canVoidPlan(p)
})
})
function dibangText(stop: PlanLoad | PlanDelivery | null) {
if (!stop) return '—'
if (stop.generateDibang === 1) {
return `生成${stop.dibangDirection ? `(${stop.dibangDirection})` : ''}`
}
return '未生成'
}
async function copyText(text: string, label: string) {
const value = text.trim()
if (!value) {
ElMessage.warning('无可复制内容')
return
}
try {
await navigator.clipboard.writeText(value)
ElMessage.success(`已复制${label}`)
} catch {
ElMessage.error('复制失败')
}
}
async function loadDetail() {
const id = String(route.params.id || '')
@@ -94,7 +399,19 @@
try {
const res = await planApi.get(id)
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
deliveryIndex.value = 0
errorText.value = ''
if (plan.value.transportStatus === 'arranged') {
try {
const infoRes = await planApi.getArrangeInfo(id)
arrangeInfo.value = ((infoRes as { data?: PlanArrangeInfo }).data ??
infoRes) as PlanArrangeInfo
} catch {
arrangeInfo.value = null
}
} else {
arrangeInfo.value = null
}
} catch (error) {
errorText.value = error instanceof Error ? error.message : '加载失败'
} finally {
@@ -106,51 +423,66 @@
void router.push('/m/pl/plan')
}
function goEdit() {
if (!plan.value) return
void router.push(`/m/pl/plan/${plan.value.id}/edit`)
}
function goArrange() {
if (!plan.value) return
void router.push(`/m/pl/plan/${plan.value.id}/arrange`)
}
function goCopy() {
if (!plan.value) return
void router.push({ path: '/m/pl/plan/new', query: { copyFrom: plan.value.id } })
}
function goPrint() {
if (!plan.value) return
void router.push(`/m/pl/plan/${plan.value.id}/print`)
}
async function doWaitNotice() {
if (!plan.value) return
await ElMessageBox.confirm('确认将该计划标记为等客户通知?', '等客户通知', {
type: 'warning'
})
acting.value = true
try {
const res = await planApi.waitNotice(plan.value.id)
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
ElMessage.success('已标记为等客户通知')
} finally {
acting.value = false
async function runAction(kind: MpDetailActionKind) {
const p = plan.value
if (!p) return
moreOpen.value = false
if (kind === 'edit') {
void router.push(`/m/pl/plan/${p.id}/edit`)
return
}
}
async function doVoid() {
if (!plan.value) return
await ElMessageBox.confirm('确认作废该运输计划?此操作不可恢复。', '作废', { type: 'warning' })
acting.value = true
try {
const res = await planApi.cancel(plan.value.id)
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
ElMessage.success('已作废')
} finally {
acting.value = false
if (kind === 'arrange') {
void router.push(`/m/pl/plan/${p.id}/arrange`)
return
}
if (kind === 'copy') {
void router.push({ path: '/m/pl/plan/new', query: { copyFrom: p.id } })
return
}
if (kind === 'print') {
void router.push(`/m/pl/plan/${p.id}/print`)
return
}
if (kind === 'wait') {
await ElMessageBox.confirm('确认将该计划标记为等客户通知?', '等客户通知', {
type: 'warning'
})
acting.value = true
try {
const res = await planApi.waitNotice(p.id)
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
ElMessage.success('已标记为等客户通知')
} finally {
acting.value = false
}
return
}
if (kind === 'cancel-wait') {
await ElMessageBox.confirm('确认取消等通知,恢复为待安排?', '取消等通知', {
type: 'warning'
})
acting.value = true
try {
const res = await planApi.cancelWaitNotice(p.id)
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
ElMessage.success('已取消等通知')
} finally {
acting.value = false
}
return
}
if (kind === 'void') {
await ElMessageBox.confirm('确认作废该运输计划?此操作不可恢复。', '作废运输计划', {
type: 'warning'
})
acting.value = true
try {
const res = await planApi.cancel(p.id)
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
ElMessage.success('已作废')
} finally {
acting.value = false
}
}
}
@@ -158,3 +490,186 @@
void loadDetail()
})
</script>
<style scoped lang="scss">
.mp-wv-header__sub {
margin-top: 4px;
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
.mp-wv-detail-body {
padding-bottom: 12px;
}
.mp-wv-link-block {
margin-bottom: 10px;
}
.mp-wv-link-label {
margin-bottom: 4px;
font-size: 12px;
color: #6b7b74;
}
.mp-wv-link-row {
display: flex;
gap: 8px;
}
.mp-wv-link-input {
flex: 1;
min-width: 0;
height: 36px;
padding: 0 10px;
border: 1px solid #dcebe4;
border-radius: 8px;
font-size: 12px;
}
.mp-wv-site-link + .mp-wv-site-link {
margin-top: 8px;
}
.mp-wv-vehicle-box {
padding: 10px;
border: 1px solid #dcebe4;
border-radius: 10px;
background: #f7faf8;
}
.mp-wv-feedback {
margin-top: 10px;
display: grid;
gap: 8px;
}
.mp-wv-feedback-box {
padding: 10px;
border: 1px dashed #dcebe4;
border-radius: 10px;
background: #f7faf8;
font-size: 12px;
}
.mp-wv-feedback-box h4 {
margin: 0 0 6px;
font-size: 12px;
color: #087f57;
}
.mp-wv-stop {
margin-bottom: 12px;
}
.mp-wv-stop-title {
margin-bottom: 6px;
font-size: 13px;
font-weight: 700;
}
.mp-wv-delivery-tabs {
display: flex;
gap: 6px;
overflow-x: auto;
margin: 8px 0;
}
.mp-wv-delivery-tab {
flex: 0 0 auto;
padding: 6px 12px;
border: 1px solid #dcebe4;
border-radius: 999px;
background: #fff;
font-size: 12px;
color: #6b7b74;
cursor: pointer;
}
.mp-wv-delivery-tab.active {
color: #fff;
background: #0a9f6d;
border-color: #0a9f6d;
font-weight: 600;
}
.mp-wv-prod-table {
margin-top: 8px;
border: 1px solid #dcebe4;
border-radius: 10px;
overflow: hidden;
}
.mp-wv-prod-empty,
.mp-wv-prod-row {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #eef4f1;
}
.mp-wv-prod-row:last-child {
border-bottom: 0;
}
.mp-wv-prod-name {
margin-bottom: 6px;
font-size: 13px;
font-weight: 700;
color: #087f57;
}
.mp-wv-prod-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px 10px;
color: #6b7b74;
}
.mp-wv-prod-grid b {
color: #1f2a26;
font-weight: 500;
}
.mp-wv-qty {
display: grid;
gap: 4px;
padding: 10px;
border-radius: 10px;
background: #f7faf8;
font-size: 12px;
}
.mp-wv-qty .is-ok {
color: #087f57;
font-weight: 600;
}
.mp-wv-cert-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
font-size: 12px;
color: #6b7b74;
}
.mp-wv-cert-hint {
margin: 10px 0 0;
font-size: 12px;
color: #6b7b74;
line-height: 1.45;
}
.mp-wv-remark {
margin: 0;
font-size: 13px;
line-height: 1.55;
white-space: pre-wrap;
word-break: break-word;
}
.is-danger {
color: #d14343 !important;
}
</style>