企微迁移智慧监护(手环)

This commit is contained in:
aixianling
2022-05-17 09:12:29 +08:00
parent 9a3aab86d0
commit f5a1cd7424
10 changed files with 5 additions and 4 deletions

View File

@@ -0,0 +1,66 @@
<template>
<section class="AppGuardianship">
<component ref="currentTab" :is="currentTab.comp"/>
<ai-tabbar :active.sync="active" :list="bottomBar"/>
</section>
</template>
<script>
import AiLoading from "../../components/AiLoading";
import GsLocation from "./gsLocation";
import AiTabbar from "../../components/AiTabbar";
import WardList from "./wardList";
import EarlyWarning from "./earlyWarning";
export default {
name: "AppGuardianship",
appName: "智慧监护",
components: {AiTabbar, AiLoading},
provide() {
return {
top: this
}
},
computed: {
bottomBar() {
return [
{text: "定位", iconPath: "bardwn", selectedIconPath: "bardwh", comp: GsLocation},
{text: "人员", iconPath: "barryn", selectedIconPath: "barryh", comp: WardList},
{text: "预警", iconPath: "baryjn", selectedIconPath: "baryjh", comp: EarlyWarning},
].map(e => ({
...e,
iconPath: this.cdn(e.iconPath),
selectedIconPath: this.cdn(e.selectedIconPath)
}))
},
currentTab() {
return this.bottomBar?.[this.active] || {}
}
},
methods: {
cdn(icon) {
return `${this.$cdn}AppGuardianship/${icon}.png`
},
},
data() {
return {
active: 0
}
},
created() {
this.$dict.load("intelligentGuardianshipItem", 'intelligentGuardianshipItem2', 'intelligentGuardianshipAbnormalStatus')
},
onReachBottom() {
if (typeof this.$refs?.currentTab?.reachBottom == 'function') this.$refs?.currentTab.reachBottom()
}
}
</script>
<style lang="scss" scoped>
.AppGuardianship {
position: absolute;
width: 100%;
top: 0;
bottom: 0;
}
</style>

View File

