应急抢险工单

main
严飞永 2 weeks ago
parent 20433f8923
commit 13f44a3658

@ -1,51 +1,5 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-43e3941654fa3054c9684bf53d1b1d356a1.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v1.2.0</h1>
<h4 align="center">基于UniApp开发的轻量级移动端框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-App/stargazers"><img src="https://gitee.com/y_project/RuoYi-App/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-App"><img src="https://img.shields.io/badge/RuoYi-v1.2.0-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-App/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介
RuoYi App 移动解决方案采用uniapp框架一份代码多终端适配同时支持APP、小程序、H5实现了与[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)、[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)完美对接的移动解决方案!目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Cloud](https://github.com/yangzongzhuan/RuoYi-Cloud) 版本。
* 应用框架基于[uniapp](https://uniapp.dcloud.net.cn/)支持小程序、H5、Android和IOS。
* 前端组件采用[uni-ui](https://github.com/dcloudio/uni-ui)全端兼容的高性能UI框架。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
## 技术文档
- 官网网站:[http://ruoyi.vip](http://ruoyi.vip)
- 文档地址:[http://doc.ruoyi.vip](http://doc.ruoyi.vip)
- H5页体验[http://h5.ruoyi.vip](http://h5.ruoyi.vip)
- QQ交流群 ①133713780(满)、②146013835(满)、③189091635
- 小程序体验
<img src="https://oscimg.oschina.net/oscnet/up-26c76dc90b92acdbd9ac8cd5252f07c8ad9.jpg" alt="小程序演示"/>
徐汇园林
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-21f6f842fdc94540469b4eb43fdadbaf7f8.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-a6f23cf9a371a30165e135eff6d9ae89a9d.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-ff5f62016bf6624c1ff27eee57499dccd44.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b9a582fdb26ec69d407fabd044d2c8494df.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-96427ee08fca29d77934cfc8d1b1a637cef.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5fdadc582d24cccd7727030d397b63185a3.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-0a36797b6bcc50c36d40c3c782665b89efc.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d77995cc00687cedd00d5ac7d68a07ea276.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-fa8f5ab20becf59b4b38c1b92a9989e7109.png"/></td>
</tr>
</table>

@ -0,0 +1,36 @@
import request from '@/utils/request'
// 分页查询应急工单列表
export function getEmergencyOrderList(params) {
return request({
url: '/bYjgd/page',
method: 'get',
params
})
}
//通过工单id查询单条数据应急工单的完成时间
export function getEmergencyTime(data) {
return request({
url: '/bGdlc/selectByGdId',
method: 'get',
data: data
})
}
//通过主键查询单条数据
// 根据 ID 查询单条数据
export function getEmergencyOrderById(id) {
return request({
url: `/bYjgd/${id}`,
method: 'GET'
});
}
//获取工单流程信息
export function getEmergencyMessage() {
return request({
url: `/bGdlc/page`,
method: 'get'
})
}

@ -0,0 +1,61 @@
<template>
<span class="dict-tag">
{{ labels.length > 0 ? labels.join(separator) : '' }}
</span>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { getDicts } from '@/api/system/dict/data';
const props = defineProps({
type: {
type: String,
required: true
},
value: {
type: [String, Number, Array],
required: true
},
separator: {
type: String,
default: '、' //
}
});
const labelMap = ref({});
const labels = ref([]);
// value
const parseValue = (val) => {
if (Array.isArray(val)) {
return val.map(String);
} else if (typeof val === 'string') {
return val.split(',').map((s) => s.trim());
} else {
return [String(val)];
}
};
//
const loadLabels = async () => {
try {
const res = await getDicts(props.type);
const map = res.data.reduce((acc, item) => {
acc[item.dictValue] = item.dictLabel;
return acc;
}, {});
labelMap.value = map;
const codes = parseValue(props.value);
labels.value = codes.map((code) => map[code] || '').filter(Boolean);
} catch (e) {
labels.value = [];
}
};
onMounted(loadLabels);
watch(() => props.value, loadLabels); // value
</script>
<style scoped></style>

@ -0,0 +1,285 @@
<template>
<view class="form-inner">
<view class="form-head">
<view class="borderleft"><text>&nbsp;</text></view>
工单基本信息
</view>
<view class="form-item">
<text class="form-label">工单地址</text>
<view class="form-value">
<text>{{ orderData.address }}</text>
</view>
</view>
<!-- 工单类型 -->
<view class="form-item">
<text class="form-label">工单类型</text>
<view class="form-value">
<text>
<dictTag class="item-description" type="gdlx" :value="orderData.gdType" />
<text v-if="orderData.gdType === 0 && orderData.xdsqm !== null">-</text>
<dictTag class="item-description" type="xdsqm" :value="orderData.xdsqm" />
</text>
</view>
</view>
<!-- 影响类型 -->
<view class="form-item">
<text class="form-label">影响类型</text>
<view class="form-value">
<text>
<dictTag class="item-description" type="yxlx" :value="orderData.yxlx" />
</text>
</view>
</view>
<!-- 处理班组 -->
<view class="form-item">
<text class="form-label">处理班组</text>
<view class="form-value">
<text>{{ orderData.deptName }}</text>
</view>
</view>
<!-- 工单描述 -->
<view class="form-item">
<text class="form-label">工单描述</text>
<view class="form-value">
<text>{{ orderData.gdms }}</text>
</view>
</view>
<!-- 工单图片 -->
<view class="form-item">
<text class="form-label">工单图片</text>
<view class="form-value">
<uni-file-picker v-model="orderData.gdtp" fileMediatype="image" mode="grid" />
</view>
</view>
<view class="form-item">
<text class="form-label">参与人数</text>
<view class="form-value">
<text>{{ orderData.pqrs }}</text>
</view>
</view>
<view class="form-item">
<text class="form-label">参与车辆</text>
<view class="form-value">
<text v-if="orderData.hyc != 0">{{ orderData.hyc || 0 }}</text>
<text v-if="orderData.dc != 0">{{ orderData.dc || 0 }}</text>
<text v-if="orderData.dgc != 0">{{ orderData.dgc || 0 }}</text>
</view>
</view>
<view class="form-item">
<text class="form-label">处理描述</text>
<view class="form-value">
<text>{{ orderData.clms }}</text>
</view>
</view>
<!-- 工单图片 -->
<view class="form-item">
<text class="form-label">处理后图片</text>
<view class="form-value">
<uni-file-picker v-model="orderData.clhtp" fileMediatype="image" mode="grid" />
</view>
</view>
</view>
<view class="form-inner">
<view class="form-head" style="margin-top: 20rpx">
<view class="borderleft"><text>&nbsp;</text></view>
工单流程信息
</view>
<view class="processinformation">
<view class="process-container">
<view class="process-node" v-for="(node, index) in processNodes" :key="index">
<!-- 左侧蓝色节点 -->
<view class="node-circle"></view>
<!-- 右侧信息 -->
<view class="node-info">
<text class="node-name">{{ node.name }}</text>
<text class="node-time">{{ node.createTime }}</text>
</view>
<!-- 连接线 -->
<view v-if="index < processNodes.length - 1" class="node-line"></view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { getDicts } from '@/api/system/dict/data';
import { getEmergencyOrderById } from '@/api/emergency/api';
import dictTag from '@/components/dictTag.vue'; //
const props = defineProps({
orderId: {
type: String,
required: true
}
});
const orderData = ref({});
onMounted(async () => {
if (props.orderId) {
await fetchOrderDetails();
}
});
watch(
() => props.orderId,
async () => {
await fetchOrderDetails();
}
);
async function fetchOrderDetails() {
try {
const response = await getEmergencyOrderById(props.orderId);
orderData.value = response.data;
} catch (error) {
console.error('获取工单详情失败:', error);
}
}
//
const processNodes = ref([
{ name: '节点1', createTime: '2023-10-01 10:00' },
{ name: '节点2', createTime: '2023-10-02 12:00' },
{ name: '节点3', createTime: '2023-10-03 14:00' }
]);
//
const fetchDicts = async () => {
const response = await getDicts('gdlx', 'xdsqm', 'yxlx');
options.value = response.data.map((item) => ({
value: item.dictValue,
text: item.dictLabel
}));
};
</script>
<style scoped>
.form-inner {
/* background-color: #f2f2f2; */
padding: 20rpx 30rpx 10rpx 5rpx;
margin-bottom: 20rpx;
}
.form-head {
display: flex;
align-items: center;
gap: 15rpx;
padding-left: 20rpx;
padding-bottom: 30rpx;
border-bottom: 1rpx solid #ccc;
}
.borderleft {
width: 5rpx;
height: 90%;
background-color: #02a7f0;
}
.form-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0rpx 20rpx 40rpx;
/* border-bottom: 1rpx solid #eee; */
}
.form-label {
width: 195rpx;
font-size: 28rpx;
color: #767d9c;
}
.form-value {
flex: 1;
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
color: #333;
}
.arrow {
color: #999;
font-size: 30rpx;
}
.checkbox-item {
margin-right: 30rpx;
margin-bottom: 20rpx;
display: inline-flex;
align-items: center;
}
.checkbox {
margin-right: 10rpx;
transform: scale(0.9);
}
.textarea {
width: 100%;
min-height: 200rpx;
padding-left: 5rpx;
border: 1px solid #eee;
border-radius: 8rpx;
}
.processinformation {
width: 100%;
height: 200rpx;
}
button[type='default'] {
background-color: #f8f8f8;
color: #333;
}
.process-container {
display: flex;
flex-direction: column;
padding: 20rpx;
}
.process-node {
display: flex;
align-items: center;
position: relative;
margin-bottom: 20rpx;
}
.node-circle {
width: 16rpx;
height: 16rpx;
background-color: #537cf7;
border-radius: 50%;
flex-shrink: 0;
}
.node-info {
margin-left: 20rpx;
display: flex;
align-items: center;
gap: 20rpx;
}
.node-name {
font-size: 28rpx;
color: #333;
}
.node-time {
font-size: 24rpx;
color: #666;
margin-top: 5rpx;
}
.node-line {
position: absolute;
width: 2rpx;
height: calc(100% + 30rpx);
background-color: #537cf7;
left: 8rpx;
top: 16rpx;
}
</style>

@ -1,58 +1,73 @@
<template>
<view class="form-container">
<!-- 工单类型 -->
<view class="form-item" style="flex-direction: column; justify-content: left; gap: 20rpx; margin-bottom: 20px">
<view class="form-item" @click="openWorkOrderTypePicker">
<text class="form-label">工单类型</text>
<uni-data-checkbox v-model="formData.workOrderType" :localdata="workOrderTypes" @change="handleWorkOrderTypeChange" />
<view class="form-value">
<text>{{ formData.workOrderTypeText || '' }}</text>
<uni-icons type="right" size="20"></uni-icons>
</view>
</view>
<!-- 倒伏相关选项 -->
<view class="form-item" v-if="showFallOptions">
<uni-data-checkbox v-model="formData.fallAffect" :localdata="fallAffectOptions" />
<view class="form-item" v-if="showFallOptions" @click="openFallAffectPicker">
<text class="form-label">倒伏状态</text>
<view class="form-value">
<text>{{ formData.fallAffectText || '' }}</text>
<uni-icons type="right" size="20"></uni-icons>
</view>
</view>
<!-- 影响类型 - 改为方形多选框 -->
<view class="form-item">
<!-- 影响类型 -->
<view class="form-item" style="justify-content: flex-start">
<text class="form-label">影响类型</text>
<checkbox-group @change="handleLevelChange">
<label class="checkbox-item" v-for="item in workOrderLevels" :key="item.value">
<checkbox :value="item.value" :checked="formData.workOrderLevel.includes(item.value)" />
<text>{{ item.text }}</text>
</label>
</checkbox-group>
<view class="form-value">
<uni-data-checkbox v-model="formData.workOrderLevel" multiple :localdata="workOrderLevels" />
</view>
</view>
<!-- 工单描述 -->
<view class="form-item" style="flex-direction: column; align-items: flex-start; gap: 20rpx">
<view class="form-item">
<text class="form-label">工单描述</text>
<textarea
v-model="formData.description"
placeholder="请详细描述问题情况..."
style="width: 100%; min-height: 200rpx; padding: 20rpx; border: 1px solid #eee; border-radius: 8rpx"
/>
<view class="form-value">
<textarea v-model="formData.description" placeholder="请输入工单描述信息" class="textarea" />
</view>
</view>
<!-- 工单图片 -->
<view class="form-item" style="margin-bottom: 200rpx">
<text class="form-label" style="width: 150rpx">工单图片</text>
<view class="form-item">
<text class="form-label">工单图片</text>
<view class="form-value">
<uni-file-picker v-model="imageValue" fileMediatype="image" mode="grid" @select="select" @progress="progress" @success="success" @fail="fail" />
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-btn">
<button type="default" class="back-btn" @click="handleBack">退</button>
<button type="primary" @click="handleSubmit('pending')"></button>
<button type="primary" @click="handleSubmit('processed')"></button>
<button type="primary" @click="handleSubmit('pending')"></button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { getDicts } from '@/api/system/dict/data';
//
const fetchDicts = async () => {
const response = await getDicts('gdlx', 'xdsqm', 'yxlx');
options.value = response.data.map((item) => ({
value: item.dictValue,
text: item.dictLabel
}));
};
//
const formData = ref({
workOrderType: '',
workOrderTypeText: '',
fallAffect: '',
fallAffectText: '',
workOrderLevel: [],
description: '',
images: []
@ -87,17 +102,29 @@ const workOrderLevels = [
{ value: 'others', text: '其他' }
];
//
const handleWorkOrderTypeChange = (e) => {
showFallOptions.value = formData.value.workOrderType === 'fall';
if (!showFallOptions.value) {
formData.value.fallAffect = '';
//
const openWorkOrderTypePicker = () => {
uni.showActionSheet({
itemList: workOrderTypes.map((item) => item.text),
success: (res) => {
const selected = workOrderTypes[res.tapIndex];
formData.value.workOrderType = selected.value;
formData.value.workOrderTypeText = selected.text;
showFallOptions.value = selected.value === 'fall';
}
});
};
//
const handleLevelChange = (e) => {
formData.value.workOrderLevel = e.detail.value;
//
const openFallAffectPicker = () => {
uni.showActionSheet({
itemList: fallAffectOptions.map((item) => item.text),
success: (res) => {
const selected = fallAffectOptions[res.tapIndex];
formData.value.fallAffect = selected.value;
formData.value.fallAffectText = selected.text;
}
});
};
//
@ -159,23 +186,38 @@ const handleSubmit = (type) => {
};
</script>
<style>
<style scoped>
.form-container {
padding: 30rpx;
padding-bottom: 160rpx; /* 给底部按钮留空间 */
overflow: auto;
}
.form-item {
display: flex;
margin-bottom: 40rpx;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
.form-label {
width: 230rpx;
font-size: 30rpx;
width: 155rpx;
font-size: 28rpx;
color: #767d9c;
}
.form-value {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
color: #333;
}
.arrow {
color: #999;
font-size: 30rpx;
}
.checkbox-item {
margin-right: 30rpx;
margin-bottom: 20rpx;
@ -183,11 +225,19 @@ const handleSubmit = (type) => {
align-items: center;
}
checkbox {
.checkbox {
margin-right: 10rpx;
transform: scale(0.9);
}
.textarea {
width: 100%;
min-height: 200rpx;
padding-left: 5rpx;
border: 1px solid #eee;
border-radius: 8rpx;
}
.bottom-btn {
width: 100%;
display: flex;

@ -1,7 +1,7 @@
// 应用全局配置
export default {
baseUrl: 'https://vue.ruoyi.vip/prod-api',
// baseUrl: 'http://221.229.220.83:9028',
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
baseUrl: 'http://192.168.0.112:9116',
// 应用信息
appInfo: {
// 应用名称

@ -2,6 +2,7 @@
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"leaflet": "^1.9.4",
"mars2d": "^3.3.2"
"mars2d": "^3.3.2",
"uview-ui": "^2.0.38"
}
}

@ -86,6 +86,13 @@
"navigationBarTitleText": "应急抢险工单录入",
"navigationStyle": "custom"
}
},
{
"path": "pages/homePage/emergencyDetails",
"style": {
"navigationBarTitleText": "应急抢险工单录入",
"navigationStyle": "custom"
}
}
],
"tabBar": {

@ -4,21 +4,21 @@
<uni-nav-bar left-icon="left" left-text="" background-color="#537CF7" color="#ffffff" @clickLeft="goBack">
<text class="navbar-title">应急抢险工单</text>
</uni-nav-bar>
<!-- 页面内容 -->
<view class="content">
<!-- 顶部标签切换 -->
<view class="top-section">
<view class="tab" :class="{ active: activeTab === 'pending' }" @click="activeTab = 'pending'">待处理工</view>
<view class="tab" :class="{ active: activeTab === 'pending' }" @click="handleTabChange('pending')"></view>
<view class="divider"></view>
<view class="tab" :class="{ active: activeTab === 'completed' }" @click="activeTab = 'completed'">已完成工</view>
<view class="tab" :class="{ active: activeTab === 'completed' }" @click="handleTabChange('completed')"></view>
</view>
<!-- 页面内容 -->
<view class="content">
<!-- 内容区域 -->
<view class="content-main">
<!-- 时间选择器 -->
<view>
<uni-datetime-picker type="daterange" v-model="dateRange" start="2023-01-01" end="2023-12-31" @change="onDateChange" placeholder="请选择时间范围" />
<uni-datetime-picker v-model="range" type="daterange" />
</view>
<!-- 工单类型选择器 -->
<view class="filter-section">
<uni-data-select v-model="value" :localdata="options" placeholder="请选择工单类型" />
@ -27,95 +27,100 @@
<!-- 待处理工单 -->
<view v-if="activeTab === 'pending'" style="overflow: auto; padding-bottom: 200rpx">
<view class="order-list">
<view v-if="pendingOrders.length > 0" class="order-list">
<view
class="order-item"
v-for="(item, index) in pendingOrders"
:key="index"
@click="navigateToDetail(item)"
:class="{
'to-handle': item.status === 'toHandle',
'to-dispatch': item.status === 'toDispatch'
'to-handle': item.status === 0,
'to-dispatch': item.status === 1
}"
>
<view class="status-tag" :class="item.status">
{{ item.status === 'toHandle' ? '待处理' : '待派发' }}
<view class="status-tag" :class="getLevelClass(item.gdLevel)">
{{ item.status === 0 ? '待处理' : '待派发' }}
</view>
<view class="form-group">
<view class="item-form">
<text>工单描述</text>
<text class="item-description">{{ item.description }}</text>
<text>工单地址</text>
<text class="item-description">{{ item.address }}</text>
</view>
<view class="item-form">
<text>工单类型</text>
<text class="item-description">{{ item.type }}</text>
<dictTag class="item-description" type="gdlx" :value="item.gdType" />
<text v-if="item.gdType === 0 && item.xdsqm !== null">-</text>
<dictTag class="item-description" type="xdsqm" :value="item.xdsqm" />
</view>
<view class="item-form">
<text>工单地址</text>
<text class="item-description">{{ item.address }}</text>
<text>影响类型</text>
<dict-tag class="item-description" type="yxlx" value="1,2,3" />
</view>
<view class="item-form">
<text>录入时间</text>
<text class="item-description">{{ item.createTime }}</text>
</view>
<!-- 工单等级显示 -->
<view class="item-form">
<text>工单等级</text>
<text class="level-tag" :class="getLevelClass(item.level)">
{{ item.level }}
</text>
</view>
<view class="item-form" v-if="item.status === 'toHandle'">
<view class="item-form" v-if="item.status === 0">
<text>处理班组</text>
<text class="item-description">{{ item.team || '暂无' }}</text>
<text class="item-description">{{ item.clbz || '暂无' }}</text>
</view>
</view>
<!-- 待处理的操作 -->
<view class="actionstwo" v-if="item.status === 'toHandle'">
<view class="actionstwo" v-if="item.status === 0">
<view class="action-text" @click="handleTransfer(item)"></view>
<view class="divider-vertical"></view>
<view class="action-text" @click="handleTransfer(item)"></view>
<view class="action-text" @click="handleFeedback(item)"></view>
</view>
<!-- 待派发的操作 -->
<view class="actionsone" v-if="item.status === 'toDispatch'">
<view class="actionsone" v-if="item.status === 1">
<view class="action-text-center" @click="dispatchOrder(item)"></view>
</view>
</view>
</view>
<view v-else class="no-data">暂无数据</view>
</view>
<!-- 已完成工单 -->
<view v-if="activeTab === 'completed'">
<view class="order-list">
<view class="order-item to-completed" v-for="(item, index) in completedOrders" :key="index">
<view v-if="completedOrders.length > 0" class="order-list">
<view class="order-item to-completed" v-for="(item, index) in completedOrders" :key="index" @click="navigateToDetail(item)">
<view class="completed status-tag">已完成</view>
<view class="item-form">
<text>工单描述</text>
{{ item.description }}
<text>工单地址</text>
<text class="item-description">{{ item.address }}</text>
</view>
<view class="item-form">
<text>工单类型</text>
{{ item.type }}
<dictTag class="item-description" type="gdlx" :value="item.gdType" />
<text v-if="item.gdType === 0 && item.xdsqm !== 'null'">-</text>
<dictTag class="item-description" type="xdsqm" :value="item.xdsqm" />
</view>
<view class="item-form">
<text>工单地址</text>
{{ item.address }}
<text>影响类型</text>
<dict-tag class="item-description" type="yxlx" value="1,2,3" />
</view>
<view class="item-form">
<text>完成时间</text>
{{ item.completeTime }}
<text>录入时间</text>
<text class="item-description">{{ item.createTime }}</text>
</view>
<view class="item-form">
<text>处理班组</text>
<text class="item-description">{{ item.deptName }}</text>
</view>
<view class="item-form">
<text>处理结果</text>
{{ item.result }}
<text>完成时间</text>
<text class="item-description">{{ item.completeTime || '' }}</text>
</view>
<!-- <view class="action-buttons">
<button class="action-btn view-detail" @click="viewDetail(item)"></button>
</view> -->
</view>
</view>
<view v-else class="no-data">暂无数据</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-btn">
<button @click="navigateToPage"></button>
</view>
@ -124,126 +129,145 @@
</view>
</template>
<style scoped>
.no-data {
text-align: center;
color: #999;
font-size: 28rpx;
margin-top: 50rpx;
}
</style>
<script setup>
import { ref, onMounted } from 'vue';
import { getDicts } from '@/api/system/dict/data';
import { getEmergencyOrderList, getEmergencyTime } from '@/api/emergency/api';
const activeTab = ref('pending');
const dateRange = ref([]);
import dictTag from '@/components/dictTag.vue'; //
//
const value = ref(1);
const activeTab = ref('pending');
const range = ref([]);
const value = ref(null);
const options = ref([]);
// -
const pendingOrders = ref([
{
orderNo: 'GD20230601001',
description: '行道树发生倒伏,影响车辆和行人正常通行',
type: '行道树倒伏',
address: '徐汇区xx街道xx路',
createTime: '2023-06-01 09:30',
level: '高危险',
team: '一分公司一组',
status: 'toHandle'
const navigateToDetail = (item) => {
uni.navigateTo({
url: `/pages/homePage/emergencyDetails?orderId=${item.id}&status=${item.status}`,
success: () => {
console.log('跳转成功');
},
{
orderNo: 'GD20230601002',
description: '行道树出现明显倾斜,可能存在安全隐患',
type: '行道树倾斜',
address: '徐汇区XX路与XX路交叉口',
createTime: '2023-06-01 10:15',
level: '中危险',
team: null,
status: 'toDispatch'
fail: (err) => {
console.error('跳转失败', err);
}
]);
// -
const completedOrders = ref([
{
orderNo: 'GD20230531001',
description: '某小区停电故障处理',
type: '电力维修',
address: 'XX小区配电室',
createTime: '2023-05-31 08:20',
completeTime: '2023-05-31 10:45',
level: '紧急',
team: '电力维修组',
result: '已修复,恢复正常供电'
}
]);
//
const getLevelClass = (level) => {
const levelMap = {
高危险: 'high-risk',
中危险: 'medium-risk',
紧急: 'urgent'
};
return levelMap[level] || '';
});
};
//
const goBack = () => {
uni.navigateBack();
};
//
const navigateToPage = () => {
uni.navigateTo({
url: '/pages/homePage/emergencyEntry'
});
};
//
const queryParams = ref({
address: '',
begainTime: '',
endTime: '',
gdLevel: null,
gdType: null,
parentId: null,
status: null,
yxlx: ''
});
//
const pendingOrders = ref([]);
const completedOrders = ref([]);
//
//
const fetchDicts = async () => {
const response = await getDicts('gdlx');
const response = await getDicts('gdlx', 'xdsqm', 'yxlx');
options.value = response.data.map((item) => ({
value: item.dictValue,
text: item.dictLabel
}));
};
//
onMounted(() => {
fetchDicts();
});
const onDateChange = (e) => {
console.log('时间范围变更:', e);
//
const getLevelClass = (level) => {
const levelMap = {
1: 'high-risk',
2: 'medium-risk',
3: 'urgent'
};
return levelMap[level] || '';
};
//
const onSearch = () => {
console.log('查询条件:', {
activeTab: activeTab.value,
dateRange: dateRange.value
});
};
//
if (range.value && range.value.length >= 2) {
queryParams.value.begainTime = range.value[0];
queryParams.value.endTime = range.value[1];
} else {
queryParams.value.begainTime = '';
queryParams.value.endTime = '';
}
queryParams.value.gdType = value.value;
//
const dispatchOrder = (order) => {
uni.showToast({
title: `派发工单 ${order.orderNo}`,
icon: 'none'
});
//
loadOrders();
};
const handleOrder = (order) => {
uni.showToast({
title: `开始处理 ${order.orderNo}`,
icon: 'none'
});
//
const loadOrders = async () => {
if (activeTab.value === 'pending') {
queryParams.value.status = 0; //
const res = await getEmergencyOrderList(queryParams.value);
if (res.code === 200) {
pendingOrders.value = res.data?.records || [];
}
} else if (activeTab.value === 'completed') {
queryParams.value.status = 2; //
const res = await getEmergencyOrderList(queryParams.value);
if (res.code === 200) {
//
completedOrders.value = await Promise.all(
(res.data?.records || []).map(async (order) => {
try {
const timeRes = await getEmergencyTime({ GdId: order.id });
return {
...order,
completeTime: timeRes.code === 200 ? timeRes.data[0]?.createTime || '暂无' : '暂无'
};
} catch (error) {
return {
...order
};
}
})
);
}
}
};
const viewDetail = (order) => {
uni.showToast({
title: `查看详情 ${order.orderNo}`,
icon: 'none'
});
//
const handleTabChange = (tab) => {
activeTab.value = tab;
loadOrders();
};
//
const navigateToPage = () => {
uni.navigateTo({
url: '/pages/homePage/emergencyEntry'
});
};
//
onMounted(() => {
fetchDicts();
loadOrders();
});
</script>
<!-- 样式 -->
<style scoped>
.container {
width: 100%;
@ -278,6 +302,7 @@ const navigateToPage = () => {
font-size: 32rpx;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
font-family: 'MiSans-Regular';
/* position: absolute; */
}
/* 分割线 */
@ -331,11 +356,12 @@ const navigateToPage = () => {
gap: 35rpx;
width: 100%;
margin-bottom: 20rpx;
padding-bottom: 60rpx;
}
.order-item {
width: 100%;
height: 550rpx;
height: auto;
border-radius: 25rpx;
display: flex;
flex-direction: column;
@ -345,8 +371,8 @@ const navigateToPage = () => {
/* 状态标签样式 */
.status-tag {
width: 27%;
padding: 16rpx;
width: 28%;
padding: 10rpx;
border-top-left-radius: 25rpx;
border-bottom-right-radius: 120rpx;
font-size: 30rpx;
@ -418,7 +444,7 @@ const navigateToPage = () => {
display: flex; /* 使用flex布局保持同行 */
align-items: center; /* 垂直居中 */
margin: 8rpx 0;
font-size: 32rpx;
font-size: 30rpx;
line-height: 1.5;
padding-left: 30rpx;
font-family: 'MiSans-Regular';
@ -434,11 +460,10 @@ const navigateToPage = () => {
/* 内容部分 */
.item-description {
flex: 1; /* 占据剩余空间 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 10rpx; /* 和标签保持间距 */
padding-left: 10rpx;
}
/* 操作文字通用样式 */
@ -456,9 +481,9 @@ const navigateToPage = () => {
display: flex;
justify-content: space-between;
align-items: center;
margin: 30rpx;
margin: 20rpx;
gap: 20rpx;
margin-top: 50rpx;
margin-top: 40rpx;
font-family: 'MiSans-Regular';
}
@ -487,7 +512,12 @@ const navigateToPage = () => {
background-color: #52c41a;
}
.bottom-btn {
margin-top: 60rpx;
/* margin-top: 60rpx; */
position: absolute;
bottom: 0%;
padding-bottom: 10rpx;
width: 94%;
background-color: #fff;
}
.bottom-btn button {
color: #ffffff;

@ -0,0 +1,436 @@
<template>
<view class="container">
<!-- 自定义标题 -->
<uni-nav-bar left-icon="left" left-text="" background-color="#537CF7" color="#ffffff" @clickLeft="goBack">
<text class="navbar-title">{{ status ? '应急抢险工单详情' : '应急抢险工单录入' }}</text>
</uni-nav-bar>
<!-- 地图容器 -->
<view class="map-container" id="map"></view>
<!-- 搜索框 -->
<!-- <view class="top-container">
<image src="/static/images/地图icon.png" mode=""></image>
<view class="search-container">
<uni-easyinput v-model="keyword" placeholder="请在地图上选择点位或手动输入" class="search-input" @confirm="toSearch" @input="handleInput" disabled></uni-easyinput>
<view class="search-results" v-if="searchBox && searchList.length > 0">
<view class="search-item" v-for="(item, index) in searchList" :key="index" @click="centerMap(item)">
<view class="item-name">{{ item.name }}</view>
<view class="item-address">{{ item.address }}</view>
</view>
</view>
</view>
</view> -->
<!-- 表单 -->
<view class="form-container">
<emDetailsform :order-id="orderId" />
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import 'mars2d/mars2d.css';
import * as mars2d from 'mars2d';
import emDetailsform from '/components/emDetailsform';
import { getEmergencyOrderById } from '@/api/emergency/api';
const orderId = ref(null);
const status = ref(null);
const map = ref(null);
const selectedMarker = ref(null);
const pointForm = ref({
name: '',
lat: '',
lng: '',
address: ''
});
const keyword = ref('');
const searchList = ref([]);
const searchBox = ref(false);
const gaodeKey = 'bd665f6310bb41cdaea4494ec86fcbfa';
const searchDebounce = ref(null);
//
function goBack() {
uni.navigateBack();
}
//
function initMap() {
map.value = new mars2d.Map('map', {
zoom: 17,
copyright: false,
// 使API
center: { lng: 121.442442, lat: 31.197245 },
basemaps: [{ name: '高德地图', type: 'gaode', layer: 'vec', show: true }],
graphicLayer: {
id: 'graphicLayer',
isAutoAdd: true
},
chinaCRS: 'GCJ02'
});
map.value.on('load', () => {
selectedMarker.value = new mars2d.graphic.Marker({
latlng: [121.442442, 31.197245],
style: { opacity: 0 }
});
map.value.graphicLayer.addGraphic(selectedMarker.value);
});
}
function addDefaultMarker(latlng) {
selectedMarker.value = new mars2d.graphic.Marker({
latlng: [latlng.lat, latlng.lng],
style: {
image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
width: 20,
height: 30,
draggable: true
}
});
map.value.graphicLayer.addGraphic(selectedMarker.value);
selectedMarker.value.on('dragend', (event) => {
const newLatLng = event.target.getLatLng();
updatePointForm(newLatLng);
reverseGeocode(newLatLng).then(() => {
pointForm.value.name = pointForm.value.address;
keyword.value = pointForm.value.address;
});
});
}
function updatePointForm(latlng) {
pointForm.value.lat = latlng.lat;
pointForm.value.lng = latlng.lng;
}
function submitLocation() {
if (!pointForm.value.address) {
uni.showToast({ title: '请选择一个地址', icon: 'none' });
return;
}
uni.showToast({ title: '提交成功', icon: 'success' });
console.log('提交地址信息:', pointForm.value);
}
function handleInput() {
if (searchDebounce.value) clearTimeout(searchDebounce.value);
searchDebounce.value = setTimeout(() => {
toSearch();
}, 300);
}
function toSearch() {
if (!keyword.value.trim()) {
searchList.value = [];
searchBox.value = false;
return;
}
const url = `https://restapi.amap.com/v3/place/text?key=${gaodeKey}&keywords=${keyword.value}&city=亳州市`;
uni.request({
url,
success: (res) => {
if (res.data.pois && res.data.pois.length > 0) {
searchList.value = res.data.pois.map((poi) => ({
name: poi.name,
address: poi.address,
location: poi.location
}));
searchBox.value = true;
} else {
uni.showToast({ title: '未找到相关地点', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '搜索失败,请稍后重试', icon: 'none' });
}
});
}
function centerMap(item) {
const [lng, lat] = item.location.split(',').map(Number);
//
map.value.flyTo([lat, lng], 17);
//
if (selectedMarker.value) {
map.value.graphicLayer.removeGraphic(selectedMarker.value);
}
//
selectedMarker.value = new mars2d.graphic.Marker({
latlng: [lat, lng],
style: {
image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
width: 32,
height: 44,
draggable: true
}
});
map.value.graphicLayer.addGraphic(selectedMarker.value);
//
selectedMarker.value.on('dragend', (event) => {
const newLatLng = event.target.getLatLng();
updatePointForm(newLatLng);
reverseGeocode(newLatLng).then(() => {
pointForm.value.name = pointForm.value.address;
keyword.value = pointForm.value.address;
});
});
//
pointForm.value = {
name: item.name,
lat: lat,
lng: lng,
address: item.address
};
//
keyword.value = item.name;
//
searchBox.value = false;
//
map.value.off('click');
map.value.on('click', (event) => {
selectedMarker.value.setLatLng(event.latlng);
updatePointForm(event.latlng);
reverseGeocode(event.latlng).then(() => {
pointForm.value.name = pointForm.value.address;
keyword.value = pointForm.value.address;
});
});
}
function reverseGeocode(latlng) {
return new Promise((resolve, reject) => {
const url = `https://restapi.amap.com/v3/geocode/regeo?key=${gaodeKey}&location=${latlng.lng},${latlng.lat}`;
uni.request({
url,
success: (res) => {
if (res.data.status === '1') {
pointForm.value.address = res.data.regeocode.formatted_address;
resolve();
} else {
reject();
}
},
fail: () => {
reject();
}
});
});
}
//
// onMounted
onMounted(async () => {
const route = useRoute();
orderId.value = route.query.orderId;
status.value = route.query.status;
initMap();
if (orderId.value) {
try {
const response = await getEmergencyOrderById(orderId.value);
const orderData = response.data;
const lat = parseFloat(orderData.lat);
const lng = parseFloat(orderData.lon);
//
updateMapCenter(lat, lng, orderData.address || '应急工单位置');
} catch (error) {
console.error('获取详情失败:', error);
uni.showToast({ title: '加载工单位置失败', icon: 'none' });
}
}
});
function updateMapCenter(lat, lng, address) {
if (!map.value) return;
//
map.value.flyTo([lng, lat], 17);
//
if (selectedMarker.value) {
map.value.graphicLayer.removeGraphic(selectedMarker.value);
}
//
selectedMarker.value = new mars2d.graphic.Marker({
latlng: [lng, lat], // [, ]
style: {
image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
width: 30,
height: 40,
draggable: false
}
});
map.value.graphicLayer.addGraphic(selectedMarker.value);
//
pointForm.value = {
name: address,
lat: lat,
lng: lng,
address: address
};
keyword.value = address;
}
</script>
<style scoped>
.container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
}
.navbar-title {
font-family: 'Alimama ShuHeiTi-Bold';
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #fff;
}
.top-container {
width: 100%;
display: flex;
align-items: center;
background-color: #fff;
gap: 20rpx;
padding-left: 30rpx;
padding-right: 20rpx;
}
.top-container image {
width: 45rpx;
height: 45rpx;
}
.search-container {
padding: 20rpx;
flex: 1;
position: relative;
display: flex;
flex-direction: column;
gap: 20rpx;
background-color: #fff;
z-index: 999;
}
.search-input {
width: 100%;
}
.search-results {
position: absolute;
top: 100%;
left: 20rpx;
width: calc(100% - 40rpx);
max-height: 300px;
overflow-y: auto;
background-color: white;
border: 1px solid #ebeef5;
border-radius: 8rpx;
box-shadow: 0 4rpx 24rpx 0 rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.search-item {
padding: 20rpx 30rpx;
cursor: pointer;
border-bottom: 1px solid #ebeef5;
}
.search-item:last-child {
border-bottom: none;
}
.search-item:hover {
background-color: #f5f7fa;
}
.item-name {
font-weight: bold;
margin-bottom: 10rpx;
}
.item-address {
color: #909399;
font-size: 24rpx;
}
.map-container {
width: 100%;
height: 800rpx;
background-color: #e6e6e6;
overflow: hidden;
position: relative;
z-index: 1;
}
.button-container {
padding: 20rpx;
background-color: #fff;
margin-top: 20rpx;
}
.selected-location {
padding: 20rpx;
background-color: #fff;
margin: 20rpx;
border-radius: 8rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.location-item {
margin-bottom: 15rpx;
font-size: 28rpx;
display: flex;
align-items: center;
}
.back-btn {
background-color: #f8f8f8;
color: #333;
position: relative;
}
button[type='primary'] {
margin-left: 20rpx;
}
.label {
font-weight: bold;
width: 120rpx;
color: #666;
}
.form-container {
height: 100%;
padding: 15rpx 20rpx 40rpx 15rpx;
overflow: auto;
background-color: #fff;
}
</style>

@ -2,7 +2,7 @@
<view class="container">
<!-- 自定义标题 -->
<uni-nav-bar left-icon="left" left-text="" background-color="#537CF7" color="#ffffff" @clickLeft="goBack">
<text class="navbar-title">应急抢险工单录入</text>
<text class="navbar-title">{{ status ? '应急抢险工单详情' : '应急抢险工单录入' }}</text>
</uni-nav-bar>
<!-- 地图容器 -->
@ -24,16 +24,22 @@
</view>
<!-- 表单 -->
<view class="form-container">
<emergencyForm />
<emEntryform />
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import 'mars2d/mars2d.css';
import * as mars2d from 'mars2d';
import emergencyForm from '/components/emergencyForm';
import emEntryform from '/components/emEntryform';
import { getEmergencyOrderById } from '@/api/emergency/api';
//
const orderId = ref(null);
const status = ref(null);
const map = ref(null);
const selectedMarker = ref(null);
@ -60,12 +66,13 @@ function initMap() {
map.value = new mars2d.Map('map', {
zoom: 17,
copyright: false,
center: { lng: 121.438097, lat: 31.199193 },
center: { lng: 121.442442, lat: 31.197245 },
basemaps: [{ name: '高德地图', type: 'gaode', layer: 'vec', show: true }],
graphicLayer: {
id: 'graphicLayer',
isAutoAdd: true
}
},
chinaCRS: 'GCJ02'
});
map.value.on('ready', () => {
@ -75,7 +82,7 @@ function initMap() {
});
//
const center = { lat: 31.199193, lng: 121.438097 };
const center = { lat: 31.197414, lng: 121.442442 };
addDefaultMarker(center);
reverseGeocode(center).then(() => {
pointForm.value = {
@ -253,14 +260,27 @@ function reverseGeocode(latlng) {
}
//
onMounted(() => {
onMounted(async () => {
setTimeout(() => {
initMap();
}, 100);
const route = useRoute();
orderId.value = route.query.orderId;
status.value = route.query.status;
if (orderId.value) {
//
try {
const response = await getEmergencyOrderById(orderId.value);
orderDetails.value = response.data;
} catch (error) {
console.error('获取详情失败:', error);
}
}
});
</script>
<style>
<style scoped>
.container {
width: 100%;
height: 100vh;
@ -380,20 +400,10 @@ onMounted(() => {
position: relative;
}
/* .back-btn::after {
content: '';
position: absolute;
right: -10rpx;
top: 50%;
transform: translateY(-50%);
height: 60%;
width: 1rpx;
background-color: #ddd;
} */
button[type='primary'] {
margin-left: 20rpx;
}
.label {
font-weight: bold;
width: 120rpx;
@ -401,7 +411,7 @@ button[type='primary'] {
}
.form-container {
height: 100%;
padding: 5rpx 20rpx;
padding: 5rpx 20rpx 100rpx 15rpx;
overflow: auto;
background-color: #fff;
}

@ -6,27 +6,27 @@
@font-face {
font-family: 'Aboreto-Regular';
src: url("@/static/font/Aboreto-Regular.ttf");
}
}
@font-face {
@font-face {
font-family: 'MiSans-Medium';
src: url("@/static/font/MiSans-Medium.ttf");
}
}
@font-face {
@font-face {
font-family: 'MiSans-Regular';
src: url("@/static/font/MiSans-Regular.ttf");
}
}
@font-face {
@font-face {
font-family: 'Aboreto-Bold';
src: url("@/static/font/MiSans-Bold.ttf");
}
}
@font-face {
@font-face {
font-family: 'Alimama ShuHeiTi-Bold';
src: url("@/static/font/ALIMAMASHUHEITI-BOLD.TTF");
}
src: url("@/static/font/ALIMAMASHUHEITI-BOLD.ttf");
}
.iconfont {
font-family: "iconfont" !important;
@ -112,4 +112,3 @@
.icon-refresh:before {
content: "\e604";
}

Loading…
Cancel
Save