|
@@ -104,53 +104,57 @@
|
|
|
>
|
|
|
<!-- 自定义插槽内容 -->
|
|
|
<template #item="{ data, subKey, type, item, canclick }">
|
|
|
- <div v-if="type === 'image'" class="flex-avatar">
|
|
|
+ <div v-if="type === 'image'" class="flex-avatar" @click="gotoScene(item)">
|
|
|
<div v-if="isMember(item)" class="vip-icon"></div>
|
|
|
<div v-else-if="isExpiredMember(item)" class="vip-icon vip-expired-icon"></div>
|
|
|
- <span>{{ data }}</span>
|
|
|
+ <span class="device-id">{{ data }}</span>
|
|
|
</div>
|
|
|
|
|
|
- <div v-else-if="type === 'qingkuang'">
|
|
|
- <span>{{ item.usedSpaceStr || '0B' }}/{{ item.totalSpaceStr || '0B' }}</span>
|
|
|
+ <div v-else-if="type === 'qingkuang'" class="capacity-info">
|
|
|
+ <div class="capacity-text">
|
|
|
+ {{ item.usedSpaceStr || '0B' }}
|
|
|
+ {{ (isMember(item) || item.cameraType == 10 || item.cameraType == 11 || item.cameraType == 12) ? '' : `/${item.totalSpaceStr || '0B'}` }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="type === 'spaceEndStr'">
|
|
|
- <span :class="{ 'text-danger': isExpired(item) }">
|
|
|
- {{ data || '-' }}
|
|
|
+ {{ item.spaceEndStr || "--" }}
|
|
|
+ <span class="expired-icon-w">
|
|
|
+ <el-popover :visible="item.popoverVisible" popper-class="expired-ctrls-w" placement="bottom" :width="200">
|
|
|
+ <p v-if="!validExpired(item.spaceEndStr).expiredTime">权益今天到期</p>
|
|
|
+ <p v-else-if="validExpired(item.spaceEndStr).trueExpired"> 权益已过期,请及时续费。</p>
|
|
|
+ <p v-else v-html="$t('权益还有 {expired} 天到期', { expired: validExpired(item.spaceEndStr).expiredText }) "></p>
|
|
|
+ <div class="ctrls-w">
|
|
|
+ <div class="btn cancel-btn" @click="item.popoverVisible = false">取消</div>
|
|
|
+ <div class="btn submit-btn" @click="handleRepay(item)">续费</div>
|
|
|
+ </div>
|
|
|
+ <template #reference>
|
|
|
+ <h-icon
|
|
|
+ type="register_agreement"
|
|
|
+ v-if="validExpired(item.spaceEndStr).isExpired"
|
|
|
+ class="expired-icon kuoda-click-after"
|
|
|
+ @click.stop="item.popoverVisible = true"
|
|
|
+ ></h-icon>
|
|
|
+ </template>
|
|
|
+ </el-popover>
|
|
|
</span>
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="canclick && subKey === 'operation'" class="operation-buttons">
|
|
|
- <span
|
|
|
- v-if="tabActive === 0"
|
|
|
- class="table-btn"
|
|
|
- @click="handleCooperation(item)"
|
|
|
- >
|
|
|
+ <li class="table-btn" @click="handleCooperationaa(item)">
|
|
|
协作
|
|
|
- </span>
|
|
|
- <template v-else>
|
|
|
- <span
|
|
|
- class="table-btn"
|
|
|
- @click="handleCooperation(item)"
|
|
|
- style="margin-right: 10px;"
|
|
|
- >
|
|
|
- 协作
|
|
|
- </span>
|
|
|
- <span
|
|
|
- class="table-btn"
|
|
|
- @click="unbind(item)"
|
|
|
- style="margin-right: 10px;"
|
|
|
- >
|
|
|
- 解绑
|
|
|
- </span>
|
|
|
- <span
|
|
|
- v-if="item.userIncrementId"
|
|
|
- class="table-btn"
|
|
|
- @click="handleRenew(item)"
|
|
|
- >
|
|
|
- 续费
|
|
|
- </span>
|
|
|
- </template>
|
|
|
+ </li>
|
|
|
+ <el-popover trigger="hover" popper-class="info-content" placement="bottom" :width="400">
|
|
|
+ <div class="more-info-content">
|
|
|
+ <span class="th">拍摄场景数量</span><span class="th">最后拍摄时间</span>
|
|
|
+ <span class="td">{{item.sceneNum || '-'}}</span><span class="td">{{item.lastTime || '-'}}</span>
|
|
|
+ </div>
|
|
|
+ <template #reference>
|
|
|
+ <li class="table-btn">
|
|
|
+ 拍摄信息
|
|
|
+ </li>
|
|
|
+ </template>
|
|
|
+ </el-popover>
|
|
|
</div>
|
|
|
|
|
|
<span v-else>{{ data || '-' }}</span>
|
|
@@ -166,7 +170,7 @@
|
|
|
</template>
|
|
|
</el-empty>
|
|
|
</div>
|
|
|
-currentPage{{ currentPage }}
|
|
|
+
|
|
|
<!-- 分页 -->
|
|
|
<Paging
|
|
|
v-if="total"
|
|
@@ -192,13 +196,21 @@ currentPage{{ currentPage }}
|
|
|
<el-button type="primary" @click="handleRenewSuccess">确定</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 协作设置弹窗 -->
|
|
|
+ <CooperationModal
|
|
|
+ v-model="showCooperationModal"
|
|
|
+ :item="cooperationItem"
|
|
|
+ @refresh="handleCooperationRefresh"
|
|
|
+ />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed, onMounted, watch } from 'vue'
|
|
|
+import { ref, onMounted, onUnmounted, watch } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import TableList from '@/components/tableList/index.vue'
|
|
|
+import CooperationModal from './components/cooperationaa.vue'
|
|
|
import { getlistNew } from '@/api/camera/index'
|
|
|
|
|
|
defineOptions({
|
|
@@ -222,9 +234,9 @@ const searchTypeList = ref([
|
|
|
// 表格头部配置
|
|
|
const tabHeader = ref([
|
|
|
{ key: 'snCode', name: 'S/N码', type: 'image' },
|
|
|
- { key: 'status', name: '状态' },
|
|
|
{ key: 'qingkuang', name: '云容量', type: 'qingkuang' },
|
|
|
{ key: 'spaceEndStr', name: '到期时间', type: 'spaceEndStr'},
|
|
|
+ { key: 'cooperationUserName', name: '协作者' },
|
|
|
{ key: 'operation', name: '操作', canclick: true }
|
|
|
])
|
|
|
|
|
@@ -242,7 +254,10 @@ const showBinding = ref(false)
|
|
|
const showRenew = ref(false)
|
|
|
const reNewItem = ref({})
|
|
|
const selectedType = ref({ name: 'S/N码', value: 2 })
|
|
|
-const cameraData = ref([])
|
|
|
+const showCtrls = ref(null)
|
|
|
+const timer = ref(null)
|
|
|
+const showCooperationModal = ref(false)
|
|
|
+const cooperationItem = ref({})
|
|
|
|
|
|
// 计算属性
|
|
|
const totalObj = ref({
|
|
@@ -261,6 +276,30 @@ const oldTotalObj = ref({
|
|
|
12: 0
|
|
|
})
|
|
|
|
|
|
+// 计算属性函数
|
|
|
+const isMember = (item) => {
|
|
|
+ const expiredInfo = getExpiredInfo(item)
|
|
|
+ return item.userIncrementId && !expiredInfo.trueExpired
|
|
|
+}
|
|
|
+
|
|
|
+const isExpiredMember = (item) => {
|
|
|
+ const expiredInfo = getExpiredInfo(item)
|
|
|
+ return item.userIncrementId && expiredInfo.trueExpired
|
|
|
+}
|
|
|
+
|
|
|
+const getExpiredInfo = (item) => {
|
|
|
+ if (!item.spaceEndTime) {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ let expired = Math.floor((new Date(item.spaceEndTime) - new Date()) / 86400000) + 1
|
|
|
+ return {
|
|
|
+ expiredTime: `<span class="expired">${expired}</span>`,
|
|
|
+ isExpired: expired <= 30,
|
|
|
+ trueExpired: expired < 0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
const cameraList = ref([])
|
|
|
|
|
|
// 组件引用
|
|
@@ -282,7 +321,11 @@ const getCameraList = async (cameraType = tabActive.value, pageNum = currentPage
|
|
|
|
|
|
if (cameraType === tabActive.value) {
|
|
|
// 只有当前标签页的数据才更新cameraData
|
|
|
- cameraList.value = response.list || []
|
|
|
+ // 为每个设备项添加独立的 visible 属性
|
|
|
+ cameraList.value = (response.list || []).map(item => ({
|
|
|
+ ...item,
|
|
|
+ popoverVisible: false,
|
|
|
+ }))
|
|
|
total.value = response.total || 0
|
|
|
}
|
|
|
|
|
@@ -389,6 +432,11 @@ const handleCooperation = (item) => {
|
|
|
ElMessage.success(`设备 ${item.snCode} 协作功能`)
|
|
|
}
|
|
|
|
|
|
+const handleCooperationaa = (item) => {
|
|
|
+ cooperationItem.value = item
|
|
|
+ showCooperationModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
const unbind = async (item) => {
|
|
|
try {
|
|
|
await ElMessageBox.confirm(`确定要解绑设备 ${item.snCode} 吗?`, '提示', {
|
|
@@ -407,6 +455,21 @@ const handleRenew = (item) => {
|
|
|
showRenew.value = true
|
|
|
}
|
|
|
|
|
|
+const setTimeoutShowCtrls = (bool, item) => {
|
|
|
+ clearTimeout(timer.value)
|
|
|
+ console.log(bool, item, 7777)
|
|
|
+ timer.value = setTimeout(() => {
|
|
|
+ showCtrls.value = bool ? item.id : null
|
|
|
+ }, 200)
|
|
|
+}
|
|
|
+
|
|
|
+const handleRepay = (item) => {
|
|
|
+ reNewItem.value = item
|
|
|
+ showRenew.value = true
|
|
|
+ item.popoverVisible = false
|
|
|
+ showCtrls.value = null
|
|
|
+}
|
|
|
+
|
|
|
const addDevice = () => {
|
|
|
showBinding.value = true
|
|
|
}
|
|
@@ -424,6 +487,11 @@ const handleRenewSuccess = () => {
|
|
|
ElMessage.success('续费成功')
|
|
|
}
|
|
|
|
|
|
+const handleCooperationRefresh = async () => {
|
|
|
+ // 刷新列表
|
|
|
+ await getCameraList()
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
const pageChange = async (page) => {
|
|
|
currentPage.value = page
|
|
@@ -431,18 +499,76 @@ const pageChange = async (page) => {
|
|
|
}
|
|
|
|
|
|
// 工具方法
|
|
|
-const isMember = (item) => {
|
|
|
- return item.userIncrementId && !isExpired(item)
|
|
|
+const validExpired = (time) => {
|
|
|
+ if (!time) {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ let expired = Math.floor((new Date(time) - new Date()) / 86400000) + 1
|
|
|
+
|
|
|
+ return {
|
|
|
+ expiredTime: expired,
|
|
|
+ expiredText: `<span class="expired">${expired}</span>`,
|
|
|
+ isExpired: expired <= 30,
|
|
|
+ trueExpired: expired < 0
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-const isExpiredMember = (item) => {
|
|
|
- return item.userIncrementId && isExpired(item)
|
|
|
+const getBar = (a, b) => {
|
|
|
+ if (a === 0) {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ let temp = (a / b) * 100
|
|
|
+ if (temp < 1) {
|
|
|
+ return '1%'
|
|
|
+ }
|
|
|
+ return (temp > 100 ? 100 : Math.round(temp)) + '%'
|
|
|
+}
|
|
|
+
|
|
|
+const getColor = (a, b) => {
|
|
|
+ let temp = (a / b) * 100
|
|
|
+ let point = 80
|
|
|
+ let color = ''
|
|
|
+ switch (true) {
|
|
|
+ case temp < point:
|
|
|
+ color = '#15BEC8'
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ color = '#ff0000'
|
|
|
+ break
|
|
|
+ }
|
|
|
+ return color
|
|
|
+}
|
|
|
+
|
|
|
+const gotoScene = (item) => {
|
|
|
+ // 根据设备类型跳转到对应的场景页面
|
|
|
+ const tabActiveMap = {
|
|
|
+ 10: 3,
|
|
|
+ 9: 2,
|
|
|
+ 11: 4,
|
|
|
+ 12: 6
|
|
|
+ }
|
|
|
+
|
|
|
+ // 这里可以根据实际需求调整路由跳转逻辑
|
|
|
+ console.log('跳转到场景页面', {
|
|
|
+ item,
|
|
|
+ cameraid: item.snCode || item.childName,
|
|
|
+ tabActive: tabActiveMap[item.cameraType] || 1
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果有路由,可以取消注释下面的代码
|
|
|
+ // router.push({
|
|
|
+ // name: 'scenesearch',
|
|
|
+ // params: { id: '4' },
|
|
|
+ // query: {
|
|
|
+ // cameraid: item.snCode || item.childName,
|
|
|
+ // tabActive: tabActiveMap[item.cameraType] || 1
|
|
|
+ // }
|
|
|
+ // })
|
|
|
}
|
|
|
|
|
|
-const isExpired = (item) => {
|
|
|
- if (!item.spaceEndTime) return false
|
|
|
- const expired = Math.floor((new Date(item.spaceEndTime).getTime() - new Date().getTime()) / 86400000) + 1
|
|
|
- return expired < 0
|
|
|
+// 点击外部关闭弹窗
|
|
|
+const handleDocumentClick = () => {
|
|
|
+ showCtrls.value = null
|
|
|
}
|
|
|
|
|
|
// 生命周期
|
|
@@ -451,6 +577,17 @@ onMounted(async () => {
|
|
|
await getAllTabsTotalCount()
|
|
|
// 然后获取当前标签页的数据
|
|
|
await getCameraList()
|
|
|
+
|
|
|
+ // 添加全局点击事件监听
|
|
|
+ document.documentElement.addEventListener('click', handleDocumentClick)
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ // 清理定时器和事件监听
|
|
|
+ if (timer.value) {
|
|
|
+ clearTimeout(timer.value)
|
|
|
+ }
|
|
|
+ document.documentElement.removeEventListener('click', handleDocumentClick)
|
|
|
})
|
|
|
|
|
|
// 监听器
|
|
@@ -612,7 +749,12 @@ watch(()=>selectedType, async () => {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+.expired-icon {
|
|
|
+ margin-left: 6px;
|
|
|
+ color: #ff0000;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
.table-btn {
|
|
|
color: #15bec8;
|
|
|
cursor: pointer;
|
|
@@ -625,6 +767,7 @@ watch(()=>selectedType, async () => {
|
|
|
.flex-avatar {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
|
|
|
.vip-icon {
|
|
|
width: 16px;
|
|
@@ -637,6 +780,14 @@ watch(()=>selectedType, async () => {
|
|
|
opacity: 0.5;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .device-id {
|
|
|
+ color: #15bec8;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ text-decoration: underline;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.content-wrapper {
|
|
@@ -652,4 +803,95 @@ watch(()=>selectedType, async () => {
|
|
|
.text-danger {
|
|
|
color: #f56c6c;
|
|
|
}
|
|
|
+
|
|
|
+// 容量相关样式
|
|
|
+.capacity-info {
|
|
|
+ .capacity-text {
|
|
|
+ margin-bottom: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .capacity-bar {
|
|
|
+ .c-line {
|
|
|
+ width: 100%;
|
|
|
+ height: 4px;
|
|
|
+ background: #f0f0f0;
|
|
|
+ border-radius: 2px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .active {
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 2px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 过期相关样式
|
|
|
+.expired-icon {
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.expired-ctrls-w {
|
|
|
+ background: #fff;
|
|
|
+ z-index: 1;
|
|
|
+ min-width: 190px;
|
|
|
+ height: 85px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ box-shadow: 0px 2px 12px 0px rgba(50, 50, 51, 0.12);
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #323233;
|
|
|
+ line-height: 22px;
|
|
|
+ p {
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ &::before {
|
|
|
+ content: "";
|
|
|
+ display: block;
|
|
|
+ border: 8px solid transparent;
|
|
|
+ border-bottom-color: #fff;
|
|
|
+ position: absolute;
|
|
|
+ top: -15px;
|
|
|
+ right: 14px;
|
|
|
+ left: auto;
|
|
|
+ z-index: 1;
|
|
|
+ // transform: translateX(-50%);
|
|
|
+ }
|
|
|
+ .ctrls-w {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ margin-top: 13px;
|
|
|
+ }
|
|
|
+ .btn {
|
|
|
+ padding: 0 8px;
|
|
|
+ line-height: 24px;
|
|
|
+ border-radius: 2px;
|
|
|
+ background: #ebebeb;
|
|
|
+ display: inline-block;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .submit-btn {
|
|
|
+ background: #15BEC8;
|
|
|
+ margin-left: 8px;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+.info-content{
|
|
|
+ .more-info-content {
|
|
|
+ span {
|
|
|
+ float: left;
|
|
|
+ width: 50%;
|
|
|
+ text-align: center;
|
|
|
+ color: #202020;
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 5px;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ &.th {
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|