@@ -0,0 +1,146 @@
<template>
<section class="areaSelector">
<ai-search-popup mode="bottom" ref="areaSelector">
<template #btn>
<div class="areaSelector">
<span v-for="area in fullArea" :key="area.id" v-text="area.name"
:class="{current:area.id==areaId}" @tap="index=area.id,getChildAreas(area.id)"/>
</div>
</template>
<div class="areaSelector">
<span v-for="area in fullArea" :key="area.id" v-text="area.name"
:class="{current:area.id==index}"
@click="index=area.id,getChildAreas(area.id)"/>
</div>
<div class="pendingItem" flex v-for="op in list" :key="op.id" @tap="handleSelect(op)">
<div class="fill" :class="{self:index==op.id}" v-html="op.name"/>
<u-icon name="arrow-right" color="#ddd"/>
</div>
</ai-search-popup>
</section>
</template>
<script>
import AiSearchPopup from "../../../components/AiSearchPopup";
import AiCell from "../../../components/AiCell";
import {mapState} from "vuex";
export default {
name: "areaSelector",
components: {AiCell, AiSearchPopup},
props: {
areaId: {default: ""}
},
computed: {
...mapState(['user']),
dataRange() {
let rules = [10, 8, 6, 3, 0], level = 2
rules.some((e, i) => {
let reg = new RegExp(`0{${e}}`, 'g')
if (reg.test(this.user.areaId)) {
return level = i
}
})
return level
}
},
data() {
return {
fullArea: [],
index: "",
list: []
}
},
watch: {
areaId(v) {
v && this.getFullArea()
}
},
methods: {
getFullArea() {
let {areaId} = this
return areaId && this.$http.post("/admin/area/getAllParentAreaId", null, {
params: {areaId}
}).then(res => {
if (res?.data) {
this.fullArea = res.data.reverse().slice(this.dataRange)
}
})
},
getChildAreas(id) {
id && this.$http.post("/admin/area/queryAreaByParentId", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.list = res.data
let self = this.fullArea.find(e => e.id == this.index)
this.list.unshift(self)
}
})
},
handleSelect(op) {
this.$emit('select', op)
this.$refs.areaSelector?.handleSelect()
}
},
created() {
this.index = this.areaId
this.getFullArea()
}
}
</script>
<style lang="scss" scoped>
.areaSelector {
::v-deep .AiSearchPopup {
.areaSelector {
display: flex;
align-items: center;
span {
cursor: pointer;
&:first-of-type:before {
content: "";
padding: 0;
}
&:before {
color: #333;
content: "/";
padding: 0 16px;
}
}
.current {
color: #3F8DF5;
}
}
.u-drawer-content {
position: fixed;
.areaSelector {
padding: 0 16px;
box-sizing: border-box;
border-bottom: 16px solid #f5f5f5;
span {
line-height: 100px;
}
}
}
.pendingItem {
margin-left: 32px;
padding-right: 32px;
height: 104px;
border-bottom: 1px solid #ddd;
.self {
font-weight: bold;
}
}
}
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<section class="makeCalls">
<div v-if="$slots.default" @tap="calls=true">
<slot/>
</div>
<div v-else flex class="column" @tap="calls=true">
<img :src="`${$cdn}guardianship/dh.png`"/>
<span v-html="label"/>
</div>
<u-popup v-model="calls" mode="bottom">
<div flex class="column option" v-for="item in list" @click="handleCall(item.guardianPhone)">
{{ [item.guardianName, item.guardianPhone].join(' ') }}
</div>
<div class="option" @tap="calls=false">取消</div>
</u-popup>
</section>
</template>
<script>
export default {
name: "makeCalls",
props: {
label: {default: "联系ta"},
list: {default: () => []}
},
data() {
return {
calls: false
}
},
methods: {
handleCall(phone) {
location.href = "tel:" + phone
},
}
}
</script>
<style lang="scss" scoped>
.makeCalls {
img {
width: 40px;
height: 40px;
}
::v-deep span {
margin-left: 0;
color: #999;
font-size: 20px;
p {
color: #3D94FB;
}
}
::v-deep .u-drawer {
text-align: center;
.uni-scroll-view-content {
max-height: 672px;
}
.option {
font-size: 32px;
cursor: pointer;
line-height: 112px;
border-bottom: 1px solid #D8DDE6;
&:first-of-type {
border-radius: 16px 16px 0 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<section class="openMap">
<div flex class="column" shrink @tap="handleOpenMap">
<img :src="`${$cdn}guardianship/seat.png`"/>
<span v-text="'地图/导航'"/>
</div>
</section>
</template>
<script>
export default {
name: "openMap",
props: {
data: {default: () => ({})}
},
methods: {
handleOpenMap() {
let {lng, lat, gpsDesc} = this.data
location.href = `https://uri.amap.com/marker?callnative=1&position=${[lng, lat].toString()}&name=${gpsDesc}`
}
}
}
</script>
<style lang="scss" scoped>
.openMap {
flex-shrink: 0;
img {
width: 40px;
height: 40px;
}
span {
margin-left: 0;
color: #999;
font-size: 20px;
}
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<section class="earlyWarning">
<ai-top-fixed>
<u-search v-model="search.name" placeholder="请输入姓名" :show-action="false"
search-icon-color="#ccc" placeholder-color="#999"
@change="page.current=1,getList()"/>
<div flex>
<ai-date class="fill" placeholder="日期选择" mode="range" @change="handleDateSearch"/>
<ai-select class="fill" dict="intelligentGuardianshipItem2" @data="handleTypeSearch">
<div>{{ $dict.getLabel('intelligentGuardianshipItem2', search.item) || '全部预警' }}</div>
<i class="iconfont iconfont-iconArrow_Down"/>
</ai-select>
</div>
</ai-top-fixed>
<div class="card" v-for="row in list" :key="row.id" @tap="handleShow(row)">
<div class="header" flex>
<img :src="top.cdn(typeIcons[row.item])"/>
<b v-text="row.desc"/>
</div>
<div class="wrapper">
<div class="start" flex>
<span v-text="`上报时间:`"/>
<div v-text="row.createTime"/>
</div>
<div class="start" flex>
<span v-text="`上报地点:`"/>
<div v-text="row.gpsDesc"/>
</div>
</div>
</div>
</section>
</template>
<script>
import AiTopFixed from "../../components/AiTopFixed";
import {mapState} from "vuex";
import AiDate from "../../components/AiDate";
import AiSelect from "../../components/AiSelect";
export default {
name: "earlyWarning",
components: {AiSelect, AiDate, AiTopFixed},
inject: ['top'],
computed: {
...mapState(['user']),
typeIcons() {
return {
0: "icon4",
1: "icon2",
2: "icon5",
3: "icon6",
4: "icon3",
5: "icon1",
}
}
},
data() {
return {
search: {name: "", createTimeRange: ",", item: ""},
areaId: "",
page: {current: 1, size: 10, total: 0},
list: []
}
},
methods: {
getList() {
let {areaId} = this
this.$http.post("/app/appintelligentguardianshipalarm/list", null, {
params: {areaId, ...this.search, ...this.page}
}).then(res => {
if (res?.data) {
let data = res.data.records.map(e => {
return {...e, desc: [e.name, this.$dict.getLabel('intelligentGuardianshipItem2', e.item)].join('的')}
})
if (this.page.current > 1) {
this.list = [...this.list, ...data]
} else this.list = data
this.page.total = res.data.total
}
})
},
reachBottom() {
if (this.page.total > this.list.length) {
this.page.current++
this.getList()
}
},
handleDateSearch(v) {
let {startDate: start, endDate: end} = v
start = this.$dateFormat(start)
end = this.$dateFormat(end)
this.search.createTimeRange = [start, end || start].toString()
this.page.current = 1
this.getList()
},
handleTypeSearch(v) {
this.search.item = v?.[0]?.value
this.page.current = 1
this.getList()
},
handleShow(row) {
uni.navigateTo({url: `./warningDetail?id=${row.id}`})
}
},
created() {
this.areaId = JSON.parse(JSON.stringify(this.user.areaId))
this.getList()
}
}
</script>
<style lang="scss" scoped>
.earlyWarning {
padding-bottom: 130px;
background: #f5f5f5;
::v-deep .AiDate > div {
justify-content: center;
}
::v-deep .u-drawer-content {
padding-bottom: 100px;
}
::v-deep .display {
justify-content: center;
}
::v-deep .iconfont-iconArrow_Down {
margin-left: 4px;
font-size: 32px;
color: inherit;
}
::v-deep .card {
margin: 32px 32px 0;
background: #FFFFFF;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.02);
border-radius: 8px;
.header {
padding: 0 32px;
height: 104px;
border-bottom: 2px solid #EFEFF4;
font-size: 36px;
img {
width: 64px;
height: 64px;
margin-right: 16px;
}
}
.wrapper {
color: #343D65;
font-size: 30px;
margin-bottom: 8px;
padding: 18px 32px;
span {
white-space: nowrap;
flex-shrink: 0;
color: #999;
margin-right: 20px;
}
}
}
}
</style>

View File

@@ -0,0 +1,461 @@
<template>
<section class="gsLocation">
<ai-map class="fill" :map.sync="amap" :lib.sync="mapLib"/>
<div class="searchZone">
<u-search v-model="search" placeholder="请输入姓名" shape="square" bg-color="#fff"
:show-action="false" @search="getList()"/>
<div class="searchResult" v-if="searchResult">
<div class="item" v-for="row in list" :key="row.id" @tap="handleSelect(row)">
<img :src="cdn(row.onlineStatus==1?'zxtx':'lxtx')"/>
<div flex class="column fill">
<b v-html="searchName(row.name)"/>
<div v-text="row.gpsDesc"/>
</div>
</div>
</div>
<u-popup v-model="popup" mode="bottom" :mask="false">
<div class="headerIcon" flex @touchstart="handleTouchStart" @touchmove="handleTouchmoveClose"/>
<div class="selectedInfo">
<div class="header" flex>
<img :src="`${$cdn}guardianship/tx.png`" @tap="handleShowDetail(selected)"/>
<b v-text="selected.name" @tap="handleShowDetail(selected)"/>
<div v-if="selected.abnormalStatus==1" class="abnormal" @tap="handleShowDetail(selected)">异常</div>
<u-icon name="arrow-right" color="#ddd" class="fill" @tap="handleShowDetail(selected)"/>
<make-calls :list="phoneList"/>
</div>
<div flex class="spb wrap">
<div class="detail" v-for="(op,i) in quotas" :key="i" flex>
<img :src="op.icon"/>
<div class="fill" v-text="op.label"/>
<div :class="{abnormal:op.abnormal}" v-text="op.value"/>
</div>
</div>
</div>
<div class="navigation">
<div class="content" flex>
<div flex class="spb wrap">
<div class="fill" v-text="selected.gpsDesc"/>
<span>最后更新{{ selected.lastUpdateTime }}</span>
<div class="battery" flex>
<img :src="batteryIcon"/>
<div v-text="`剩余${selected.electricQuantity}%`"/>
</div>
</div>
<open-map :data="selected"/>
</div>
</div>
</u-popup>
</div>
</section>
</template>
<script>
import AiSearchPopup from "../../components/AiSearchPopup";
import {mapState} from "vuex";
import UPopup from "../../uview/components/u-popup/u-popup";
import MakeCalls from "./component/makeCalls";
import OpenMap from "./component/openMap";
import AiMap from "../../components/AiMap";
export default {
name: "gsLocation",
components: {AiMap, OpenMap, MakeCalls, UPopup, AiSearchPopup},
computed: {
...mapState(['user']),
markers() {
return this.list.filter(e => e.lng).map(e => {
let abnormal = 'offline'
if (e.onlineStatus == 1) {
switch (e.abnormalStatus) {
case '1':
abnormal = 'warning';
break;
case '2':
abnormal = 'abnormal';
break;
default:
abnormal = ''
}
}
return new this.mapLib.Marker({
position: new this.mapLib.LngLat(e.lng, e.lat),
anchor: 'bottom-center',
content: `<div class="marker ${abnormal}">${e.name}</div>`,
extData: e,
topWhenClick: true
}).on('click', () => {
this.handleSelect(e)
})
})
},
quotas() {
let quota = [
{key: "0", icon: "1"},
{key: "1", icon: "2"},
{key: "2", icon: "3"},
{key: "3", icon: "4"},
]
return quota.map(e => {
let item = this.detail.find(d => d.item == e.key)
let label = this.$dict.getLabel('intelligentGuardianshipItem', e.key)
return {
label, icon: this.cdn(e.icon),
value: item?.itemValue || "-",
abnormal: item?.abnormalStatus == 1
}
})
},
batteryIcon() {
return this.cdn(this.selected.electricQuantity == 100 ? 'dcm' : 'dcq')
},
phoneList() {
let {name: guardianName, phone: guardianPhone} = this.selected
return [{guardianName, guardianPhone}, ...(this.selected.guardians || [])]
},
},
data() {
return {
mapLib: null,
amap: null,
search: "",
selected: {},
list: [],
popup: false,//被监护人信息弹窗
detail: [],
moveDistance: 0,
searchResult: false //搜索下拉弹窗
}
},
watch: {
amap(v) {
v && this.getList().then(() => {
this.amap?.add(this.markers)
this.getMapArea()
})
}
},
methods: {
cdn(icon) {
return `${this.$cdn}guardianship/${icon}.png`
},
getMapArea() {
if (this.mapLib) {
new this.mapLib.DistrictSearch({
subdistrict: 0, //获取边界不需要返回下级行政区
extensions: 'all', //返回行政区边界坐标组等具体信息
level: 'district' //查询行政级别为 市
}).search(this.user.areaId.substring(0, 6), (status, result) => {
let bounds = result?.districtList?.[0]?.boundaries;
let polygons = []
bounds?.forEach(path => polygons.push(new this.mapLib.Polygon({
strokeWeight: 1,
path,
strokeStyle: 'dashed',
fillOpacity: 0.1,
fillColor: '#80d8ff',
strokeColor: '#0091ea'
})))
this.amap.add(polygons)
this.amap.setFitView();//视口自适应
})
}
},
getList() {
this.searchResult = !!this.search
return this.$http.post("/app/appintelligentguardianshipdevice/list", null, {
params: {name: this.search, size: 999, areaId: this.user.areaId}
}).then(res => {
if (res?.data) {
this.list = res.data.records
}
})
},
getDetail(deviceId) {
this.$http.post("/app/appintelligentguardianshipdevice/queryMonitorList", null, {
params: {type: 1, deviceId}
}).then(res => {
if (res?.data) {
this.detail = res.data.records
}
})
this.$http.post("/app/appintelligentguardianshipdevice/queryDetailById", null, {
params: {id: deviceId}
}).then(res => {
if (res?.data) {
this.selected = {...this.selected, ...res.data}
}
})
},
searchName(name) {
return name?.replace(this.search, `<span>${this.search}</span>`)
},
handleSelect(e) {
this.amap.setCenter(new this.mapLib.LngLat(e.lng, e.lat))
this.selected = e
this.popup = true
this.searchResult = false
this.getDetail(e.id)
},
handleShowDetail(user) {
uni.navigateTo({url: `./userDetail?id=${user.id}`})
},
handleTouchmoveClose(e) {
if (e.touches?.[0]?.clientY > this.moveDistance + 10) {
this.popup = false
}
},
handleTouchStart(e) {
this.moveDistance = e.touches?.[0]?.clientY
}
}
}
</script>
<style lang="scss" scoped>
.gsLocation {
height: 100%;
a {
color: inherit;
text-decoration: none;
display: flex;
align-items: center;
justify-content: center;
height: 112px;
border-top: 1px solid #d8dde6;
&:first-of-type {
border-top: none;
}
}
.headerIcon {
width: 100%;
height: 60px;
justify-content: center;
&:before {
content: " ";
display: block;
width: 64px;
height: 10px;
background: #CCCCCC;
border-radius: 5px;
}
}
::v-deep .makeCalls {
.option:last-of-type {
margin-bottom: 120px;
}
}
::v-deep .selectedInfo {
width: 100%;
border-radius: 20px 20px 0 0;
padding: 0 32px;
background: #fff;
overflow: hidden;
box-sizing: border-box;
.header {
margin-bottom: 40px;
font-size: 40px;
& > img {
width: 82px;
height: 82px;
margin-right: 16px;
}
.abnormal {
color: #FF4466;
font-size: 24px;
padding: 12px;
background: rgba(#EC4461, .1);
border-radius: 8px;
margin: 0 16px;
}
span {
margin-left: 0;
color: #999;
font-size: 20px;
}
}
.detail {
width: 318px;
height: 84px;
background: #F4F5F6;
border-radius: 8px;
padding: 0 24px;
box-sizing: border-box;
margin-bottom: 36px;
img {
margin-right: 16px;
width: 56px;
height: 56px;
}
.abnormal {
color: #FF4466;
}
}
}
.navigation {
padding-bottom: 100px;
.content {
padding: 32px 40px;
font-size: 26px;
& > .spb {
margin-right: 40px;
}
.fill {
min-width: 100%;
flex-shrink: 0;
margin-bottom: 10px;
}
span {
margin-left: 0;
color: #999;
font-size: 22px;
}
.battery > img {
margin-right: 14px;
}
}
&:before {
content: " ";
display: block;
width: 100%;
height: 8px;
background: #F4F5F6;
}
}
::v-deep .searchZone {
position: absolute;
top: 0;
z-index: 2;
width: 100%;
background: transparent;
padding: 24px 16px;
box-sizing: border-box;
.u-search {
box-shadow: 0 4px 8px 0 rgba(192, 185, 185, 0.5);
}
.searchResult {
margin-top: 16px;
padding: 0 28px;
background: #fff;
.item {
font-size: 24px;
display: flex;
line-height: 36px;
padding: 24px 0;
border-bottom: 3px solid #DEDFE1;
&:last-of-type {
border-bottom: none;
}
img {
width: 36px;
height: 36px;
margin-right: 16px;
}
& > .fill {
align-items: unset;
b > span {
color: #1365DD;
}
& > div {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
::v-deep .marker {
color: #fff;
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
padding: 0 32px;
height: 56px;
white-space: nowrap;
background: #5088FF;
border-color: #5088FF;
border-radius: 52px;
position: relative;
&:after {
position: absolute;
display: block;
content: " ";
bottom: -12px;
left: 50%;
transform: translateX(-50%);
border: 12px solid transparent;
border-bottom: none;
height: 0;
width: 0;
border-top-color: inherit;
}
&.offline {
background: #C4CAD4;
border-color: #C4CAD4;
}
&.warning {
background: #FFAA44;
border-color: #FFAA44;
}
&.abnormal {
background: #F46159;
border-color: #F46159;
&:before {
position: absolute;
z-index: -1;
bottom: -40px;
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #F46159;
transform: translate(-50%, -50%);
animation: mapWarn 1s ease-out 0s infinite;
content: " ";
}
}
}
.AiMap {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<section class="historyList">
<div flex>
<b class="header" v-text="itemLabel"/>
</div>
<div v-for="row in list" :key="row.id" flex class="spb row">
<div :class="{abnormal:row.abnormalStatus==1}" v-text="row.itemValue"/>
<span v-text="row.sampleTime"/>
</div>
<ai-back/>
</section>
</template>
<script>
import AiBack from "../../components/AiBack";
export default {
name: "historyList",
components: {AiBack},
computed: {
itemLabel() {
return '历史' + this.$dict.getLabel('intelligentGuardianshipItem', this.$route.query.type) + `(${this.$dict.getLabel('intelligentGuardianshipItemUnit', this.$route.query.type)})`
}
},
data() {
return {
list: []
}
},
methods: {
getHistory() {
let {type: item, id: deviceId} = this.$route.query
this.$http.post("/app/appintelligentguardianshipdevice/queryMonitorList", null, {
params: {deviceId, size: 999, item}
}).then(res => {
if (res?.data) {
this.list = res.data.records
}
})
}
},
created() {
this.$dict.load("intelligentGuardianshipItem", 'intelligentGuardianshipItemUnit')
this.getHistory()
}
}
</script>
<style lang="scss" scoped>
.historyList {
font-size: 30px;
& > div {
padding: 0 32px;
border-bottom: 1px solid #ddd;
height: 96px;
background: #FFFFFF;
}
.header {
font-size: 32px;
}
.row {
color: #5AAD6A;
.abnormal {
color: #CD413A;
}
& > span {
color: #999;
}
}
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<section class="userDetail">
<div class="selectedInfo">
<div class="header" flex>
<img :src="`${$cdn}guardianship/tx.png`"/>
<b v-text="detail.name"/>
<div v-if="detail.abnormalStatus==1" class="abnormal">异常</div>
<div class="fill"/>
<make-calls :list="phoneList" :label="`<p>拨打电话</p>`"/>
</div>
<div class="content">
<ai-cell label="所属地区">{{ detail.areaName }}</ai-cell>
<ai-cell label="联系电话">{{ detail.phone }}</ai-cell>
<ai-cell label="性别">{{ $dict.getLabel('sex', detail.sex) }}</ai-cell>
<ai-cell label="年龄">{{ $calcAge(detail.idNumber) }}</ai-cell>
</div>
</div>
<div class="card">
<div flex class="spb header">
<b v-text="`设备状况`"/>
<span class="onlineStatus" v-html="detail.onlineStatus==1?'设备在线':'<p>设备离线</p>'"/>
</div>
<div flex class="spb wrap quotas">
<div class="quota" v-for="(op,i) in quotas" :key="i" flex @tap="handleShowHistory(op)">
<img :src="op.icon"/>
<div class="fill" v-text="op.label"/>
<div :class="{abnormal:op.abnormal}" v-text="op.value"/>
<u-icon name="arrow-right" color="#ddd"/>
</div>
</div>
<div class="navigation">
<div class="content spb" flex>
<div flex class="spb wrap">
<div class="fill" v-text="detail.gpsDesc"/>
<span>最后更新{{ detail.lastUpdateTime }}</span>
<div class="battery" flex>
<img :src="batteryIcon"/>
<div v-text="`剩余${detail.electricQuantity||0}%`"/>
</div>
</div>
<open-map :data="detail"/>
</div>
</div>
</div>
<div class="card">
<div flex class="spb header">
<b v-text="`监护人信息`"/>
</div>
<div flex class="spb guardian" v-for="row in detail.guardians" :key="row.id">
<span v-text="row.guardianName"/>
<div v-text="row.guardianPhone"/>
</div>
</div>
<ai-back/>
</section>
</template>
<script>
import MakeCalls from "./component/makeCalls";
import OpenMap from "./component/openMap";
import AiCell from "../../components/AiCell";
import AiBack from "../../components/AiBack";
export default {
name: "userDetail",
components: {AiBack, AiCell, OpenMap, MakeCalls},
computed: {
batteryIcon() {
return this.cdn(this.detail.electricQuantity == 100 ? 'dcm' : 'dcq')
},
quotas() {
let quota = [
{key: "0", icon: "1"},
{key: "1", icon: "2"},
{key: "2", icon: "3"},
{key: "3", icon: "4"},
]
return quota.map(e => {
let item = this.detail.quota?.find(d => d.item == e.key)
let label = this.$dict.getLabel('intelligentGuardianshipItem', e.key)
return {
label, icon: this.cdn(e.icon), type: e.key,
value: item?.itemValue || "-",
abnormal: item?.abnormalStatus == 1
}
})
},
phoneList() {
let {name: guardianName, phone: guardianPhone} = this.detail
return [{guardianName, guardianPhone}, ...this.detail.guardians]
}
},
data() {
return {
detail: {
guardians: []
}
}
},
methods: {
cdn(icon) {
return `${this.$cdn}guardianship/${icon}.png`
},
getDetail(deviceId) {
this.$http.post("/app/appintelligentguardianshipdevice/queryMonitorList", null, {
params: {type: 1, deviceId}
}).then(res => {
if (res?.data) {
this.$set(this.detail, 'quota', res.data.records)
}
})
this.$http.post("/app/appintelligentguardianshipdevice/queryDetailById", null, {
params: {id: deviceId}
}).then(res => {
if (res?.data) {
this.detail = {...this.detail, ...res.data}
}
})
},
handleShowHistory(item) {
uni.navigateTo({url: `./historyList?type=${item.type}&id=${this.$route.query.id}`})
}
},
created() {
this.$dict.load("intelligentGuardianshipItem", 'sex')
this.getDetail(this.$route.query.id)
}
}
</script>
<style lang="scss" scoped>
.userDetail {
padding-bottom: 60px;
& > * {
margin-bottom: 16px;
}
.card {
background: #fff;
font-size: 32px;
box-shadow: 0 1px 1px 0 rgba(221, 221, 221, 1);
.header {
height: 96px;
background: #FFFFFF;
border-bottom: 1px solid #ddd;
}
& > .spb {
padding: 0 32px;
}
}
::v-deep .selectedInfo {
width: 100%;
background: #fff;
overflow: hidden;
box-sizing: border-box;
.header {
height: 136px;
font-size: 40px;
padding: 0 32px;
border-bottom: 1px solid #D8DDE6;
& > img {
width: 82px;
height: 82px;
margin-right: 16px;
}
.abnormal {
color: #FF4466;
font-size: 24px;
padding: 12px;
background: rgba(#EC4461, .1);
border-radius: 8px;
margin-left: 16px;
}
span {
margin-left: 0;
color: #999;
font-size: 20px;
}
}
& > .content {
padding: 32px;
font-size: 30px;
::v-deep .AiCell {
padding: 0;
min-height: 58px;
}
}
}
.quotas {
margin-top: 24px;
.quota {
cursor: pointer;
width: 318px;
height: 84px;
background: #F4F5F6;
border-radius: 8px;
padding: 0 8px 0 24px;
box-sizing: border-box;
margin-bottom: 36px;
font-size: 28px;
img {
margin-right: 16px;
width: 56px;
height: 56px;
}
.abnormal {
color: #FF4466;
}
}
}
.navigation {
.content {
padding: 10px 40px 32px;
font-size: 26px;
& > .spb {
margin-right: 40px;
}
.fill {
min-width: 100%;
flex-shrink: 0;
margin-bottom: 10px;
}
span {
margin-left: 0;
color: #999;
font-size: 22px;
}
.battery > img {
margin-left: 32px;
margin-right: 14px;
}
}
}
::v-deep .onlineStatus {
font-size: 30px;
color: #5AAD6A;
p {
color: #F5A319;
}
}
.guardian {
height: 96px;
border-bottom: 1px solid #ddd;
font-size: 28px;
color: #222;
&:last-of-type {
border-bottom: none;
}
span {
color: #666666;
}
}
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<section class="wardList">
<ai-top-fixed>
<u-search v-model="search" placeholder="请输入姓名" :show-action="false"
search-icon-color="#ccc" placeholder-color="#999"
@change="page.current=1,getUser()"/>
<area-selector :areaId="areaId" @select="handleSelectArea"/>
</ai-top-fixed>
<div class="userList">
<div v-for="row in list" :key="row.id" flex class="row" @tap="handleShowDetail(row)">
<img :src="top.cdn(row.onlineStatus==1?'zxtx':'lxtx')"/>
<b class="fill" v-text="row.name"/>
<div class="status" :style="{color:$dict.getColor('intelligentGuardianshipAbnormalStatus',row.abnormalStatus)}"
v-text="$dict.getLabel('intelligentGuardianshipAbnormalStatus',row.abnormalStatus)"/>
<u-icon name="arrow-right" color="#ddd"/>
</div>
</div>
</section>
</template>
<script>
import AiTopFixed from "../../components/AiTopFixed";
import {mapState} from "vuex";
import AreaSelector from "./component/areaSelector";
export default {
name: "wardList",
components: {AreaSelector, AiTopFixed},
computed: {
...mapState(['user']),
},
inject: ['top'],
data() {
return {
search: "",
areaId: "",
page: {current: 1, size: 20, total: 0},
list: []
}
},
methods: {
getUser() {
let {areaId, search: name} = this
this.$http.post("/app/appintelligentguardianshipdevice/list", null, {
params: {areaId, name, ...this.page}
}).then(res => {
if (res?.data) {
let data = res.data.records.reverse()
if (this.page.current > 1) {
this.list = [...this.list, ...data]
} else this.list = data
this.page.total = res.data.total
}
})
},
handleSelectArea({id}) {
this.areaId = id
this.getUser()
},
reachBottom() {
if (this.page.total > this.list.length) {
this.page.current++
this.getUser()
}
},
handleShowDetail(user) {
uni.navigateTo({url: `./userDetail?id=${user.id}`})
}
},
created() {
this.areaId = JSON.parse(JSON.stringify(this.user.areaId))
this.getUser()
}
}
</script>
<style lang="scss" scoped>
.wardList {
background: #f5f5f5;
padding-bottom: 130px;
font-size: 30px;
::v-deep .userList {
margin-top: 16px;
background: #fff;
.row {
font-size: 36px;
margin-left: 32px;
padding-right: 32px;
height: 104px;
border-bottom: 1px solid #ddd;
img {
width: 72px;
height: 72px;
margin-right: 38px;
}
.status {
margin-right: 12px;
}
}
}
}
</style>

View File

@@ -0,0 +1,185 @@
<template>
<section class="warningDetail">
<div flex class="header">
<b v-text="detail.name"/>
<div>
{{ $dict.getLabel('intelligentGuardianshipItem2', detail.item) }}
{{ detail.itemValue }}
</div>
</div>
<ai-map class="fill" :map.sync="amap" :lib.sync="mapLib"/>
<div class="navigation">
<div class="content spb" flex>
<div flex class="spb wrap">
<div class="fill" v-text="detail.gpsDesc"/>
<span>最后更新{{ detail.createTime }}</span>
</div>
<open-map :data="detail"/>
</div>
</div>
<make-calls :list="phoneList">
<div class="bottomBtn">拨打电话</div>
</make-calls>
<ai-back/>
</section>
</template>
<script>
import OpenMap from "./component/openMap";
import MakeCalls from "./component/makeCalls";
import AiBack from "../../components/AiBack";
import AiMap from "../../components/AiMap";
export default {
name: "warningDetail",
components: {AiMap, AiBack, MakeCalls, OpenMap},
computed: {
phoneList() {
let {name: guardianName, phone: guardianPhone} = this.detail
return [{guardianName, guardianPhone}, ...this.detail.guardians]
}
},
data() {
return {
detail: {guardians: []},
mapLib: null,
amap: null,
}
},
methods: {
getDetail(id) {
return this.$http.post("/app/appintelligentguardianshipalarm/queryDetailById", null, {
params: {id},
withoutToken: true
}).then(res => {
if (res?.data) {
this.detail = res.data
}
})
},
initMap() {
if (this.mapLib) {
let pos = new this.mapLib.LngLat(this.detail.lng, this.detail.lat)
this.amap.add(new this.mapLib.Marker({
position: pos,
anchor: 'bottom-center',
content: `<div class="marker">${this.detail.name}</div>`,
}))
this.amap.setFitView()
}
}
},
watch: {
mapLib(v) {
this.detail.id && v && this.initMap()
}
},
created() {
this.$dict.load("intelligentGuardianshipItem", 'intelligentGuardianshipItem2', 'sex')
},
mounted() {
this.getDetail(this.$route.query.id).then(() => this.initMap())
}
}
</script>
<style lang="scss" scoped>
.warningDetail {
padding: 48px 48px 112px;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
position: absolute;
.header {
font-size: 40px;
font-weight: bold;
color: #333333;
justify-content: center;
margin-bottom: 48px;
& > div {
color: #EC4461;
}
}
.navigation {
.content {
padding: 10px 0 32px;
font-size: 28px;
color: #555;
& > .spb {
margin-right: 40px;
}
.fill {
min-width: 100%;
flex-shrink: 0;
margin-bottom: 10px;
}
span {
margin-left: 0;
color: #999;
font-size: 22px;
}
}
}
.bottomBtn {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
background: #1365DD;
line-height: 112px;
}
.AiMap {
margin-bottom: 72px;
}
::v-deep .marker {
border-radius: 52px;
background: #F46159;
color: #fff;
font-size: 22px;
padding: 4px 16px;
white-space: nowrap;
position: relative;
&:before {
position: absolute;
left: calc(50% - 30px);
bottom: -34px;
z-index: -1;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #F46159;
animation: mapWarn 1s ease-out 0s infinite;
content: " ";
}
&:after {
position: absolute;
display: block;
content: " ";
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border: 8px solid transparent;
border-bottom: none;
border-top-color: #F46159;
height: 0;
width: 0;
}
}
}
</style>