| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404 |
- <template>
- <div class="water-page device-page">
- <div class="page-heading">
- <div class="page-title">
- <span class="page-title-icon"><el-icon><component :is="pageIcon"/></el-icon></span>
- <div><h2>{{ title }}</h2><span class="muted">{{ subtitle }}</span></div>
- </div>
- <el-button class="heading-refresh" :icon="Refresh" :loading="loading" @click="loadData">刷新</el-button>
- </div>
- <div v-if="view === 'abnormal'" class="metric-grid">
- <div v-for="item in abnormalStats" :key="item.label" class="metric">
- <div class="metric-icon">
- <el-icon>
- <component :is="item.icon"/>
- </el-icon>
- </div>
- <div class="metric-body">
- <span>{{ item.label }}</span>
- <div class="metric-value"><strong>{{ item.value }}</strong><small>{{ item.unit }}</small></div>
- </div>
- </div>
- </div>
- <div v-if="view === 'ledger'" class="metric-grid">
- <div v-for="item in ledgerStatsCards" :key="item.label" class="metric">
- <div class="metric-icon">
- <el-icon>
- <component :is="item.icon"/>
- </el-icon>
- </div>
- <div class="metric-body">
- <span>{{ item.label }}</span>
- <div class="metric-value"><strong>{{ item.value }}</strong><small>{{ item.unit }}</small></div>
- </div>
- </div>
- </div>
- <el-card shadow="never" class="filter-card">
- <el-form :inline="true" :model="query">
- <el-form-item v-if="view !== 'gis'" label="设备名称">
- <el-input v-model="query.equipmentName" clearable/>
- </el-form-item>
- <el-form-item v-if="view !== 'gis'" label="设备编码">
- <el-input v-model="query.equipmentCode" clearable/>
- </el-form-item>
- <el-form-item v-if="view === 'ledger'" label="报警状态">
- <el-select v-model="query.alarmStatus" clearable class="filter-select" style="width:180px">
- <el-option label="正常" :value="0"/>
- <el-option label="报警" :value="1"/>
- </el-select>
- </el-form-item>
- <el-form-item v-if="view === 'ledger' || view === 'dispatch' || view === 'abnormal'" label="报警等级">
- <el-select v-model="query.alertLevel" clearable class="filter-select" style="width:180px">
- <el-option v-for="item in levels" :key="item.value" v-bind="item"/>
- </el-select>
- </el-form-item>
- <el-form-item v-if="view === 'dispatch' || view === 'abnormal'" label="异常类型">
- <el-select v-model="query.abnormalType" clearable class="filter-select" style="width:160px">
- <el-option label="报警" value="alarm"/>
- <el-option label="维修" value="repair"/>
- <el-option label="离线" value="offline"/>
- </el-select>
- </el-form-item>
- <el-form-item v-if="view === 'dispatch' || view === 'abnormal'" label="区域">
- <el-select v-model="query.region" clearable class="filter-select" style="width:140px">
- <el-option label="城南" value="城南"/>
- <el-option label="城北" value="城北"/>
- <el-option label="太常" value="太常"/>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadData">查询</el-button>
- <el-button @click="reset">重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <el-card shadow="never" class="water-panel">
- <div class="card-title"><span><el-icon><Monitor/></el-icon> {{ title }}清单</span>
- <div class="panel-actions">
- <el-button v-if="view === 'ledger'" type="primary" plain size="small" :icon="Plus"
- @click="openCreate">新增
- </el-button>
- <el-button v-if="view === 'ledger'" type="danger" plain size="small" :icon="Delete"
- :disabled="!selectedDevices.length" @click="removeSelectedDevices">删除
- </el-button>
- <el-button v-if="view === 'ledger'" type="info" plain size="small" :icon="Upload"
- @click="openImport">导入
- </el-button>
- <el-button v-if="view !== 'gis'" type="success" plain size="small" :icon="Download"
- @click="exportData">导出
- </el-button>
- </div>
- </div>
- <el-alert v-if="loadError" :title="loadError" type="error" show-icon :closable="false" class="load-error">
- <el-button size="small" type="danger" plain @click="loadData">重试</el-button>
- </el-alert>
- <el-empty v-if="view === 'gis' && !loading && !loadError && !page.records.length"
- description="暂无设备 GIS 点位数据"/>
- <el-table :data="page.records" border stripe v-loading="loading" row-key="equipmentId"
- @selection-change="handleDeviceSelectionChange">
- <el-table-column v-if="view === 'ledger'" type="selection" width="48" align="center"/>
- <el-table-column type="index" label="#" width="55"/>
- <el-table-column prop="equipmentCode" label="设备编码" min-width="130"/>
- <el-table-column prop="equipmentName" label="设备名称" min-width="150"/>
- <el-table-column prop="equipmentTypeName" label="设备类型" min-width="120"/>
- <el-table-column v-if="view === 'abnormal' || view === 'dispatch'" label="所属区域" width="110">
- <template #default="{ row }">{{ row.district || '-' }}</template>
- </el-table-column>
- <el-table-column prop="equipmentLocation" label="安装位置" min-width="180" show-overflow-tooltip/>
- <el-table-column v-if="view === 'ledger' || view === 'abnormal'" label="设备图片" width="110" align="center">
- <template #default="{ row }">
- <el-image v-if="row.equipmentImageUrl" :src="row.equipmentImageUrl"
- :preview-src-list="[row.equipmentImageUrl]" preview-teleported hide-on-click-modal
- fit="cover" class="device-image"/>
- <span v-else class="image-placeholder">暂无图片</span></template>
- </el-table-column>
- <el-table-column v-if="view !== 'maintain'" label="设备状态" width="100">
- <template #default="{ row }">
- <el-tag :type="Number(row.currentStatus) === 3 ? 'danger' : 'success'">{{ row.currentStatusText
- }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column v-if="view !== 'maintain'" label="在线状态" width="100">
- <template #default="{ row }">
- <el-tag :type="Number(row.onlineStatus) === 1 ? 'success' : 'info'">{{ row.onlineStatusText }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column v-if="view !== 'maintain'" label="报警状态" width="100">
- <template #default="{ row }">
- <el-tag :type="Number(row.alarmStatus) === 1 ? 'danger' : 'success'">{{ row.alarmStatusText }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column v-if="view !== 'maintain'" label="报警等级" width="110">
- <template #default="{ row }">
- <el-tag :type="alarmLevelType(row.alertLevel)">{{ row.alarmLevelText }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column v-if="view === 'abnormal' || view === 'dispatch'" label="异常原因" min-width="150"
- show-overflow-tooltip>
- <template #default="{ row }">{{ row.abnormalReasonText || '-' }}</template>
- </el-table-column>
- <el-table-column label="关联工单" min-width="190" show-overflow-tooltip>
- <template #default="{ row }">{{ view === 'maintain' ? (row.orderNo || '-') : row.relatedOrderNo }}
- </template>
- </el-table-column>
- <el-table-column label="工单状态" min-width="110">
- <template #default="{ row }">
- <el-tag v-if="view !== 'maintain' && row.workOrderStatusText !== '暂无工单'"
- :type="workOrderStatusType(row.workOrderStatusCode)">{{ row.workOrderStatusText }}
- </el-tag>
- <span v-else>{{ view === 'maintain' ? (row.status || '暂无') : row.workOrderStatusText }}</span>
- </template>
- </el-table-column>
- <el-table-column v-if="view === 'abnormal' || view === 'dispatch'" label="接单责任人" min-width="110">
- <template #default="{ row }">{{ view === 'maintain' ? (row.maintainPerson || '-') :
- (row.relatedWorkOrderPerson || '-') }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="210" fixed="right">
- <template #default="{ row }">
- <div class="facility-row-actions">
- <el-tooltip content="查看详情" placement="top">
- <el-button class="facility-row-action is-detail" aria-label="查看详情" size="small"
- type="primary" plain circle :icon="View" @click="selectPoint(row)"/>
- </el-tooltip>
- <el-tooltip v-if="view === 'ledger'" content="修改信息" placement="top">
- <el-button class="facility-row-action is-edit" aria-label="修改信息" size="small"
- type="warning" plain circle :icon="Edit" @click="openEdit(row)"/>
- </el-tooltip>
- <el-tooltip v-if="canDispatch(row)" content="维修派单" placement="top">
- <el-button class="facility-row-action is-dispatch" aria-label="维修派单" size="small"
- type="warning" plain circle :icon="Position" @click="openOrder(row)"/>
- </el-tooltip>
- <el-tooltip v-if="view === 'ledger'" content="删除设备" placement="top">
- <el-button class="facility-row-action is-delete" aria-label="删除设备" size="small"
- type="danger" plain circle :icon="Delete" @click="removeDevice(row)"/>
- </el-tooltip>
- <el-tooltip v-if="view === 'maintain'" content="删除记录" placement="top">
- <el-button class="facility-row-action is-delete" aria-label="删除记录" size="small"
- type="danger" plain circle :icon="Delete" @click="removeRecord(row)"/>
- </el-tooltip>
- </div>
- </template>
- </el-table-column>
- </el-table>
- <el-pagination v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="page.total"
- layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]" @change="loadData"/>
- </el-card>
- <el-drawer v-model="detailVisible" class="detail-drawer device-detail-drawer" title="设备详情" direction="rtl"
- size="680px" append-to-body>
- <div class="detail-summary"><span class="detail-name">{{ detail.equipmentName || '-' }}</span>
- <el-tag :type="Number(detail.currentStatus) === 3 ? 'danger' : 'success'">{{ detail.currentStatusText ||
- '-' }}
- </el-tag>
- <el-tag :type="Number(detail.alarmStatus) === 1 ? 'danger' : 'success'">{{ detail.alarmStatusText || '-'
- }}
- </el-tag>
- <el-tag v-if="detail.workOrderStatusText && detail.workOrderStatusText !== '暂无工单'"
- :type="workOrderStatusType(detail.workOrderStatusCode)">{{ detail.workOrderStatusText }}
- </el-tag>
- </div>
- <div v-if="view === 'ledger' || view === 'abnormal'" class="detail-media">
- <div class="detail-media-label">设备图片</div>
- <el-image v-if="detail.equipmentImageUrl" :src="detail.equipmentImageUrl"
- :preview-src-list="[detail.equipmentImageUrl]" preview-teleported hide-on-click-modal
- fit="cover"
- class="detail-image"/>
- <div v-else class="detail-image-placeholder">暂无图片</div>
- </div>
- <el-descriptions :column="2" border>
- <el-descriptions-item v-for="item in detailFields" :key="item.label" :label="item.label">{{ item.value
- }}
- </el-descriptions-item>
- </el-descriptions>
- <template #footer>
- <el-button @click="detailVisible = false">关闭</el-button>
- </template>
- </el-drawer>
- <el-drawer v-model="editVisible" class="detail-drawer device-edit-drawer"
- :title="editMode === 'create' ? '新增设备信息' : '修改设备信息'" direction="rtl" size="680px"
- append-to-body>
- <el-form ref="editFormRef" :model="editForm" label-width="110px" :rules="editRules">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="设备编码" prop="equipmentCode">
- <el-input v-model="editForm.equipmentCode" :disabled="editMode === 'edit'" maxlength="50"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备类型" prop="equipmentTypeId">
- <el-select v-if="editMode === 'create'" v-model="editForm.equipmentTypeId" filterable
- class="full-input">
- <el-option v-for="item in waterDeviceTypes" :key="item.id" :label="item.typeName"
- :value="item.id"/>
- </el-select>
- <el-input v-else v-model="editForm.equipmentTypeName" disabled/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备名称" prop="equipmentName">
- <el-input v-model="editForm.equipmentName" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备型号">
- <el-input v-model="editForm.equipmentModel" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="所属片区">
- <el-select v-model="editForm.district" clearable class="full-input">
- <el-option label="城南" value="城南"/>
- <el-option label="城北" value="城北"/>
- <el-option label="太常" value="太常"/>
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="制造商">
- <el-input v-model="editForm.manufacturer" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="经度">
- <el-input-number v-model="editForm.longitude" :precision="8" :controls="false"
- class="full-input"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="纬度">
- <el-input-number v-model="editForm.latitude" :precision="8" :controls="false"
- class="full-input"/>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="安装位置">
- <el-input v-model="editForm.equipmentLocation" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备负责人">
- <el-input v-model="editForm.maintainer" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="联系方式">
- <el-input v-model="editForm.maintainerPhone" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="权属单位">
- <el-input v-model="editForm.ownershipUnit" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="设备图片">
- <ImageUpload v-model="editForm.equipmentImage" :limit="1" :file-size="5"/>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="备注">
- <el-input v-model="editForm.remark" type="textarea" :rows="3" maxlength="500"
- show-word-limit/>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <el-button @click="editVisible = false">取消</el-button>
- <el-button type="primary" :loading="editSaving" @click="submitEdit">保存</el-button>
- </template>
- </el-drawer>
- <el-dialog v-model="dispatchVisible" class="dispatch-dialog" title="设备维修派单" width="860px">
- <div class="dispatch-layout">
- <div class="assignee-picker">
- <aside class="assignee-depts">
- <el-input v-model="deptFilterText" clearable placeholder="筛选部门" size="small"/>
- <el-tree ref="deptTreeRef" :data="deptTreeData" node-key="deptId" highlight-current
- :props="{ label: 'deptName', children: 'children' }"
- :filter-node-method="filterDeptNode" class="assignee-tree"
- @node-click="handleDeptClick"/>
- </aside>
- <section class="assignee-users">
- <div class="assignee-search">
- <el-input v-model="assigneeKeyword" clearable placeholder="搜索用户名、昵称或手机号"
- @keyup.enter="searchAssigneeUsers"/>
- <el-button type="primary" :loading="userLoading" @click="searchAssigneeUsers">查询
- </el-button>
- </div>
- <el-alert v-if="userLoadError" :title="userLoadError" type="error" show-icon :closable="false">
- <el-button size="small" type="danger" plain @click="retryAssigneeLoading">重试</el-button>
- </el-alert>
- <el-table :data="users" border row-key="userId" highlight-current-row
- :current-row-key="assigneeDraft?.userId" @row-click="chooseAssignee">
- <el-table-column label="选择" width="58" align="center">
- <template #default="{ row }">
- <el-radio :model-value="assigneeDraft?.userId" :label="row.userId"
- @click.stop="chooseAssignee(row)">
- </el-radio>
- </template>
- </el-table-column>
- <el-table-column prop="nickName" label="昵称" min-width="100"/>
- <el-table-column prop="userName" label="用户名" min-width="110"/>
- <el-table-column prop="deptName" label="部门" min-width="120" show-overflow-tooltip/>
- <el-table-column prop="phone" label="手机号" min-width="130"/>
- <el-table-column prop="statusText" label="账号状态" width="90"/>
- </el-table>
- <el-empty v-if="!userLoading && !userLoadError && !users.length"
- description="暂无符合条件的责任人" :image-size="80"/>
- <el-pagination v-model:current-page="assigneePageNum" v-model:page-size="assigneePageSize"
- :total="assigneeTotal" layout="total, sizes, prev, pager, next"
- :page-sizes="[10, 20, 50]" @current-change="loadAssigneeUsers"
- @size-change="handleAssigneeSizeChange"/>
- <div class="assignee-picked">已选责任人:<strong v-if="assigneeDraft">{{ assigneeDraft.nickName
- || assigneeDraft.userName }}</strong><span v-else>未选择</span>
- <el-button size="small" type="primary" plain :disabled="!assigneeDraft"
- @click="confirmAssignee">确认选择
- </el-button>
- </div>
- </section>
- </div>
- <el-form class="dispatch-form" :model="dispatchForm" label-width="100px">
- <el-form-item label="设备"><span>{{ dispatchDevice.equipmentName }}({{ dispatchDevice.equipmentCode }})</span>
- </el-form-item>
- <el-form-item label="工单类型"><span>1-故障维修</span></el-form-item>
- <el-form-item label="优先级" required>
- <el-select v-model="dispatchForm.orderLevel" placeholder="请选择优先级">
- <el-option label="紧急" :value="0"/>
- <el-option label="一般" :value="1"/>
- <el-option label="低" :value="2"/>
- </el-select>
- </el-form-item>
- <el-form-item label="问题描述" required>
- <el-input v-model="dispatchForm.description" type="textarea" :rows="5" maxlength="500"
- show-word-limit placeholder="请输入故障或异常描述"/>
- </el-form-item>
- <el-form-item label="接单责任人" required>
- <span v-if="selectedAssignee" class="assignee-summary">{{ selectedAssignee.nickName || selectedAssignee.userName }}({{ selectedAssignee.deptName || '-' }})</span>
- <span v-else class="assignee-placeholder">请在左侧选择责任人</span>
- </el-form-item>
- </el-form>
- </div>
- <template #footer>
- <el-button @click="dispatchVisible = false">取消</el-button>
- <el-button type="primary" :loading="dispatching" @click="submitDispatch">确认派单</el-button>
- </template>
- </el-dialog>
- <el-dialog v-model="importVisible" title="供水设备批量导入" width="520px" append-to-body @closed="resetImport">
- <el-upload ref="importUploadRef" drag :limit="1" accept=".xlsx,.xls" :auto-upload="false"
- :file-list="importFileList" :on-change="handleImportChange" :on-remove="handleImportRemove"
- :disabled="importUploading">
- <el-icon class="el-icon--upload">
- <Upload/>
- </el-icon>
- <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
- <template #tip>
- <div class="el-upload__tip import-tip">仅支持 xls、xlsx;设备类型必须属于供水类型树。</div>
- </template>
- </el-upload>
- <el-checkbox v-model="importUpdateSupport" class="import-update">更新已存在的设备编码</el-checkbox>
- <template #footer>
- <el-button type="primary" :loading="importUploading" @click="submitImport">开始导入</el-button>
- <el-button @click="importVisible = false">取消</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import {computed, onMounted, ref, watch} from 'vue'
- import {ElMessage, ElMessageBox} from 'element-plus'
- import {
- Monitor,
- Refresh,
- Warning,
- Bell,
- Tools,
- DataLine,
- Upload,
- Download,
- Search,
- Plus,
- Delete,
- View,
- Edit,
- Position
- } from '@element-plus/icons-vue'
- import {listUser} from '@/api/system/user'
- import {listDept} from '@/api/system/dept'
- import {handleTree} from '@/utils/ruoyi'
- import {
- createAbnormalOrder,
- createWaterDevice,
- deleteMaintainRecord,
- deleteWaterDevices,
- exportAbnormalDevices,
- exportDeviceLedger,
- getAbnormalDeviceStats,
- getDeviceLedgerStatistics,
- getWaterDeviceById,
- importDeviceLedger,
- listAbnormalDevices,
- listDeviceGisPoints,
- listDeviceLedger,
- listMaintainRecords,
- updateWaterDevice
- } from '@/api/pipeNetwork/waterSupply'
- import {listEquipmentTypeAll} from '@/api/pipeNetwork/pdeviceInform'
- import {normalizePage, readMonitoringExportError} from '@/utils/waterSupplyModel'
- import {
- buildAssigneeQuery,
- buildMaintainOrderPayload,
- canDispatchDevice,
- normalizeAssigneeUser,
- normalizeDeviceGisPoints,
- normalizeDeviceRow,
- normalizeDispatchOrderResponse,
- preserveDevicePageOnError,
- selectSingleAssignee
- } from '@/utils/waterSupplyUiModel'
- import {saveAs} from 'file-saver'
- import {orderStatusType as workOrderStatusType} from '@/views/subSystem/pipeNetwork/ops/workOrder/workOrderDict.js'
- const props = defineProps({
- title: {type: String, required: true},
- subtitle: {type: String, default: '设备信息、运行状态与维护过程管理'},
- view: {type: String, default: 'ledger'}
- })
- const {title, subtitle, view} = props
- const query = ref({
- pageNum: 1,
- pageSize: 10,
- equipmentName: '',
- equipmentCode: '',
- alarmStatus: undefined,
- alertLevel: undefined,
- abnormalType: undefined,
- region: undefined
- })
- const importVisible = ref(false);
- const importUploading = ref(false);
- const importUpdateSupport = ref(false);
- const importFileList = ref([]);
- const importUploadRef = ref(null);
- const editVisible = ref(false);
- const editSaving = ref(false);
- const editFormRef = ref(null);
- const editForm = ref({})
- const editMode = ref('edit');
- const selectedDevices = ref([]);
- const waterDeviceTypes = ref([])
- const editRules = {
- equipmentCode: [{required: true, message: '请输入设备编码', trigger: 'blur'}],
- equipmentTypeId: [{required: true, message: '请选择设备类型', trigger: 'change'}],
- equipmentName: [{required: true, message: '请输入设备名称', trigger: 'blur'}]
- }
- const page = ref({records: [], total: 0});
- const ledgerStats = ref({total: 0, online: 0, offline: 0, onlineRate: 0});
- const abnormalStatsData = ref({total: 0, alarm: 0, repair: 0, offline: 0});
- const loading = ref(false);
- const loadError = ref('');
- const detailVisible = ref(false);
- const detail = ref({});
- const dispatchVisible = ref(false);
- const dispatching = ref(false);
- const dispatchDevice = ref({});
- const dispatchForm = ref({description: '', receiveUserId: undefined});
- const users = ref([]);
- const userLoading = ref(false);
- const userLoadError = ref('');
- const deptLoadError = ref('');
- const selectedAssignee = ref(null);
- const assigneeDraft = ref(null);
- const deptTreeRef = ref(null);
- const deptTreeData = ref([]);
- const deptFilterText = ref('');
- const assigneeKeyword = ref('');
- const assigneeDeptId = ref(undefined);
- const assigneePageNum = ref(1);
- const assigneePageSize = ref(10);
- const assigneeTotal = ref(0)
- const levels = [{label: '一级报警', value: 1}, {label: '二级报警', value: 2}, {
- label: '三级报警',
- value: 3
- }, {label: '四级报警', value: 4}];
- const pageIcon = Monitor;
- const alarmLevelType = level => ({1: 'danger', 2: 'warning', 3: 'primary', 4: 'success'}[Number(level)] || 'info')
- function canDispatch(row) {
- if (view !== 'dispatch') return false
- return canDispatchDevice(row)
- }
- const abnormalStats = computed(() => {
- return [
- {label: '异常设备', value: abnormalStatsData.value.total, unit: '台', icon: Warning},
- {label: '报警中', value: abnormalStatsData.value.alarm, unit: '台', icon: Bell},
- {label: '维修中', value: abnormalStatsData.value.repair, unit: '台', icon: Tools},
- {label: '离线设备', value: abnormalStatsData.value.offline, unit: '台', icon: DataLine}
- ]
- })
- const ledgerStatsCards = computed(() => [
- {label: '设备数量', value: ledgerStats.value.total, unit: '台', icon: Monitor},
- {label: '在线设备', value: ledgerStats.value.online, unit: '台', icon: DataLine},
- {label: '离线设备', value: ledgerStats.value.offline, unit: '台', icon: Warning},
- {label: '在线率', value: ledgerStats.value.onlineRate, unit: '%', icon: Tools}
- ])
- const abnormalViews = ['abnormal', 'dispatch']
- const detailFields = computed(() => [
- ['设备编码', detail.value.equipmentCode],
- ['设备类型', detail.value.equipmentTypeName || detail.value.typeName],
- ...(abnormalViews.includes(view) ? [['所属区域', detail.value.district]] : []),
- ['安装位置', detail.value.equipmentLocation],
- ['设备状态', detail.value.currentStatusText],
- ['在线状态', detail.value.onlineStatusText],
- ['报警状态', detail.value.alarmStatusText],
- ['报警等级', detail.value.alarmLevelText],
- ['异常原因', detail.value.abnormalReasonText],
- ['关联工单', detail.value.relatedOrderNo || detail.value.orderNo],
- ['工单状态', detail.value.workOrderStatusText || detail.value.status],
- ['接单责任人', detail.value.relatedWorkOrderPerson || detail.value.maintainPerson],
- ['状态更新时间', detail.value.statusUpdateTime]
- ].map(([label, value]) => ({
- label,
- value: value ?? '-'
- })))
- async function loadData() {
- loading.value = true;
- loadError.value = '';
- try {
- if (view === 'gis') {
- const response = await listDeviceGisPoints({...query.value});
- const records = normalizeDeviceGisPoints(response).map(normalizeDeviceRow);
- page.value = {records, total: records.length, current: 1, size: records.length || 10};
- return
- }
- let response;
- if (view === 'maintain') response = await listMaintainRecords({...query.value}); else if (view === 'dispatch' || view === 'abnormal') response = await listAbnormalDevices({...query.value, ...(view === 'dispatch' ? {dispatchableOnly: true} : {})}); else response = await listDeviceLedger({...query.value});
- const normalized = normalizePage(response);
- page.value = {...normalized, records: normalized.records.map(normalizeDeviceRow)}
- if (view === 'ledger') await loadLedgerStats()
- if (view === 'abnormal') await loadAbnormalStats()
- } catch (error) {
- const retained = preserveDevicePageOnError(page.value);
- page.value = retained.page;
- loadError.value = retained.error;
- console.warn(`[${title}] 数据加载失败`, error)
- } finally {
- loading.value = false
- }
- }
- async function loadLedgerStats() {
- try {
- const response = await getDeviceLedgerStatistics()
- const stats = response?.data ?? response ?? {}
- ledgerStats.value = {
- total: Number(stats.total ?? 0) || 0,
- online: Number(stats.online ?? 0) || 0,
- offline: Number(stats.offline ?? 0) || 0,
- onlineRate: Number(stats.onlineRate ?? 0) || 0
- }
- } catch (error) {
- console.warn('设备台账统计加载失败', error)
- }
- }
- async function loadAbnormalStats() {
- try {
- const response = await getAbnormalDeviceStats()
- const stats = response?.data ?? response ?? {}
- abnormalStatsData.value = {
- total: Number(stats.total ?? 0) || 0,
- alarm: Number(stats.alarm ?? 0) || 0,
- repair: Number(stats.repair ?? 0) || 0,
- offline: Number(stats.offline ?? 0) || 0
- }
- } catch (error) {
- console.warn('[设备异常管理] 统计加载失败', error)
- }
- }
- function reset() {
- query.value = {
- pageNum: 1,
- pageSize: 10,
- equipmentName: '',
- equipmentCode: '',
- alarmStatus: undefined,
- alertLevel: undefined,
- abnormalType: undefined,
- region: undefined
- };
- loadData()
- }
- function selectPoint(row) {
- detail.value = normalizeDeviceRow(row);
- detailVisible.value = true
- }
- watch(deptFilterText, value => deptTreeRef.value?.filter(value))
- function filterDeptNode(value, data) {
- return !value || String(data.deptName || '').includes(value)
- }
- async function loadDeptTree(force = false) {
- if (!force && deptTreeData.value.length) return true;
- deptLoadError.value = '';
- try {
- const response = await listDept();
- deptTreeData.value = handleTree(response.data || [], 'deptId');
- return true
- } catch (error) {
- deptLoadError.value = '部门列表加载失败,请点击重试';
- userLoadError.value = deptLoadError.value;
- return false
- }
- }
- async function loadAssigneeUsers() {
- userLoading.value = true;
- userLoadError.value = '';
- try {
- const response = await listUser(buildAssigneeQuery({
- deptId: assigneeDeptId.value,
- keyword: assigneeKeyword.value,
- pageNum: assigneePageNum.value,
- pageSize: assigneePageSize.value
- }));
- const rows = response.rows || response.data?.rows || [];
- users.value = rows.map(normalizeAssigneeUser).filter(user => user.eligible);
- assigneeTotal.value = Number(response.total ?? response.data?.total ?? users.value.length) || 0
- } catch (error) {
- userLoadError.value = '责任人列表加载失败,请点击重试'
- } finally {
- userLoading.value = false
- }
- }
- function searchAssigneeUsers() {
- assigneePageNum.value = 1;
- loadAssigneeUsers()
- }
- function handleAssigneeSizeChange() {
- assigneePageNum.value = 1;
- loadAssigneeUsers()
- }
- async function retryAssigneeLoading() {
- const deptLoaded = await loadDeptTree(true);
- if (!deptLoaded) return;
- await loadAssigneeUsers()
- }
- async function openOrder(row) {
- dispatchDevice.value = row
- dispatchForm.value = {
- description: row.abnormalReason || row.abnormalReasonText || row.description || '',
- orderLevel: 1,
- receiveUserId: undefined
- }
- selectedAssignee.value = null
- assigneeDraft.value = null
- assigneeDeptId.value = undefined
- assigneeKeyword.value = ''
- assigneePageNum.value = 1
- dispatchVisible.value = true
- await retryAssigneeLoading()
- }
- function handleDeptClick(node) {
- assigneeDeptId.value = node.deptId;
- assigneePageNum.value = 1;
- loadAssigneeUsers()
- }
- function chooseAssignee(row) {
- assigneeDraft.value = selectSingleAssignee([row])
- }
- function confirmAssignee() {
- selectedAssignee.value = assigneeDraft.value;
- dispatchForm.value.receiveUserId = assigneeDraft.value?.userId
- }
- async function submitDispatch() {
- if (!dispatchForm.value.description?.trim()) return ElMessage.warning('请填写问题描述')
- if (!dispatchForm.value.receiveUserId) return ElMessage.warning('请选择接单责任人')
- dispatching.value = true
- try {
- const response = await createAbnormalOrder(buildMaintainOrderPayload(dispatchDevice.value, dispatchForm.value))
- const summary = normalizeDispatchOrderResponse(response)
- if (!summary) throw new Error('派单失败:服务端未返回有效的待接单工单')
- ElMessage.success(`已派单,等待接单(${summary.orderNo})`)
- dispatchVisible.value = false
- await loadData()
- } catch (error) {
- const responseData = error?.response?.data
- const message = responseData?.msg || responseData?.message || error?.message || '派单失败,请检查权限或服务状态'
- // A conflict means another order now owns the device; refresh the list so its order number is visible.
- if (/已有|冲突|重复|处理中的工单|关联工单/.test(String(message))) {
- await loadData()
- ElMessage.warning(`该设备已有相关处置工单:${message}`)
- } else {
- ElMessage.error(message)
- }
- } finally {
- dispatching.value = false
- }
- }
- async function removeRecord(row) {
- try {
- await deleteMaintainRecord(row.maintainId || row.id);
- ElMessage.success('记录已删除');
- await loadData()
- } catch (error) {
- ElMessage.error('删除失败')
- }
- }
- function emptyDeviceForm() {
- return {
- equipmentId: undefined, equipmentCode: '', equipmentTypeId: undefined, equipmentTypeName: '',
- equipmentName: '', equipmentModel: '', district: '', manufacturer: '', longitude: undefined,
- latitude: undefined, equipmentLocation: '', maintainer: '', maintainerPhone: '', ownershipUnit: '',
- equipmentImage: '', remark: ''
- }
- }
- function isWaterDeviceType(type, typesById) {
- if (!type || (type.typeName === '供水' && String(type.parentId) === '0')) return false
- let cursor = type
- let guard = 0
- while (cursor && guard++ <= typesById.size) {
- if (cursor.typeName === '供水' && String(cursor.parentId) === '0') return true
- cursor = typesById.get(cursor.parentId)
- }
- return false
- }
- async function loadWaterTypes() {
- if (waterDeviceTypes.value.length) return
- try {
- const response = await listEquipmentTypeAll()
- const rows = response?.data || []
- const typesById = new Map(rows.map(item => [item.id, item]))
- waterDeviceTypes.value = rows.filter(item => isWaterDeviceType(item, typesById))
- .map(({id, typeName}) => ({id, typeName}))
- } catch (error) {
- ElMessage.warning('供水设备类型加载失败,请重试')
- }
- }
- async function openCreate() {
- editMode.value = 'create'
- editVisible.value = true
- editSaving.value = false
- editForm.value = emptyDeviceForm()
- await loadWaterTypes()
- }
- function handleDeviceSelectionChange(rows) {
- selectedDevices.value = rows
- }
- async function confirmDeleteDevices(ids) {
- await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 台设备吗?删除后不可恢复。`, '删除设备', {type: 'warning'})
- await deleteWaterDevices(ids)
- ElMessage.success('删除成功')
- selectedDevices.value = []
- query.value.pageNum = 1
- await loadData()
- }
- async function removeDevice(row) {
- if (!row?.equipmentId) return
- await confirmDeleteDevices([row.equipmentId])
- }
- async function removeSelectedDevices() {
- const ids = selectedDevices.value.map(row => row.equipmentId).filter(Boolean)
- if (!ids.length) return
- await confirmDeleteDevices(ids)
- }
- async function openEdit(row) {
- editMode.value = 'edit'
- editVisible.value = true
- editSaving.value = false
- editForm.value = {...row}
- try {
- const response = await getWaterDeviceById(row.equipmentId)
- const full = response?.data || response
- if (full) editForm.value = {...editForm.value, ...full}
- } catch (error) {
- ElMessage.warning('设备详情加载失败,已使用列表数据填充')
- }
- }
- async function submitEdit() {
- try {
- await editFormRef.value?.validate()
- } catch {
- return
- }
- editSaving.value = true
- try {
- const creating = editMode.value === 'create'
- if (creating) await createWaterDevice(editForm.value)
- else await updateWaterDevice(editForm.value)
- ElMessage.success(creating ? '新增成功' : '修改成功')
- editVisible.value = false
- if (creating) query.value.pageNum = 1
- await loadData()
- } catch (error) {
- ElMessage.error(error?.response?.data?.msg || error?.message || (editMode.value === 'create' ? '新增失败,请稍后重试' : '修改失败,请稍后重试'))
- } finally {
- editSaving.value = false
- }
- }
- function openImport() {
- importVisible.value = true
- }
- function handleImportChange(file, fileList) {
- if (!/\.(xlsx|xls)$/iu.test(file?.name || '')) {
- ElMessage.error('仅支持 xls、xlsx 文件')
- importFileList.value = []
- return false
- }
- importFileList.value = fileList.slice(-1)
- }
- function handleImportRemove(file, fileList) {
- importFileList.value = fileList
- }
- function resetImport() {
- importUploading.value = false
- importUpdateSupport.value = false
- importFileList.value = []
- importUploadRef.value?.clearFiles()
- }
- async function submitImport() {
- if (!importFileList.value.length) return ElMessage.warning('请选择 xls 或 xlsx 文件')
- importUploading.value = true
- try {
- const response = await importDeviceLedger(importFileList.value[0].raw, importUpdateSupport.value)
- if (response?.code && response.code !== 200) throw new Error(response.msg || response.message || '导入失败,请检查文件内容')
- ElMessage.success(response?.msg || response?.data || '导入成功')
- importVisible.value = false
- await loadData()
- } catch (error) {
- ElMessage.error(error?.response?.data?.msg || error?.response?.data?.message || error?.message || '导入失败,请稍后重试')
- } finally {
- importUploading.value = false
- }
- }
- async function exportData() {
- try {
- const response = await (view === 'dispatch' || view === 'abnormal' ? exportAbnormalDevices : exportDeviceLedger)({...query.value})
- const errorMessage = await readMonitoringExportError(response)
- if (errorMessage) throw new Error(errorMessage)
- const blob = response instanceof Blob ? response : new Blob([response], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})
- saveAs(blob, `${title}_${new Date().toISOString().slice(0, 10)}.xlsx`)
- ElMessage.success('导出成功')
- } catch (error) {
- const backendMessage = await readMonitoringExportError(error?.response?.data)
- ElMessage.error(backendMessage || error?.message || '导出失败,请稍后重试')
- }
- }
- onMounted(loadData)
- </script>
- <style scoped>
- .device-page {
- padding: 20px;
- background: linear-gradient(135deg, #eff6ff 0%, #f8fafc 45%, #eef2f7 100%);
- min-height: calc(100vh - 84px)
- }
- .page-heading {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 16px;
- margin-bottom: 16px
- }
- .page-title {
- display: flex;
- align-items: center;
- gap: 12px;
- min-width: 0
- }
- .page-title-icon {
- display: grid;
- place-items: center;
- width: 42px;
- height: 42px;
- color: #fff;
- background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
- border-radius: 8px;
- box-shadow: 0 8px 16px rgba(37, 99, 235, .2)
- }
- .page-title h2 {
- margin: 0 0 4px;
- font-size: 20px;
- color: #1e293b
- }
- .muted {
- font-size: 13px;
- color: #64748b
- }
- .heading-refresh {
- border-color: #bfdbfe;
- color: #2563eb;
- background: #fff
- }
- .filter-card, .water-panel {
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- box-shadow: 0 6px 14px rgba(15, 23, 42, .04)
- }
- .filter-card {
- margin-bottom: 16px;
- background: #fff
- }
- .filter-card :deep(.el-card__header) {
- padding: 14px 18px;
- border-bottom: 1px solid #e2e8f0;
- background: #fbfdff
- }
- .filter-card :deep(.el-form-item) {
- margin-top: 8px;
- margin-right: 16px;
- margin-bottom: 8px
- }
- .filter-card :deep(.el-form-item__label) {
- color: #475569
- }
- .metric-grid {
- display: grid;
- grid-template-columns:repeat(4, minmax(0, 1fr));
- gap: 14px;
- margin-bottom: 16px
- }
- .metric {
- display: flex;
- align-items: center;
- gap: 14px;
- min-width: 0;
- padding: 16px 18px;
- background: #fff;
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- box-shadow: 0 6px 14px rgba(15, 23, 42, .04)
- }
- .metric-icon {
- position: static;
- flex: 0 0 42px;
- display: grid;
- place-items: center;
- width: 42px;
- height: 42px;
- color: #2563eb;
- background: linear-gradient(135deg, #dbeafe 0%, #eff6ff 100%);
- border-radius: 8px
- }
- .metric-icon .el-icon {
- font-size: 22px
- }
- .metric-body {
- min-width: 0;
- text-align: left
- }
- .metric-body span {
- display: block;
- overflow: hidden;
- margin-bottom: 3px;
- color: #64748b;
- font-size: 13px;
- text-overflow: ellipsis;
- white-space: nowrap
- }
- .metric-value {
- display: flex;
- align-items: baseline;
- gap: 5px;
- min-width: 0
- }
- .metric-value strong {
- font-size: 24px;
- line-height: 1.2;
- color: #1e293b
- }
- .metric-value small {
- color: #94a3b8
- }
- .water-panel {
- overflow: hidden
- }
- .water-panel :deep(.el-card__header) {
- padding: 14px 18px;
- border-bottom: 1px solid #e2e8f0;
- background: #fbfdff
- }
- .card-title {
- display: flex;
- align-items: center;
- gap: 8px;
- color: #1e293b;
- font-weight: 600;
- margin-bottom: 16px
- }
- .card-title .el-icon {
- color: #2563eb;
- font-size: 18px
- }
- .table-toolbar {
- display: flex;
- justify-content: space-between;
- align-items: center
- }
- .panel-actions {
- display: flex;
- align-items: center;
- gap: 8px
- }
- .load-error {
- margin-bottom: 14px
- }
- .device-page :deep(.el-table) {
- --el-table-border-color: #e2e8f0;
- --el-table-header-bg-color: #f8fafc;
- --el-table-header-text-color: #475569;
- --el-table-row-hover-bg-color: #eff6ff
- }
- .device-page :deep(.el-table__row) {
- cursor: pointer
- }
- .device-image {
- width: 66px;
- height: 44px;
- border-radius: 6px;
- overflow: hidden
- }
- .image-placeholder {
- display: inline-grid;
- place-items: center;
- width: 66px;
- height: 44px;
- color: #94a3b8;
- font-size: 12px;
- background: #f1f5f9;
- border-radius: 6px
- }
- .detail-summary {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 16px
- }
- .detail-name {
- font-size: 18px;
- font-weight: 700;
- color: #17324d
- }
- .device-detail-drawer :deep(.el-drawer__body), .device-edit-drawer :deep(.el-drawer__body) {
- overflow: auto;
- padding: 20px 24px
- }
- .device-detail-drawer :deep(.el-descriptions__label) {
- white-space: nowrap
- }
- .device-detail-drawer :deep(.el-descriptions__content) {
- overflow-wrap: anywhere;
- word-break: break-word
- }
- .detail-media {
- margin-bottom: 14px;
- padding: 12px;
- background: #f7fbff;
- border: 1px solid rgba(37, 99, 235, .1);
- border-radius: 10px
- }
- .detail-media-label {
- margin-bottom: 8px;
- font-weight: 600;
- color: #17324d
- }
- .detail-image {
- width: 100%;
- height: 180px;
- border-radius: 8px;
- overflow: hidden
- }
- .detail-image-placeholder {
- display: grid;
- place-items: center;
- height: 120px;
- background: #f1f5f9;
- border-radius: 8px;
- color: #909399
- }
- .full-input {
- width: 100%
- }
- .import-update {
- margin-top: 10px
- }
- .import-tip {
- line-height: 1.6
- }
- .el-pagination {
- justify-content: flex-end;
- margin-top: 16px
- }
- .dispatch-layout {
- display: flex;
- gap: 20px;
- min-height: 420px
- }
- .dispatch-form {
- width: 300px;
- min-width: 280px;
- padding-left: 20px;
- border-left: 1px solid #e4e7ed
- }
- .assignee-field {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
- min-height: 32px
- }
- .assignee-summary {
- color: #17324d
- }
- .assignee-placeholder {
- color: #909399
- }
- .assignee-picker {
- display: flex;
- gap: 16px;
- flex: 1;
- min-width: 0;
- min-height: 390px
- }
- .assignee-depts {
- width: 190px;
- min-width: 190px;
- padding-right: 16px;
- border-right: 1px solid #e4e7ed
- }
- .assignee-tree {
- margin-top: 10px;
- max-height: 340px;
- overflow: auto
- }
- .assignee-users {
- flex: 1;
- min-width: 0
- }
- .assignee-search {
- display: flex;
- gap: 8px;
- margin-bottom: 12px
- }
- .assignee-search .el-input {
- flex: 1
- }
- .assignee-users .el-pagination {
- margin-top: 12px
- }
- .assignee-picked {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 8px;
- margin-top: 14px;
- padding: 10px 12px;
- background: #f5f7fa;
- border: 1px solid #ebeef5;
- color: #606266
- }
- .assignee-picked strong {
- color: #2563eb
- }
- .assignee-users .el-alert {
- margin-bottom: 12px
- }
- @media (max-width: 900px) {
- .metric-grid {
- grid-template-columns:repeat(2, minmax(0, 1fr))
- }
- .filter-card :deep(.el-form-item) {
- margin-bottom: 12px
- }
- }
- @media (max-width: 768px) {
- .device-detail-drawer, .device-edit-drawer {
- width: 100% !important
- }
- .dispatch-dialog {
- width: calc(100% - 24px) !important;
- margin-left: auto;
- margin-right: auto
- }
- .dispatch-layout {
- flex-direction: column
- }
- .assignee-picker {
- flex-direction: column
- }
- .assignee-depts {
- width: auto;
- min-width: 0;
- padding-right: 0;
- padding-bottom: 12px;
- border-right: 0;
- border-bottom: 1px solid #e4e7ed
- }
- .assignee-tree {
- max-height: 180px
- }
- .dispatch-form {
- width: auto;
- min-width: 0;
- padding-left: 0;
- padding-top: 16px;
- border-left: 0;
- border-top: 1px solid #e4e7ed
- }
- }
- </style>
- <style lang="scss">
- @use './waterSupplyPageTheme.scss';
- @media (max-width: 768px) {
- .device-detail-drawer {
- width: 100% !important
- }
- .dispatch-dialog {
- width: calc(100% - 24px) !important;
- margin-left: auto;
- margin-right: auto
- }
- .dispatch-layout {
- flex-direction: column
- }
- .assignee-picker {
- flex-direction: column
- }
- .assignee-depts {
- width: auto;
- min-width: 0;
- padding-right: 0;
- padding-bottom: 12px;
- border-right: 0;
- border-bottom: 1px solid #e4e7ed
- }
- .assignee-tree {
- max-height: 180px
- }
- .dispatch-form {
- width: auto;
- min-width: 0;
- padding-left: 0;
- padding-top: 16px;
- border-left: 0;
- border-top: 1px solid #e4e7ed
- }
- }
- </style>
|