测试上传添加水印
This commit is contained in:
parent
b76a2e36f4
commit
54d8eb1bd2
36
src/App.vue
36
src/App.vue
@ -2,7 +2,7 @@
|
||||
* @Author: XHC
|
||||
* @Date: 2025-05-19 10:21:48
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-30 10:44:11
|
||||
* @LastEditTime: 2025-06-06 17:37:54
|
||||
* @Description:
|
||||
-->
|
||||
<script>
|
||||
@ -37,6 +37,24 @@ export default {
|
||||
color: #808185;
|
||||
}
|
||||
|
||||
// 回到顶部
|
||||
.top-back {
|
||||
position: fixed;
|
||||
right: 30rpx;
|
||||
bottom: 180rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 28rpx;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
// 卡片列表
|
||||
.list-card {
|
||||
.head {
|
||||
@ -47,9 +65,10 @@ export default {
|
||||
justify-content: space-between;
|
||||
padding: 10rpx 0;
|
||||
.title {
|
||||
width: 70%;
|
||||
display: inline-block;
|
||||
height: 25rpx;
|
||||
line-height: 25rpx;
|
||||
height: 30rpx;
|
||||
line-height: 30rpx;
|
||||
padding-left: 15rpx;
|
||||
border-left: 6rpx solid #0A61B9;
|
||||
}
|
||||
@ -65,8 +84,19 @@ export default {
|
||||
background: #EDF3FD;
|
||||
margin-right: 20rpx;
|
||||
font-size: 24rpx;
|
||||
.info-text {
|
||||
display: inline-block;
|
||||
width: 70%;
|
||||
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title, .num, .info, .info-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: xhc
|
||||
* @Date: 2025-05-19 10:44:34
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-29 17:28:49
|
||||
* @LastEditTime: 2025-06-03 14:07:17
|
||||
* @FilePath: \bht-app\src\api\api.js
|
||||
* @Description: api
|
||||
*/
|
||||
@ -13,6 +13,11 @@ export const getRouteList = () => {
|
||||
return http('/bht/patrolRoute/findList', 'post')
|
||||
}
|
||||
|
||||
// 分页查询巡查路线
|
||||
export const getRoutePage = () => {
|
||||
return http('/bht/patrolRoute/findPage', 'post')
|
||||
}
|
||||
|
||||
// 查询点位路线
|
||||
export const getPointList = () => {
|
||||
return http('/bht/patrolPoint/findList', 'post')
|
||||
|
164
src/components/da-dropdown/changelog.md
Normal file
164
src/components/da-dropdown/changelog.md
Normal file
@ -0,0 +1,164 @@
|
||||
# 2.2.2
|
||||
|
||||
修复
|
||||
|
||||
1. 修复`picker`值选定残留问题
|
||||
|
||||
# 2.2.1
|
||||
|
||||
优化
|
||||
|
||||
1. 新增`getMenuList`方法,获取菜单数据【示例6】
|
||||
2. 新增`getMenuIndex`方法,获取指定`prop`的菜单索引位置
|
||||
3. 优化示例使用方法
|
||||
4. 修复互斥、联动的值处理问题
|
||||
5. 更新了说明文档
|
||||
|
||||
# 2.2.0
|
||||
|
||||
推荐更新
|
||||
|
||||
1. 新增更多的示例,示例更全面丰富
|
||||
2. 新增`openMenuItemPopup`方法,打开指定菜单弹窗【示例8】
|
||||
3. 新增`closeMenuPopup`方法,关闭菜单弹窗【示例5】
|
||||
4. 新增`getMenuValue`方法,获取菜单的值【示例8】
|
||||
5. 新增`updateMenu`方法,更新指定菜单项【示例7】
|
||||
6. 新增`setMenuLoading`方法,让某个菜单项处于加载中状态,异步数据有用【示例7】
|
||||
7. 修复`fixedTopValue`及顶部样式错误【示例2】
|
||||
8. 新增`syncDataKey`支持异步数据嵌套回调【示例7】
|
||||
9. 新增异步数据加载状态【示例7】
|
||||
10. 修复日期快捷获取上个月时间的错误
|
||||
11. 修复手动关闭时存在的点击残留
|
||||
12. 优化菜单项联动、互斥选择
|
||||
13. 优化异步数据阻塞菜单回显问题
|
||||
14. 修复数据回调时,会重新渲染菜单问题
|
||||
15. `@close`参数2支持返回当前菜单列表(Array)
|
||||
16. `@confirm`参数2支持返回当前菜单已选内容(Object)
|
||||
17. 菜单项`daterange`支持`showQuick`控制是否显示日期快选
|
||||
18. 移除`menuActiveText`,用处不大
|
||||
19. 更新了说明文档
|
||||
|
||||
# 2.1.1
|
||||
|
||||
优化
|
||||
|
||||
1. 优化图标字体命名
|
||||
|
||||
# 2.1.0
|
||||
|
||||
推荐更新
|
||||
|
||||
1. 现阶段由于兼容性问题,移除动态插槽
|
||||
2. 新增五个拓展插槽,`data`类型为 `slot1`/`slot2`/`slot3`/`slot4`/`slot5`
|
||||
3. 修复倒三角点击蒙层没有复原
|
||||
4. 新增更多演示示例
|
||||
5. 优化非固定页面顶部的效果
|
||||
6. 类型`cell`(下拉列表)数据项新增 `disabled` 用来禁用部分不可用选项
|
||||
7. 修复小程序对`v-show`、主题色的兼容问题
|
||||
8. 优化对 APP 的兼容
|
||||
|
||||
# 2.0.9
|
||||
|
||||
修复
|
||||
|
||||
1. 修复初始化数据时可能存在的选项高亮问题
|
||||
|
||||
# 2.0.8
|
||||
|
||||
优化
|
||||
|
||||
1. 优化拷贝函数引起的 App 兼容问题
|
||||
|
||||
# 2.0.7
|
||||
|
||||
优化
|
||||
|
||||
1. 优化模块图标支持主题换色
|
||||
2. 新增`fixedTopValue`,用于优化异形屏导致搜索框被挡住问题,具体请参考示例项目
|
||||
|
||||
# 2.0.6
|
||||
|
||||
优化
|
||||
|
||||
1. 优化三角图标支持主题换色
|
||||
|
||||
# 2.0.5
|
||||
|
||||
修复
|
||||
|
||||
1. 修复 `picker` 大量数据时未能滚动问题
|
||||
|
||||
# 2.0.4
|
||||
|
||||
修复
|
||||
|
||||
1. 修复弹窗后点击 `click`、`sort` 类型未能收起弹窗
|
||||
|
||||
# 2.0.3
|
||||
|
||||
优化
|
||||
|
||||
1. 移除 scss 的 @use 用法,以修复可能会导致部分用户的 lint 错误
|
||||
|
||||
# 2.0.2
|
||||
|
||||
优化
|
||||
|
||||
1. 优化菜单样式
|
||||
|
||||
# 2.0.1
|
||||
|
||||
新增
|
||||
|
||||
1. 下拉列表`cell`、级联`picker`新增选中图标`showIcon`
|
||||
|
||||
# 2.0.0
|
||||
|
||||
移除 TS
|
||||
|
||||
1. 移除了 TS 写法,现在是纯粹 JS 版本的 Vue3 版本
|
||||
2. 进一步完善使用文档及示例
|
||||
|
||||
# 1.2.1
|
||||
|
||||
新增功能
|
||||
|
||||
1. 新增顶部搜索,当 type 为 `slot` 时,可在弹窗内容自定义插槽
|
||||
2. 新增自定插槽,当 type 为 `search` 时,头部显示搜索框
|
||||
3. 优化界面样式
|
||||
|
||||
# 1.1.2
|
||||
|
||||
优化异步菜单项数据
|
||||
|
||||
1. 菜单项新增 `syncDataFn` 函数字段,返回异步菜单项数据内容
|
||||
|
||||
# 1.1.1
|
||||
|
||||
优化固定弹窗问题
|
||||
|
||||
1. `da-dropdown`新增 `bgColor` 字段,当固定在顶部时,需要填写背景颜色,默认`#fff`
|
||||
2. 优化弹窗时底部滑动问题
|
||||
|
||||
# 1.1.0
|
||||
|
||||
优化数据问题
|
||||
|
||||
### 优化
|
||||
|
||||
1. `da-dropdown`新增 `prop` 字段,通过 prop 的唯一性来区分已选的数据
|
||||
2. `da-dropdown`新增 `fixedTop` 字段,为 true 时将会固定在头部
|
||||
3. 优化说明文档
|
||||
|
||||
# 1.0.0
|
||||
|
||||
初始版本 1.0.0,基于 Typescript+Scss 进行开发,基本完善相关各大平台的小程序兼容问题
|
||||
|
||||
### 新增
|
||||
|
||||
1. 下拉列表(单选)
|
||||
2. 点击高亮
|
||||
3. 点击排序
|
||||
4. 下拉筛选(单选按钮、多选按钮、滑动选择器)
|
||||
5. 级联筛选(单选)
|
||||
6. 日期筛选(日期快选、日期区间选择)
|
172
src/components/da-dropdown/components/cell.vue
Normal file
172
src/components/da-dropdown/components/cell.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<view class="da-dropdown-cell">
|
||||
<view
|
||||
class="da-dropdown-cell-item"
|
||||
:class="[item.checked ? 'is-actived' : '',item.disabled ? 'is-disabled' : '']"
|
||||
v-for="(item, index) in cellOptions"
|
||||
:key="index"
|
||||
@click="handleSelect(item)">
|
||||
<text class="da-dropdown-cell-item--label">{{ item.label }}</text>
|
||||
<text class="da-dropdown-cell-item--suffix">{{ item.suffix }}</text>
|
||||
<text class="da-dropdown-cell-item--check" v-if="item.checked && showIcon" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, watch, ref } from 'vue'
|
||||
import { deepClone } from '../utils'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
dropdownItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
dropdownIndex: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
emits: ['success'],
|
||||
setup(props, { emit }) {
|
||||
const cellOptions = ref([])
|
||||
const showIcon = ref(false)
|
||||
|
||||
function initData(options, value) {
|
||||
const list = deepClone(options)
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const item = list[i]
|
||||
if (item.value === value) {
|
||||
item.checked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
cellOptions.value = list
|
||||
}
|
||||
|
||||
function handleSelect(item) {
|
||||
if (item.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.dropdownItem?.prop) {
|
||||
const res = { [props.dropdownItem.prop]: item.value }
|
||||
emit('success', res, item, props.dropdownIndex)
|
||||
} else {
|
||||
console.error(`菜单项${props.dropdownItem.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.dropdownItem,
|
||||
(v) => {
|
||||
if (v?.options?.length) {
|
||||
initData(v.options, v.value)
|
||||
} else {
|
||||
cellOptions.value = []
|
||||
}
|
||||
showIcon.value = v?.showIcon || false
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
return {
|
||||
cellOptions,
|
||||
showIcon,
|
||||
handleSelect,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 下拉列表
|
||||
.da-dropdown-cell {
|
||||
--cell-height: 80rpx;
|
||||
|
||||
width: 100%;
|
||||
max-height: 60vh;
|
||||
overflow: hidden auto;
|
||||
|
||||
&-item {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: var(--cell-height);
|
||||
padding: 0 24rpx;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
white-space: nowrap;
|
||||
border-bottom: 1rpx solid #dedede;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&--label {
|
||||
flex-grow: 1;
|
||||
max-width: 80%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
// #ifdef H5
|
||||
:deep(> span) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// #endif
|
||||
}
|
||||
|
||||
&--suffix {
|
||||
flex-grow: 1;
|
||||
margin-left: 10px;
|
||||
overflow: hidden;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
// #ifdef H5
|
||||
:deep(> span) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// #endif
|
||||
}
|
||||
|
||||
&--check {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--cell-height);
|
||||
height: var(--cell-height);
|
||||
|
||||
&::after {
|
||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||
font-family: 'da-dropdown-iconfont' !important;
|
||||
font-size: calc(var(--cell-height) / 3 * 2);
|
||||
font-style: normal;
|
||||
content: '\e736';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-actived {
|
||||
color: var(--dropdown-theme-color);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
color: #aaa;
|
||||
background: #efefef;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
209
src/components/da-dropdown/components/daterange.vue
Normal file
209
src/components/da-dropdown/components/daterange.vue
Normal file
@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<view class="da-dropdown-daterange-box">
|
||||
<view class="da-dropdown-daterange">
|
||||
<view class="da-dropdown-daterange--date" :class="daterange.start ? 'is-actived' : ''">
|
||||
<picker mode="date" :value="daterange.start" @change="handleStartDate">
|
||||
{{ daterange.start || '请选择日期' }}
|
||||
</picker>
|
||||
</view>
|
||||
<view class="da-dropdown-daterange--separate">至</view>
|
||||
<view class="da-dropdown-daterange--date" :class="daterange.end ? 'is-actived' : ''">
|
||||
<picker
|
||||
mode="date"
|
||||
:value="daterange.end"
|
||||
:disabled="!daterange.start"
|
||||
:start="daterange.start"
|
||||
@change="handleEndDate">
|
||||
{{ daterange.end || '请选择日期' }}
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="da-dropdown-daterange-tags" v-if="dropdownItem.showQuick">
|
||||
<block v-for="(tag, tagi) in dateTagList" :key="tagi">
|
||||
<view
|
||||
class="da-dropdown-tag"
|
||||
:class="datetag=== tag.value ? 'is-actived' : ''"
|
||||
@click="handleTagDate(tag.value)">
|
||||
<text class="da-dropdown-tag--text">{{ tag.label }}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<PartDropdownFooter
|
||||
:resetText="dropdownItem.resetText"
|
||||
:confirmText="dropdownItem.confirmText"
|
||||
@reset="handleReset()"
|
||||
@confirm="handleConfirm()"></PartDropdownFooter>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
import { deepClone, getRangeDate } from '../utils'
|
||||
import PartDropdownFooter from './part-dropdown-footer.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { PartDropdownFooter },
|
||||
props: {
|
||||
dropdownItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
dropdownIndex: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
emits: ['success'],
|
||||
setup(props, { emit }) {
|
||||
const daterange = ref(null)
|
||||
const datetag = ref('')
|
||||
const dateTagList = ref([
|
||||
{ value: '-7', label: '本周' },
|
||||
{ value: '-14', label: '上周' },
|
||||
{ value: '-30', label: '本月' },
|
||||
{ value: '-60', label: '上月' },
|
||||
// { value: '-1', label: '昨日' },
|
||||
{ value: '7', label: '近7天' },
|
||||
{ value: '15', label: '近15天' },
|
||||
{ value: '30', label: '近30天' },
|
||||
])
|
||||
|
||||
function initData(dropdownItem, clearValue = false) {
|
||||
const item = deepClone(dropdownItem || null)
|
||||
if (clearValue === true) {
|
||||
daterange.value = {
|
||||
start: '',
|
||||
end: '',
|
||||
}
|
||||
datetag.value = ''
|
||||
} else {
|
||||
daterange.value = {
|
||||
start: item.value?.start || '',
|
||||
end: item.value?.end || '',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStartDate(item) {
|
||||
daterange.value.start = item.detail.value
|
||||
daterange.value.end = ''
|
||||
datetag.value = ''
|
||||
}
|
||||
function handleEndDate(item) {
|
||||
if (!daterange.value?.start) {
|
||||
return
|
||||
}
|
||||
daterange.value.end = item.detail.value
|
||||
datetag.value = ''
|
||||
}
|
||||
function handleTagDate(code) {
|
||||
daterange.value = getRangeDate(code)
|
||||
datetag.value = code
|
||||
}
|
||||
function handleReset() {
|
||||
initData(props.dropdownItem, true)
|
||||
}
|
||||
function handleConfirm() {
|
||||
if (props.dropdownItem?.prop) {
|
||||
const res = { [props.dropdownItem.prop]: deepClone(daterange.value) }
|
||||
emit('success', res, daterange.value, props.dropdownIndex)
|
||||
} else {
|
||||
console.error(`菜单项${props.dropdownItem.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.dropdownItem,
|
||||
(v) => {
|
||||
initData(v)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return {
|
||||
daterange,
|
||||
datetag,
|
||||
dateTagList,
|
||||
handleStartDate,
|
||||
handleEndDate,
|
||||
handleTagDate,
|
||||
handleReset,
|
||||
handleConfirm,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 日期范围
|
||||
.da-dropdown-daterange {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 999rpx;
|
||||
|
||||
&--date {
|
||||
flex-grow: 1;
|
||||
height: 66rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 66rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
text-align: center;
|
||||
border-radius: 4rpx;
|
||||
|
||||
&.is-actived {
|
||||
color: var(--dropdown-theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--separate {
|
||||
flex-shrink: 0;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
&-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.da-dropdown-tag {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 40rpx;
|
||||
margin-right: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 999rpx;
|
||||
|
||||
&--text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.is-actived {
|
||||
color: var(--dropdown-theme-color);
|
||||
background-color: #fff;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
content: '';
|
||||
background-color: var(--dropdown-theme-color);
|
||||
opacity: 0.05;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
220
src/components/da-dropdown/components/filter.vue
Normal file
220
src/components/da-dropdown/components/filter.vue
Normal file
@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<view class="da-dropdown-filter">
|
||||
<view class="da-dropdown-filter-box" v-for="(item, index) in filterList" :key="index">
|
||||
<view class="da-dropdown-filter--title">{{ item.title }}</view>
|
||||
<view class="da-dropdown-filter-content">
|
||||
<!-- 单选类型 -->
|
||||
<block v-if="item.type === 'radio'">
|
||||
<view
|
||||
v-for="(opt, optIdx) in item.options"
|
||||
class="da-dropdown-filter-item da-dropdown-tag"
|
||||
:class="item.value === opt.value ? 'is-actived' : ''"
|
||||
:key="optIdx"
|
||||
@click="handleRadioChange(item, opt, optIdx, index)">
|
||||
<text class="da-dropdown-tag--text">{{ opt.label }}</text>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 多选类型 -->
|
||||
<block v-else-if="item.type === 'checkbox'">
|
||||
<view
|
||||
v-for="(opt, optIdx) in item.options"
|
||||
class="da-dropdown-filter-item da-dropdown-tag"
|
||||
:class="opt.isActived ? 'is-actived' : ''"
|
||||
:key="optIdx"
|
||||
@click="handleCheckboxChange(item, opt, optIdx, index)">
|
||||
<text class="da-dropdown-tag--text">{{ opt.label }}</text>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 滑块类型 -->
|
||||
<block v-else-if="item.type === 'slider'">
|
||||
<slider
|
||||
style="width: 100%"
|
||||
:value="item.value"
|
||||
:min="item.componentProps.min || 0"
|
||||
:max="item.componentProps.max || 100"
|
||||
:step="item.componentProps.step || 1"
|
||||
:activeColor="item.componentProps.activeColor"
|
||||
:show-value="item.componentProps.showValue"
|
||||
@change="(e) => handleSliderChange(e, item, index)" />
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<PartDropdownFooter
|
||||
:resetText="dropdownItem.resetText"
|
||||
:confirmText="dropdownItem.confirmText"
|
||||
@reset="handleReset()"
|
||||
@confirm="handleConfirm()"></PartDropdownFooter>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
import { deepClone } from '../utils'
|
||||
import PartDropdownFooter from './part-dropdown-footer.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { PartDropdownFooter },
|
||||
props: {
|
||||
dropdownItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
dropdownIndex: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
emits: ['success', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const filterList = ref(null)
|
||||
|
||||
function initData(dropdownItem, clearValue = false) {
|
||||
const { options = [], value = {} } = dropdownItem
|
||||
if (options?.length) {
|
||||
const list = deepClone(options)
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const k = list[i]
|
||||
if (clearValue !== true && (value[k.prop] || value[k.prop] === 0)) {
|
||||
k.value = value[k.prop]
|
||||
}
|
||||
|
||||
// 多选
|
||||
if (k.type === 'checkbox' && k.value?.length) {
|
||||
if (k.options?.length) {
|
||||
k.options.forEach((x) => {
|
||||
x.isActived = k.value?.includes(x.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
filterList.value = list
|
||||
} else {
|
||||
filterList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function handleRadioChange(item, opt, _optIdx, _index) {
|
||||
item.value = opt.value
|
||||
}
|
||||
function handleCheckboxChange(item, opt, _optIdx, _index) {
|
||||
if (opt.isActived) {
|
||||
opt.isActived = false
|
||||
if (item.value?.length) {
|
||||
const idx = item.value.findIndex((k) => k === opt.value)
|
||||
item.value.splice(idx, 1)
|
||||
} else {
|
||||
item.value = []
|
||||
}
|
||||
} else {
|
||||
if (item.value?.length) {
|
||||
item.value.push(opt.value)
|
||||
} else {
|
||||
item.value = [opt.value]
|
||||
}
|
||||
opt.isActived = true
|
||||
}
|
||||
}
|
||||
function handleSliderChange(event, item, _index) {
|
||||
const v = event.detail.value
|
||||
item.value = v
|
||||
}
|
||||
function handleReset() {
|
||||
initData(props.dropdownItem || [], true)
|
||||
}
|
||||
function handleConfirm() {
|
||||
const list = deepClone(filterList.value)
|
||||
|
||||
if (props.dropdownItem?.prop) {
|
||||
const obj = {}
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const k = list[i]
|
||||
if (k.value || k.value === 0) {
|
||||
obj[k.prop] = k.value
|
||||
}
|
||||
}
|
||||
const res = { [props.dropdownItem.prop]: obj }
|
||||
emit('success', res, obj, props.dropdownIndex)
|
||||
} else {
|
||||
console.error(`菜单项${props.dropdownItem.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.dropdownItem,
|
||||
(v) => {
|
||||
initData(v || null)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return {
|
||||
filterList,
|
||||
handleRadioChange,
|
||||
handleCheckboxChange,
|
||||
handleSliderChange,
|
||||
handleReset,
|
||||
handleConfirm,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 多条件筛选
|
||||
.da-dropdown-filter {
|
||||
&-box {
|
||||
padding: 0 24rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&--title {
|
||||
flex-shrink: 0;
|
||||
padding: 20rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.da-dropdown-tag {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 40rpx;
|
||||
margin-right: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 999rpx;
|
||||
|
||||
&--text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.is-actived {
|
||||
color: var(--dropdown-theme-color);
|
||||
background-color: #fff;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
content: '';
|
||||
background-color: var(--dropdown-theme-color);
|
||||
opacity: 0.05;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<view class="da-dropdown-footer">
|
||||
<view class="da-dropdown-footer--reset" @click="handleReset()">{{ resetText || '重置' }}</view>
|
||||
<view class="da-dropdown-footer--confirm" @click="handleConfirm()">
|
||||
{{ confirmText || '确定' }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PartDropdownFooter',
|
||||
props: {
|
||||
resetText: {
|
||||
type: String,
|
||||
default: '重置',
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确定',
|
||||
},
|
||||
},
|
||||
emits: ['confirm', 'reset'],
|
||||
setup(_, { emit }) {
|
||||
function handleReset() {
|
||||
emit('reset')
|
||||
}
|
||||
function handleConfirm() {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
return {
|
||||
handleReset,
|
||||
handleConfirm,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.da-dropdown-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
margin-top: 20rpx;
|
||||
|
||||
&--reset,
|
||||
&--confirm {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 66rpx;
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #ccc;
|
||||
border-radius: 66rpx;
|
||||
}
|
||||
|
||||
&--confirm {
|
||||
margin-left: 24rpx;
|
||||
color: #fff;
|
||||
background-color: var(--dropdown-theme-color);
|
||||
border-color: var(--dropdown-theme-color);
|
||||
}
|
||||
|
||||
&--reset:hover,
|
||||
&--confirm:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
220
src/components/da-dropdown/components/picker.vue
Normal file
220
src/components/da-dropdown/components/picker.vue
Normal file
@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<view class="da-dropdown-picker" v-if="viewCol && viewCol.length">
|
||||
<view
|
||||
class="da-dropdown-picker-inner"
|
||||
v-for="(vc, vci) in viewCol"
|
||||
:key="vci">
|
||||
<scroll-view
|
||||
class="da-dropdown-picker-view"
|
||||
scroll-y>
|
||||
<view
|
||||
class="da-dropdown-picker-item"
|
||||
:class="vr.checked ? 'is-actived' : ''"
|
||||
v-for="(vr, vri) in viewRow[vci]"
|
||||
:key="vri"
|
||||
@click="handleSelect(vr, vci, vri)">
|
||||
<text class="da-dropdown-picker-item--name">{{ vr.label }}</text>
|
||||
<text class="da-dropdown-picker-item--icon" v-if="vr.children && vr.children.length"></text>
|
||||
<text class="da-dropdown-picker-item--check" v-if="vr.checked && (!vr.children || vr.children.length === 0)" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
import { deepClone } from '../utils'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
dropdownItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
dropdownIndex: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
emits: ['success'],
|
||||
setup(props, { emit }) {
|
||||
const viewCol = ref([])
|
||||
const viewRow = ref([])
|
||||
|
||||
function checkData(selected, list) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const k = list[i]
|
||||
for (let j = 0; j < selected.length; j++) {
|
||||
const x = selected[j]
|
||||
if (k.value === x) {
|
||||
k.checked = true
|
||||
viewCol.value.push(k.value)
|
||||
viewRow.value.push(list)
|
||||
if (k.children?.length) {
|
||||
checkData(selected, k.children)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initData(item) {
|
||||
const list = deepClone(item?.options || [])
|
||||
if (list?.length) {
|
||||
if (item.value?.length) {
|
||||
viewCol.value = []
|
||||
viewRow.value = []
|
||||
|
||||
checkData(item.value, list)
|
||||
} else {
|
||||
viewCol.value.push('tmpValue')
|
||||
viewRow.value.push(list)
|
||||
}
|
||||
} else {
|
||||
viewCol.value = []
|
||||
viewRow.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelect(item, colIndex, _rowIndex) {
|
||||
let lastItem = false
|
||||
viewCol.value.splice(colIndex)
|
||||
viewCol.value[colIndex] = item.value
|
||||
|
||||
if (viewRow.value[colIndex]?.length) {
|
||||
viewRow.value[colIndex].forEach(k => {
|
||||
k.checked = false
|
||||
})
|
||||
}
|
||||
|
||||
item.checked = true
|
||||
const list = item?.children || null
|
||||
|
||||
if (list?.length) {
|
||||
viewCol.value[colIndex + 1] = 'tmpValue'
|
||||
viewRow.value[colIndex + 1] = list
|
||||
lastItem = false
|
||||
} else {
|
||||
console.warn('最后一项', item)
|
||||
lastItem = true
|
||||
}
|
||||
|
||||
try {
|
||||
if (viewRow.value[colIndex + 1]?.length) {
|
||||
viewRow.value[colIndex + 1].forEach(k => {
|
||||
k.checked = false
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('try clean row data', e)
|
||||
// --
|
||||
}
|
||||
|
||||
if (lastItem) {
|
||||
if (props.dropdownItem?.prop) {
|
||||
const res = { [props.dropdownItem.prop]: deepClone(viewCol.value) }
|
||||
emit('success', res, viewCol.value, props.dropdownIndex)
|
||||
} else {
|
||||
console.error(`菜单项${props.dropdownItem.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.dropdownItem,
|
||||
(v) => {
|
||||
initData(v)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return {
|
||||
viewCol,
|
||||
viewRow,
|
||||
|
||||
handleSelect,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.da-dropdown-picker {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-height: 60vh;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
|
||||
&-inner {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&-view {
|
||||
display: flex;
|
||||
|
||||
/* #ifdef MP-ALIPAY */
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
|
||||
/* #endif */
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
+ .da-dropdown-picker-view {
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
text-align: left;
|
||||
|
||||
&--icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
|
||||
&::after {
|
||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||
font-family: 'da-dropdown-iconfont' !important;
|
||||
font-size: 24rpx;
|
||||
font-style: normal;
|
||||
content: '\e643';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
&--check {
|
||||
flex-shrink: 0;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
|
||||
&::after {
|
||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||
font-family: 'da-dropdown-iconfont' !important;
|
||||
font-size: 24rpx;
|
||||
font-style: normal;
|
||||
content: '\e696';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
&.is-actived {
|
||||
color: var(--dropdown-theme-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
898
src/components/da-dropdown/index.vue
Normal file
898
src/components/da-dropdown/index.vue
Normal file
@ -0,0 +1,898 @@
|
||||
<template>
|
||||
<view class="da-dropdown" :class="{'is-fixed': fixedTop, 'has-search': hasSearch}" :style="dropdownStyle">
|
||||
<!-- 搜索 -->
|
||||
<view class="da-dropdown-search" v-if="hasSearch" @touchmove.stop.prevent="handleMove">
|
||||
<input
|
||||
class="da-dropdown-search-input"
|
||||
:value="searchItem.value"
|
||||
@input="handleSearchChange"
|
||||
:placeholder="searchItem.placeholder || '请输入'"
|
||||
@confirm="handleSearch"
|
||||
confirm-type="search" />
|
||||
<button class="da-dropdown-search-btn" @click="handleSearch">搜索</button>
|
||||
</view>
|
||||
<!-- 菜单 -->
|
||||
<view class="da-dropdown-menu" @touchmove.stop.prevent="handleMove">
|
||||
<view
|
||||
class="da-dropdown-menu-item"
|
||||
:class="{'is-hidden':item.isHidden === 'true'}"
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index"
|
||||
@click="handleMenuClick(index,item)">
|
||||
<text class="da-dropdown-menu-item--text" :class="item.isActived ? 'is-actived' : ''">{{ item.title }}</text>
|
||||
<view class="da-dropdown-menu-item--icon" v-if="item.showArrow">
|
||||
<text v-if="item.isLoading" class="is--loading"></text>
|
||||
<text v-else-if="item.isClick" class="is--arrup"></text>
|
||||
<text v-else class="is--arrdown"></text>
|
||||
</view>
|
||||
<view class="da-dropdown-menu-item--sort" v-else-if="item.showSort" :class="'is--' + item.value"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 弹出 -->
|
||||
<view class="da-dropdown-content" :class="{'is-show': isShow,'is-visible': isVisible}">
|
||||
<view class="da-dropdown-content-popup" :class="isShow ? 'is-show' : ''">
|
||||
<view class="da-dropdown-popup-box" v-for="(item, index) in menuList" :key="index">
|
||||
<!-- 下拉列表 -->
|
||||
<DropdownCell
|
||||
v-if="item.type === 'cell' && index === currentIndex"
|
||||
:dropdownItem="item"
|
||||
:dropdownIndex="index"
|
||||
@success="handleCellSelect"></DropdownCell>
|
||||
<!-- 多条件筛选 -->
|
||||
<DropdownFilter
|
||||
v-if="item.type === 'filter' && index === currentIndex"
|
||||
:dropdownItem="item"
|
||||
:dropdownIndex="index"
|
||||
@success="handleFilterConfirm"></DropdownFilter>
|
||||
<!-- 级联选择 -->
|
||||
<DropdownPicker
|
||||
v-if="item.type === 'picker' && index === currentIndex"
|
||||
:dropdownItem="item"
|
||||
:dropdownIndex="index"
|
||||
@success="handlePickerConfirm" />
|
||||
<!-- 日期范围 -->
|
||||
<DropdownDaterange
|
||||
v-if="item.type === 'daterange' && index === currentIndex"
|
||||
:dropdownItem="item"
|
||||
:dropdownIndex="index"
|
||||
@success="handleDaterangeConfirm" />
|
||||
<!-- 弹窗插槽拓展X5 -->
|
||||
<template v-if="item.type === 'slot1' && index === currentIndex">
|
||||
<slot name="slot1" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<template v-if="item.type === 'slot2' && index === currentIndex">
|
||||
<slot name="slot2" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<template v-if="item.type === 'slot3' && index === currentIndex">
|
||||
<slot name="slot3" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<template v-if="item.type === 'slot4' && index === currentIndex">
|
||||
<slot name="slot4" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<template v-if="item.type === 'slot5' && index === currentIndex">
|
||||
<slot name="slot5" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="da-dropdown-content-mask"
|
||||
v-if="fixedTop"
|
||||
@tap="handlePopupMask"
|
||||
@touchmove.stop.prevent="handleMove" />
|
||||
</view>
|
||||
<view class="da-dropdown--blank" v-if="fixedTop"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, computed, onMounted } from 'vue'
|
||||
|
||||
import { deepClone, menuInitOpts, getValueByKey, checkDataField } from './utils'
|
||||
import DropdownPicker from './components/picker.vue'
|
||||
import DropdownCell from './components/cell.vue'
|
||||
import DropdownFilter from './components/filter.vue'
|
||||
import DropdownDaterange from './components/daterange.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { DropdownPicker, DropdownCell, DropdownFilter, DropdownDaterange },
|
||||
props: {
|
||||
/**
|
||||
* 导航菜单数据
|
||||
*/
|
||||
dropdownMenu: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
/**
|
||||
* 激活颜色
|
||||
*/
|
||||
themeColor: {
|
||||
type: String,
|
||||
default: '#007aff',
|
||||
},
|
||||
/**
|
||||
* 常规颜色
|
||||
*/
|
||||
textColor: {
|
||||
type: String,
|
||||
default: '#333333',
|
||||
},
|
||||
/**
|
||||
* 背景颜色,当固定在顶部时,此为必填
|
||||
*/
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff',
|
||||
},
|
||||
/**
|
||||
* 是否固定在顶部
|
||||
*/
|
||||
fixedTop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 固定在头部时的位置,单位px
|
||||
* 如果页面定义了 "navigationStyle": "custom" ,因此固定头部时需要额外获取状态栏高度,以免被异形屏头部覆盖
|
||||
*/
|
||||
fixedTopValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
* 弹窗过渡时间
|
||||
*/
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 300,
|
||||
},
|
||||
},
|
||||
emits: ['open', 'close', 'confirm'],
|
||||
setup(props, { emit }) {
|
||||
const currentIndex = ref(-1)
|
||||
const isVisible = ref(false)
|
||||
const isShow = ref(false)
|
||||
const menuList = ref([])
|
||||
const hasSearch = ref(false)
|
||||
const searchItem = ref(null)
|
||||
|
||||
// 主题样式
|
||||
const dropdownStyle = computed(() => {
|
||||
return `
|
||||
--dropdown-theme-color: ${props.themeColor};
|
||||
--dropdown-text-color: ${props.textColor};
|
||||
--dropdown-background-color: ${props.bgColor};
|
||||
--dropdown-popup-duration: ${props.duration / 1000}s;
|
||||
--dropdown-fixed-top: ${props.fixedTopValue || 0}px;
|
||||
`
|
||||
})
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
async function initData() {
|
||||
const newMenu = deepClone(props.dropdownMenu || [])
|
||||
const allItem = { label: '不限', value: '-9999' }
|
||||
if (!newMenu || newMenu.length === 0) {
|
||||
menuList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < newMenu.length; i++) {
|
||||
let item = newMenu[i]
|
||||
if (item?.type) {
|
||||
item = { ...(menuInitOpts[newMenu[i]['type']] || {}), ...item }
|
||||
}
|
||||
|
||||
// 处理异步初始项
|
||||
if (typeof item.syncDataFn === 'function') {
|
||||
item.isLoading = true
|
||||
item.syncDataFn(item, i).then((res) => {
|
||||
menuList.value[i].options = checkDataField(item.syncDataKey ? getValueByKey(res, item.syncDataKey) : res, item.field)
|
||||
// 处理 不限 项
|
||||
if (this.menuList[i].showAll) {
|
||||
if (this.menuList[i].options.findIndex((k) => k.value === allItem.value) === -1) {
|
||||
this.menuList[i].options.unshift(allItem)
|
||||
}
|
||||
}
|
||||
menuList.value[i].isLoading = false
|
||||
}).catch(() => {
|
||||
menuList.value[i].isLoading = false
|
||||
})
|
||||
}
|
||||
|
||||
if (item.options?.length) {
|
||||
// 同步差异字段
|
||||
item.options = checkDataField(item.options, item.field)
|
||||
// 处理 不限 项
|
||||
if (item.showAll) {
|
||||
if (item.options.findIndex((k) => k.value === allItem.value) === -1) {
|
||||
item.options.unshift(allItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理已选项
|
||||
if (typeof item.value !== 'undefined') {
|
||||
switch (item.type) {
|
||||
case 'cell':
|
||||
for (let x = 0; x < item.options.length; x++) {
|
||||
const k = item.options[x]
|
||||
if (k.value === item.value) {
|
||||
item.isActived = true
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'click':
|
||||
item.isActived = item.value === true
|
||||
|
||||
break
|
||||
case 'sort':
|
||||
item.isActived = item.value === 'asc' || item.value === 'desc'
|
||||
|
||||
break
|
||||
case 'filter':
|
||||
item.isActived = JSON.stringify(item.value || {}) !== '{}'
|
||||
|
||||
break
|
||||
case 'picker':
|
||||
item.isActived = item.value?.length
|
||||
|
||||
break
|
||||
case 'daterange':
|
||||
|
||||
item.isActived = item.value?.start && item.value?.end
|
||||
break
|
||||
case 'slot':
|
||||
item.isActived = !!item.value
|
||||
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
item.isActived = false
|
||||
}
|
||||
|
||||
// 搜索项特殊处理
|
||||
if (!hasSearch.value && item.type === 'search') {
|
||||
item.isHidden = 'true'
|
||||
searchItem.value = item
|
||||
hasSearch.value = true
|
||||
}
|
||||
|
||||
newMenu[i] = item
|
||||
}
|
||||
menuList.value = newMenu
|
||||
}
|
||||
/**
|
||||
* 更新数据
|
||||
* @param prop
|
||||
* @param value
|
||||
* @param key
|
||||
*/
|
||||
function updateMenu(prop, value, key) {
|
||||
if (!key) {
|
||||
console.error('updateMenu 错误,key不存在')
|
||||
return
|
||||
}
|
||||
const idx = getMenuIndex(prop)
|
||||
menuList.value[idx][key] = key === 'options' ? checkDataField(value, menuList.value[idx].field || null) : value
|
||||
|
||||
// 去除点击效果
|
||||
if (key === 'value' && (!value && value !== 0)) {
|
||||
menuList.value[idx].isActived = false
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 更新数据
|
||||
* @param prop
|
||||
* @param state
|
||||
*/
|
||||
function setMenuLoading(prop, state) {
|
||||
const idx = getMenuIndex(prop)
|
||||
menuList.value[idx].isLoading = state
|
||||
}
|
||||
/**
|
||||
* 获取菜单项位置
|
||||
* @param prop
|
||||
*/
|
||||
function getMenuIndex(prop) {
|
||||
return menuList.value.findIndex(k => k.prop === prop)
|
||||
}
|
||||
/**
|
||||
* 获取菜单数据
|
||||
*/
|
||||
function getMenuList() {
|
||||
return menuList.value
|
||||
}
|
||||
/**
|
||||
* 初始化获取系统信息
|
||||
*/
|
||||
function initDomInfo() { }
|
||||
/**
|
||||
* 打开弹窗
|
||||
* @param index 当前激活索引
|
||||
*/
|
||||
function openMenuItemPopup(index) {
|
||||
isShow.value = true
|
||||
isVisible.value = true
|
||||
currentIndex.value = index
|
||||
menuList.value[index].isClick = true
|
||||
emit('open', currentIndex.value)
|
||||
}
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
function closeMenuPopup() {
|
||||
clearClickState()
|
||||
isShow.value = false
|
||||
// 延迟移除下拉弹窗
|
||||
setTimeout(() => {
|
||||
isVisible.value = false
|
||||
clearIndex()
|
||||
}, props.duration)
|
||||
emit('close', currentIndex.value, menuList.value)
|
||||
}
|
||||
/**
|
||||
* 点击蒙层
|
||||
*/
|
||||
function handlePopupMask() {
|
||||
closeMenuPopup()
|
||||
}
|
||||
/**
|
||||
* 清除点击状态
|
||||
*/
|
||||
function clearClickState() {
|
||||
if (menuList.value?.length) {
|
||||
menuList.value.forEach(k => {
|
||||
k.isClick = false
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清理滚动
|
||||
*/
|
||||
function handleMove() {
|
||||
return false
|
||||
}
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
function clearIndex() {
|
||||
currentIndex.value = -1
|
||||
}
|
||||
/**
|
||||
* 点击菜单项
|
||||
*/
|
||||
function handleMenuClick(index, item) {
|
||||
if (item.isLoading) return
|
||||
|
||||
const dropdownMenu = menuList.value
|
||||
const menuItem = dropdownMenu[index]
|
||||
|
||||
dropdownMenu.forEach(k => {
|
||||
k.isClick = false
|
||||
})
|
||||
|
||||
if (menuItem.type === 'click') {
|
||||
return handleItemClick(menuItem, index)
|
||||
}
|
||||
|
||||
if (menuItem.type === 'sort') {
|
||||
return handleItemSort(menuItem, index)
|
||||
}
|
||||
if (index === currentIndex.value) {
|
||||
item.isClick = false
|
||||
closeMenuPopup()
|
||||
return
|
||||
}
|
||||
|
||||
item.isClick = true
|
||||
|
||||
openMenuItemPopup(index)
|
||||
}
|
||||
/**
|
||||
* 获取菜单值
|
||||
*/
|
||||
function getMenuValue() {
|
||||
const obj = {}
|
||||
menuList.value.forEach(k => {
|
||||
obj[k.prop] = k.value
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
function handleSearchChange(e) {
|
||||
searchItem.value.value = e?.detail?.value
|
||||
}
|
||||
/**
|
||||
* 确定搜索
|
||||
*/
|
||||
function handleSearch() {
|
||||
if (searchItem.value?.prop) {
|
||||
const res = { [searchItem.value.prop]: searchItem.value.value }
|
||||
emit('confirm', res, getMenuValue())
|
||||
} else {
|
||||
console.error(`菜单项${searchItem.value.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 菜单项-下拉列表回调
|
||||
* @param callbackData 操作返回的数据
|
||||
* @param cellItem 下拉列表项数据
|
||||
* @param index 菜单索引
|
||||
*/
|
||||
function handleCellSelect(callbackData, cellItem, index) {
|
||||
const dropdownMenu = menuList.value
|
||||
const item = dropdownMenu[index]
|
||||
item.isClick = false
|
||||
|
||||
if (cellItem.value === '-9999') {
|
||||
item.isActived = false
|
||||
item.value = null
|
||||
} else {
|
||||
item.isActived = true
|
||||
item.value = cellItem.value
|
||||
}
|
||||
|
||||
closeMenuPopup()
|
||||
emit('confirm', callbackData, getMenuValue())
|
||||
}
|
||||
/**
|
||||
* 菜单项-点击
|
||||
* @param item 菜单项
|
||||
* @param index 菜单项索引
|
||||
*/
|
||||
function handleItemClick(item, index) {
|
||||
closeMenuPopup()
|
||||
|
||||
if (currentIndex.value === -1) {
|
||||
currentIndex.value = index
|
||||
item.value = true
|
||||
item.isActived = true
|
||||
} else {
|
||||
item.value = false
|
||||
item.isActived = false
|
||||
clearIndex()
|
||||
}
|
||||
if (item?.prop) {
|
||||
const res = { [item.prop]: item.value }
|
||||
emit('confirm', res, getMenuValue())
|
||||
} else {
|
||||
console.error(`菜单项${item.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 菜单项-排序
|
||||
* @param item 菜单项
|
||||
* @param index 菜单项索引
|
||||
*/
|
||||
function handleItemSort(item, index) {
|
||||
closeMenuPopup()
|
||||
|
||||
if (item.value === 'asc') {
|
||||
item.value = 'desc'
|
||||
currentIndex.value = index
|
||||
item.isActived = true
|
||||
} else if (item.value === 'desc') {
|
||||
item.value = undefined
|
||||
item.isActived = false
|
||||
clearIndex()
|
||||
} else {
|
||||
item.value = 'asc'
|
||||
currentIndex.value = index
|
||||
item.isActived = true
|
||||
}
|
||||
|
||||
if (item?.prop) {
|
||||
const res = { [item.prop]: item.value }
|
||||
emit('confirm', res, getMenuValue())
|
||||
} else {
|
||||
console.error(`菜单项${item.title}未定义prop,返回内容失败`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 菜单项-筛选回调
|
||||
* @param callbackData 操作返回的数据
|
||||
* @param filterData 筛选数据
|
||||
* @param index 菜单索引
|
||||
*/
|
||||
function handleFilterConfirm(callbackData, filterData, index) {
|
||||
const dropdownMenu = menuList.value
|
||||
const item = dropdownMenu[index]
|
||||
item.isClick = false
|
||||
|
||||
item.isActived = JSON.stringify(filterData || {}) !== '{}'
|
||||
item.value = filterData
|
||||
|
||||
closeMenuPopup()
|
||||
emit('confirm', callbackData, getMenuValue())
|
||||
}
|
||||
/**
|
||||
* 菜单项-级联回调
|
||||
* @param callbackData 操作返回的数据
|
||||
* @param pickerItem 级联已选数据
|
||||
* @param index 菜单索引
|
||||
*/
|
||||
function handlePickerConfirm(callbackData, pickerItem, index) {
|
||||
const dropdownMenu = menuList.value
|
||||
const item = dropdownMenu[index]
|
||||
item.isClick = false
|
||||
|
||||
if (!pickerItem || pickerItem[0] === '-9999') {
|
||||
item.isActived = false
|
||||
item.value = null
|
||||
} else {
|
||||
item.isActived = true
|
||||
item.value = pickerItem
|
||||
}
|
||||
|
||||
closeMenuPopup()
|
||||
emit('confirm', callbackData, getMenuValue())
|
||||
}
|
||||
/**
|
||||
* 菜单项-日期范围回调
|
||||
* @param callbackData 操作返回的数据
|
||||
* @param daterangeItem 日期范围数据
|
||||
* @param index 菜单索引
|
||||
*/
|
||||
function handleDaterangeConfirm(callbackData, daterangeItem, index) {
|
||||
const dropdownMenu = menuList.value
|
||||
const item = dropdownMenu[index]
|
||||
item.isClick = false
|
||||
|
||||
if (daterangeItem?.start && daterangeItem?.end) {
|
||||
item.isActived = true
|
||||
item.value = daterangeItem
|
||||
} else {
|
||||
item.isActived = false
|
||||
item.value = null
|
||||
}
|
||||
|
||||
closeMenuPopup()
|
||||
emit('confirm', callbackData, getMenuValue())
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initDomInfo()
|
||||
initData()
|
||||
})
|
||||
|
||||
return {
|
||||
initData,
|
||||
menuList,
|
||||
updateMenu,
|
||||
setMenuLoading,
|
||||
getMenuIndex,
|
||||
getMenuList,
|
||||
dropdownStyle,
|
||||
currentIndex,
|
||||
isShow,
|
||||
isVisible,
|
||||
hasSearch,
|
||||
searchItem,
|
||||
handleSearchChange,
|
||||
handleSearch,
|
||||
handleMenuClick,
|
||||
handlePopupMask,
|
||||
handleMove,
|
||||
getMenuValue,
|
||||
|
||||
handleCellSelect,
|
||||
handleFilterConfirm,
|
||||
handlePickerConfirm,
|
||||
handleDaterangeConfirm,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@font-face {
|
||||
font-family: 'da-dropdown-iconfont'; /* Project id */
|
||||
src: url('data:application/octet-stream;base64,AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AAAAVE9TLzI8GUoGAAABjAAAAGBjbWFwgZ2FYQAAAgQAAAHIZ2x5ZmWuwwYAAAPcAAACHGhlYWQm2YiXAAAA4AAAADZoaGVhB94DhwAAALwAAAAkaG10eBgAAAAAAAHsAAAAGGxvY2EB9gF4AAADzAAAAA5tYXhwARgAVAAAARgAAAAgbmFtZRCjPLAAAAX4AAACZ3Bvc3QrCOz4AAAIYAAAAFsAAQAAA4D/gABcBAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAYAAQAAAAEAAMt/P/FfDzz1AAsEAAAAAADh3SJNAAAAAOHdIk0AAP//BAADAQAAAAgAAgAAAAAAAAABAAAABgBIAAgAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAQEAAGQAAUAAAKJAswAAACPAokCzAAAAesAMgEIAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAwOYE5zYDgP+AAAAD3ACAAAAAAQAAAAAAAAAAAAAAAAACBAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAAAAABQAAAAMAAAAsAAAABAAAAXwAAQAAAAAAdgADAAEAAAAsAAMACgAAAXwABABKAAAADAAIAAIABOYE5ifmQ+aW5zb//wAA5gTmJ+ZD5pbnNv//AAAAAAAAAAAAAAABAAwADAAMAAwADAAAAAUAAgADAAQAAQAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAATAAAAAAAAAAFAADmBAAA5gQAAAAFAADmJwAA5icAAAACAADmQwAA5kMAAAADAADmlgAA5pYAAAAEAADnNgAA5zYAAAABAAAAAAAoAJgAwADgAQ4AAAABAAAAAANkAooAEwAAGwEeATcBNi4CBwEOAS8BJg4BFKXqBhMHAa4HAQwSB/5vBg8GzwgQDAGi/vEHAQYB0QcSDQEG/rsEAQSHBAINEQAAAAgAAAAAA3EC+AAIABEAGgAjACwANQA+AEcAAAEUBiImNDYyFgMiBhQWMjY0JiUiJjQ2MhYUBiU0JiIGFBYyNhMWFAYiJjQ2MgEGFBYyNjQmIhMGIiY0NjIWFAEmIgYUFjI2NAJYKz4rKz4rShsmJjYmJgEZFBsbJxsb/dAsPSwsPSxEFiw9LCw9AW0QIC8gIC8yCx8WFh8W/lwWPSwsPSwCrR4sLD0sLP27JjYmJjYmxBwmGxsmHC8fKys+LCwBLRY9LCw9LP4qES4gIC4hAWELFh8VFR/+kRYsPSwsPQAAAQAA//8CwAMBABQAAAE0JzUBFSYiBhQXCQEGFBYyNxUBNgLACP7AChsTCAEt/tMIExsKAUAIAYAMCQEBYAELExkJ/rX+tQkZEwsBAWEJAAACAAAAAAN0AsEADQAOAAAlATcXNjc2NxcGBwYHBgcBz/7XTa5QWYeOFF1cT0I7H1oBLz2FW1J7WClWdGRrX0YAAQAAAAADWQJKABkAAAEyHgEGBw4BBw4CJicmLwImJy4BPgEzNwMbFx0JCRBAdzcPKSooDR8hRUIgHQ0ICRsWtgJKEhwkEUeIPBARAQ4QIiNHRiMgDyEbEQEAAAAAABIA3gABAAAAAAAAABMAAAABAAAAAAABAAgAEwABAAAAAAACAAcAGwABAAAAAAADAAgAIgABAAAAAAAEAAgAKgABAAAAAAAFAAsAMgABAAAAAAAGAAgAPQABAAAAAAAKACsARQABAAAAAAALABMAcAADAAEECQAAACYAgwADAAEECQABABAAqQADAAEECQACAA4AuQADAAEECQADABAAxwADAAEECQAEABAA1wADAAEECQAFABYA5wADAAEECQAGABAA/QADAAEECQAKAFYBDQADAAEECQALACYBY0NyZWF0ZWQgYnkgaWNvbmZvbnRpY29uZm9udFJlZ3VsYXJpY29uZm9udGljb25mb250VmVyc2lvbiAxLjBpY29uZm9udEdlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFIAZQBnAHUAbABhAHIAaQBjAG8AbgBmAG8AbgB0AGkAYwBvAG4AZgBvAG4AdABWAGUAcgBzAGkAbwBuACAAMQAuADAAaQBjAG8AbgBmAG8AbgB0AEcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAAcwB2AGcAMgB0AHQAZgAgAGYAcgBvAG0AIABGAG8AbgB0AGUAbABsAG8AIABwAHIAbwBqAGUAYwB0AC4AaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAQIBAwEEAQUBBgEHAAdnb3V4dWFuBmppYXphaQp5b3VqaWFudG91BnhpYXphaQh4aWFuZ3hpYQAAAA==') format('truetype');
|
||||
}
|
||||
|
||||
.da-dropdown {
|
||||
--dropdown-menu-height: 80rpx;
|
||||
--dropdown-popup-duration: 0.3s;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 888;
|
||||
width: 100%;
|
||||
line-height: 1;
|
||||
|
||||
&--blank {
|
||||
width: 100%;
|
||||
height: var(--dropdown-menu-height);
|
||||
}
|
||||
|
||||
&-search {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: var(--dropdown-menu-height);
|
||||
padding: 10rpx 20rpx 6rpx;
|
||||
background: var(--dropdown-background-color, #fff);
|
||||
|
||||
&-input {
|
||||
flex-grow: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
background: #f6f6f6;
|
||||
border-radius: 8rpx 0 0 8rpx;
|
||||
}
|
||||
|
||||
&-btn {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
background: #f6f6f6;
|
||||
border: none;
|
||||
border-radius: 0 8rpx 8rpx 0;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-menu {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--dropdown-menu-height);
|
||||
background: var(--dropdown-background-color, #fff);
|
||||
box-shadow: 0 1rpx 0 0 #bbb;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&:hover {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--text {
|
||||
font-size: 24rpx;
|
||||
color: var(--dropdown-text-color);
|
||||
|
||||
&.is-actived {
|
||||
color: var(--dropdown-theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--icon {
|
||||
flex-shrink: 0;
|
||||
margin-left: 2px;
|
||||
color: #bbb;
|
||||
|
||||
.is--loading,
|
||||
.is--arrup,
|
||||
.is--arrdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
|
||||
&::after {
|
||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||
font-family: 'da-dropdown-iconfont' !important;
|
||||
font-size: 24rpx;
|
||||
font-style: normal;
|
||||
content: '\e604';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
.is--loading {
|
||||
animation: RunLoading 1s linear 0s infinite;
|
||||
|
||||
&::after {
|
||||
content: '\e627';
|
||||
}
|
||||
}
|
||||
|
||||
.is--arrup {
|
||||
color: var(--dropdown-theme-color);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&--sort {
|
||||
position: relative;
|
||||
margin-left: 6rpx;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: calc(50% - 16rpx);
|
||||
left: 0;
|
||||
content: '';
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 8rpx;
|
||||
border-bottom-color: #bbb;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: calc(50% + 6rpx);
|
||||
border-top-color: #bbb;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
&.is--asc::before {
|
||||
border-bottom-color: var(--dropdown-theme-color);
|
||||
}
|
||||
|
||||
&.is--desc::after {
|
||||
border-top-color: var(--dropdown-theme-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
position: absolute;
|
||||
top: var(--dropdown-menu-height);
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
box-shadow: 0 -1rpx 0 0 #bbb;
|
||||
opacity: 0;
|
||||
transition: all var(--dropdown-popup-duration, 0.3s) linear;
|
||||
|
||||
&.is-show {
|
||||
z-index: 901;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-visible {
|
||||
visibility: visible;
|
||||
animation: CustomBS var(--dropdown-popup-duration) linear var(--dropdown-popup-duration) forwards;
|
||||
}
|
||||
|
||||
&-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
&-popup {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
transition: transform var(--dropdown-popup-duration) linear;
|
||||
transform: translateY(-100%);
|
||||
|
||||
&.is-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-popup-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
background: var(--dropdown-background-color, #fff);
|
||||
transition: border-radius var(--dropdown-popup-duration) linear;
|
||||
}
|
||||
|
||||
&.has-search {
|
||||
.da-dropdown {
|
||||
&-content {
|
||||
top: calc(var(--dropdown-menu-height) + var(--dropdown-menu-height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 固定至顶 */
|
||||
&.is-fixed {
|
||||
z-index: 980;
|
||||
|
||||
.da-dropdown {
|
||||
&-search {
|
||||
position: fixed;
|
||||
top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px));
|
||||
right: 0;
|
||||
left: 0;
|
||||
max-width: 1190px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&-menu {
|
||||
position: fixed;
|
||||
top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px));
|
||||
right: 0;
|
||||
left: 0;
|
||||
max-width: 1190px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&-content {
|
||||
position: fixed;
|
||||
top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px));
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-search {
|
||||
.da-dropdown {
|
||||
&-menu {
|
||||
top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px));
|
||||
}
|
||||
|
||||
&-content {
|
||||
top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px) + var(--dropdown-menu-height, 0px));
|
||||
}
|
||||
|
||||
&--blank {
|
||||
height: calc(var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px) + var(--dropdown-menu-height, 0px));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes RunLoading {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes CustomBS {
|
||||
0% {
|
||||
box-shadow: 0 -1rpx 0 0 #bbb;
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 -1rpx 0 0 #bbb, 0 20rpx 20rpx -10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
443
src/components/da-dropdown/readme.md
Normal file
443
src/components/da-dropdown/readme.md
Normal file
@ -0,0 +1,443 @@
|
||||
# da-dropdown
|
||||
|
||||
一个基于 Vue3 的头部导航栏下拉弹窗组件,多平台兼容。
|
||||
|
||||
组件一直在更新,遇到问题可在下方讨论。
|
||||
|
||||
`同时更新 Vue2 版本,在此查看 ===>` **[Vue2 版](https://ext.dcloud.net.cn/plugin?id=13062)**
|
||||
|
||||
### 关于使用
|
||||
|
||||
可在右侧的`使用 HBuilderX 导入插件`或`下载示例项目ZIP`,示例项目已添加多个示例,方便快速上手。
|
||||
|
||||
可通过下方的示例及文档说明,进一步了解使用组件相关细节参数。
|
||||
|
||||
插件地址:https://ext.dcloud.net.cn/plugin?id=11840
|
||||
|
||||
### 功能一览
|
||||
|
||||
1. 下拉列表(单选)
|
||||
2. 点击常亮
|
||||
3. 点击排序
|
||||
4. 下拉筛选(单选按钮、多选按钮、滑动选择器)
|
||||
5. 级联筛选(单选)
|
||||
6. 日期筛选(日期快选、日期区间选择)
|
||||
7. 顶部搜索
|
||||
8. 自定插槽
|
||||
|
||||
### 组件示例
|
||||
|
||||
```jsx
|
||||
<template>
|
||||
<DaDropdown
|
||||
:dropdownMenu="dropdownMenuList"
|
||||
themeColor="#007aff"
|
||||
textColor="#333333"
|
||||
:duration="300"
|
||||
fixedTop
|
||||
@confirm="handleConfirm"
|
||||
@close="handleClose"
|
||||
@open="handleOpen">
|
||||
<template #slot1="{item,index}">
|
||||
<view style="padding: 40px">自定义插槽内容</view>
|
||||
</template>
|
||||
</DaDropdown>
|
||||
</template>
|
||||
```
|
||||
|
||||
```js
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
import DaDropdown from '@/components/da-dropdown/index.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { DaDropdown },
|
||||
setup() {
|
||||
const dropdownMenuList = ref([
|
||||
// 演示数据请看下方各模块说明或下载示例项目查看
|
||||
// ...
|
||||
])
|
||||
function handleConfirm(v) {
|
||||
console.log('handleConfirm ==>', v)
|
||||
}
|
||||
function handleClose(v) {
|
||||
console.log('handleClose ==>', v)
|
||||
}
|
||||
function handleOpen(v) {
|
||||
console.log('handleOpen ==>', v)
|
||||
}
|
||||
return {
|
||||
dropdownMenuList,
|
||||
handleConfirm,
|
||||
handleClose,
|
||||
handleOpen,
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 组件参数
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :------------------- | :-------- | :-------- | :--- | :--------------------------------- |
|
||||
| v-model:dropdownMenu | `Array` | `[]` | 是 | 导航菜单数据 |
|
||||
| themeColor | `String` | `#007aff` | 否 | 主题颜色 |
|
||||
| textColor | `String` | `#333333` | 否 | 导航文字颜色 |
|
||||
| bgColor | `String` | `#ffffff` | 否 | 背景颜色,当固定在顶部时,此为必填 |
|
||||
| fixedTop | `Boolean` | `false` | 否 | 是否固定在顶部 |
|
||||
| fixedTopValue | `Number` | `0` | 否 | 固定在头部时的位置,单位 px |
|
||||
| duration | `Number` | `300` | 否 | 弹窗动画的过渡时间 |
|
||||
|
||||
> 温馨提示:如果页面定义了 "navigationStyle": "custom" ,因此固定头部时需要额外获取状态栏高度,以免被异形屏头部覆盖,此时的 fixedTopValue 的作用就出来了,通过 fixedTopValue 自定义加减固定头部所处的位置。
|
||||
|
||||
|
||||
### 组件事件
|
||||
|
||||
| 事件名称 | 回调参数 | 说明 |
|
||||
| :------- | :------------------------- | :----------------------------------------------------------------- |
|
||||
| open | `(index) => void` | 打开弹窗时回调 |
|
||||
| close | `(index,menuList) => void` | 关闭弹窗时回调 |
|
||||
| confirm | `(value,data) => void` | 确定选择内容时回调,返回选择的数据,格式`{'菜单项prop值': '内容'}` |
|
||||
|
||||
|
||||
### 组件方法
|
||||
|
||||
| 事件名称 | 回调参数 | 说明 |
|
||||
| :---------------- | :------------------------- | :-------------------------------------- |
|
||||
| openMenuItemPopup | `(index) => void` | 打开指定位置的菜单项弹窗 |
|
||||
| closeMenuPopup | `() => void` | 关闭菜单项弹窗 |
|
||||
| getMenuValue | `() => object` | 获取菜单存在的值 |
|
||||
| updateMenu | `(prop,value,key) => void` | 更新菜单项内容【参考示例7】 |
|
||||
| setMenuLoading | `(prop,state) => void` | 操作指定菜单项为加载中状态【参考示例7】 |
|
||||
| getMenuIndex | `(prop) => number` | 获取菜单项所在索引位置 |
|
||||
| getMenuList | `() => array` | 获取当前菜单列表数据【参考示例6】 |
|
||||
|
||||
|
||||
### 组件菜单项
|
||||
|
||||
#### dropdownMenu 基础参数
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :---------- | :--------- | :----- | :--- | :--------------------------------------------------------------- |
|
||||
| title | `String` | - | 是 | 菜单名称 |
|
||||
| prop | `String` | - | 是 | 菜单 prop 值,**菜单项的 prop 是唯一的** |
|
||||
| type | `String` | - | 是 | 菜单类型,参考下方类型说明 |
|
||||
| syncDataFn | `Function` | - | 否 | 异步函数返回子项数据,优先级大于 options |
|
||||
| syncDataKey | `String` | - | 否 | 异步数据不是根数据时需要。支持嵌套,如:`data.list`【参考示例7】 |
|
||||
|
||||
除上方基础参数以外,不同的菜单项(type)会有额外的配置参数
|
||||
|
||||
**type 说明**
|
||||
**cell** 下拉列表
|
||||
**click** 点击
|
||||
**sort** 排序
|
||||
**filter** 复杂筛选
|
||||
**picker** 级联
|
||||
**daterange** 日期范围
|
||||
**search** 搜索框(菜单项 type 唯一)
|
||||
**slot** 弹窗插槽
|
||||
|
||||
#### 菜单项 - 下拉列表(cell)
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :------- | :----------------- | :----------------------------------------------------- | :--- | :----------------------------------------- |
|
||||
| value | `Number`\|`String` | - | 否 | 默认值,和`options`的 value 必须保持同类型 |
|
||||
| showAll | `Boolean` | `false` | 否 | 是否显示 “不限” 项 |
|
||||
| showIcon | `Boolean` | `false` | 否 | 是否在选中时显示勾选图标 |
|
||||
| field | `Object` | `{ label: 'label', value: 'value', suffix: 'suffix' }` | 否 | 列表子项数据对应内容字段 |
|
||||
| options | `Array` | `[]` | 否 | 下拉列表子项数据 |
|
||||
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '下拉',
|
||||
type: 'cell',
|
||||
prop: 'god1',
|
||||
showAll: true,
|
||||
showIcon: true,
|
||||
// value: '2', // 默认内容2
|
||||
options: [
|
||||
{ label: '下拉列表项1', value: '1', suffix: '副标题' },
|
||||
{ label: '下拉列表项2', value: '2' },
|
||||
{ label: '下拉列表项3', value: '3' },
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 高亮(click)
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :---- | :-------- | :----- | :--- | :-------------------------------- |
|
||||
| value | `Boolean` | - | 否 | 默认值,true 选中、false 取消选中 |
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '点击',
|
||||
type: 'click',
|
||||
prop: 'god2',
|
||||
// value: true, // 默认选中
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 排序(sort)
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :---- | :------------ | :----- | :--- | :-------------------------- |
|
||||
| value | `asc`\|`desc` | - | 否 | 默认值,asc 升序、desc 倒序 |
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '排序',
|
||||
type: 'sort',
|
||||
prop: 'god3',
|
||||
// value: 'asc', // 默认升序
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 筛选(filter)
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :------ | :------- | :----- | :--- | :------------------------------------------- |
|
||||
| value | `Object` | - | 否 | 默认值,格式`{ prop1: '值1', prop2: '值2' }` |
|
||||
| options | `Array` | `[]` | 否 | 筛选子项数据,**说明见下** |
|
||||
|
||||
##### filter -> options 参数说明
|
||||
|
||||
| 属性 | 类型 | 必填 | 说明 |
|
||||
| :------------- | :---------------------------- | :--- | :-------------------------------------------------------------------------------------------- |
|
||||
| title | `String` | 是 | 筛选项的子项标题 |
|
||||
| type | `radio`\|`checkbox`\|`slider` | 是 | 筛选项的子项类型,可选 radio 单选按钮、checkbox 多选按钮、slider 滑动选择器 |
|
||||
| prop | `String` | 是 | 筛选项的子项 prop,**注意保持子项 prop 唯一** |
|
||||
| componentProps | `Object` | 否 | 筛选项的对应的组件配置,[slider 组件配置](https://uniapp.dcloud.net.cn/component/slider.html) |
|
||||
| options | `Array` | 否 | 筛选子项的类型对应的数据 |
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '筛选',
|
||||
type: 'filter',
|
||||
prop: 'god4',
|
||||
// 默认选中单选2、多选2、3、滑动30
|
||||
// value: { ft1: '2', ft2: ['2', '3'], ft3: 30 },
|
||||
options: [
|
||||
{
|
||||
title: '单选',
|
||||
type: 'radio',
|
||||
prop: 'ft1',
|
||||
options: [
|
||||
{ label: '单选1', value: '1' },
|
||||
{ label: '单选2', value: '2' }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '多选',
|
||||
type: 'checkbox',
|
||||
prop: 'ft2',
|
||||
options: [
|
||||
{ label: '多选1', value: '1' },
|
||||
{ label: '多选2', value: '2' }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '滑块',
|
||||
type: 'slider',
|
||||
prop: 'ft3',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
showValue: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 级联(picker)
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :---------- | :--------- | :--------------------------------------------------------- | :--- | :--------------------------------------------------------------- |
|
||||
| value | `Array` | - | 否 | 默认值,格式`['一级value', '二级value']` |
|
||||
| showAll | `Boolean` | `false` | 否 | 是否显示 “不限” 项 |
|
||||
| showIcon | `Boolean` | `false` | 否 | 是否在选中末级时显示勾选图标 |
|
||||
| field | `Object` | `{ label: 'label', value: 'value', children: 'children' }` | 否 | 级联子项数据对应内容字段 |
|
||||
| options | `Array` | `[]` | 否 | 级联子项数据 |
|
||||
| syncDataFn | `Function` | - | 否 | 异步函数返回级联子项数据,优先级大于 options |
|
||||
| syncDataKey | `String` | - | 否 | 异步数据不是根数据时需要。支持嵌套,如:`data.list`【参考示例7】 |
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '级联选择',
|
||||
type: 'picker',
|
||||
prop: 'god5',
|
||||
showAll: true,
|
||||
showIcon: true,
|
||||
// showAll 为true时相当于在options第一的位置插入“不限”项
|
||||
// { label: '不限', value: '-9999' },
|
||||
field: {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
// value: ['2', '22'], // 默认选中 级联X22
|
||||
options: [
|
||||
{
|
||||
label: '级联X1',
|
||||
value: '1',
|
||||
children: [
|
||||
{ label: '级联X11', value: '11' },
|
||||
{ label: '级联X12', value: '12' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '级联X2',
|
||||
value: '2',
|
||||
children: [
|
||||
{ label: '级联X21', value: '21' },
|
||||
{ label: '级联X22', value: '22' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 日期(daterange)
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :-------- | :-------- | :----- | :--- | :--------------------------------------------------- |
|
||||
| value | `Object` | - | 否 | 默认值,格式`{ start: '开始日期', end: '结束日期' }` |
|
||||
| showQuick | `Boolean` | `true` | 否 | 是否显示日期快选 |
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '日期范围',
|
||||
type: 'daterange',
|
||||
prop: 'god6',
|
||||
// 默认选中 2022-01-01到2022-02-01
|
||||
// value: { start: '2022-01-01', end: '2022-02-01' },
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 顶部搜索框(search)
|
||||
|
||||
|
||||
当存在此类型时,头部将会展示搜索框,**注意:此类型唯一**
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :---- | :------- | :----- | :--- | :----- |
|
||||
| value | `String` | - | 否 | 默认值 |
|
||||
|
||||
```js
|
||||
// 简单示例
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '搜索',
|
||||
type: 'search',
|
||||
prop: 'god0',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单项 - 拓展插槽(slot1、slot2、slot3、slot4、slot5)
|
||||
|
||||
拓展插槽有 5 个,足以应付业务需求了,类型名称为`slot1`、`slot2`、`slot3`、`slot4`、`slot5`,这是固定的类型值
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| :---- | :------- | :----- | :--- | :----- |
|
||||
| value | `String` | - | 否 | 默认值 |
|
||||
|
||||
```jsx
|
||||
// 简单示例
|
||||
<template>
|
||||
<DaDropdown>
|
||||
<template #slot1="{item,index}">
|
||||
<view>自定义插槽2内容 {{item.value}} {{index}}</view>
|
||||
</template>
|
||||
<template #slot2="{item,index}">
|
||||
<view>自定义插槽2内容 {{item.value}} {{index}}</view>
|
||||
</template>
|
||||
<template #slot3="{item,index}">
|
||||
<view>自定义插槽3内容 {{item.value}} {{index}}</view>
|
||||
</template>
|
||||
<template #slot4="{item,index}">
|
||||
<view>自定义插槽4内容 {{item.value}} {{index}}</view>
|
||||
</template>
|
||||
<template #slot5="{item,index}">
|
||||
<view>自定义插槽5内容 {{item.value}} {{index}}</view>
|
||||
</template>
|
||||
</DaDropdown>
|
||||
</template>
|
||||
```
|
||||
|
||||
```js
|
||||
const dropdownMenuList = [
|
||||
{
|
||||
title: '插槽1',
|
||||
type: 'slot1',
|
||||
prop: 'god1',
|
||||
},
|
||||
{
|
||||
title: '插槽2',
|
||||
type: 'slot2',
|
||||
prop: 'god2',
|
||||
},
|
||||
{
|
||||
title: '插槽3',
|
||||
type: 'slot3',
|
||||
prop: 'god3',
|
||||
},
|
||||
{
|
||||
title: '插槽4',
|
||||
type: 'slot4',
|
||||
prop: 'god4',
|
||||
},
|
||||
{
|
||||
title: '插槽5',
|
||||
type: 'slot5',
|
||||
prop: 'god5',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### 组件版本
|
||||
|
||||
v2.2.2
|
||||
|
||||
### 差异化
|
||||
|
||||
已通过测试
|
||||
|
||||
> - H5 页面
|
||||
> - 微信小程序
|
||||
> - 支付宝、钉钉小程序
|
||||
> - 字节跳动、抖音、今日头条小程序
|
||||
> - 百度小程序
|
||||
> - 飞书小程序
|
||||
> - QQ 小程序
|
||||
> - 京东小程序
|
||||
|
||||
未测试
|
||||
|
||||
> - 快手小程序由于非企业用户暂无演示
|
||||
> - 快应用、360 小程序因 Vue3 支持的原因暂无演示
|
||||
|
||||
### 开发组
|
||||
|
||||
[@CRLANG](https://crlang.com)
|
151
src/components/da-dropdown/typing.ts
Normal file
151
src/components/da-dropdown/typing.ts
Normal file
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 菜单项-下拉配置
|
||||
*/
|
||||
export interface DaCellOption {
|
||||
/**
|
||||
* 是否显示“不限”选项
|
||||
*/
|
||||
showAll?: boolean
|
||||
/**
|
||||
* 是否显示勾选图标
|
||||
*/
|
||||
showIcon?: boolean
|
||||
}
|
||||
/**
|
||||
* 菜单项-点击配置
|
||||
*/
|
||||
export interface DaClickOption {}
|
||||
/**
|
||||
* 菜单项-排序配置
|
||||
*/
|
||||
export interface DaSortOption {}
|
||||
/**
|
||||
* 菜单项-筛选配置
|
||||
*/
|
||||
export interface DaFilterOption {}
|
||||
/**
|
||||
* 菜单项-级联配置
|
||||
*/
|
||||
export interface DaPickerOption {
|
||||
/**
|
||||
* 是否显示“不限”选项
|
||||
*/
|
||||
showAll?: boolean
|
||||
/**
|
||||
* 是否显示勾选图标
|
||||
*/
|
||||
showIcon?: boolean
|
||||
field?: {
|
||||
label: string
|
||||
value: string
|
||||
children: string
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 菜单项-日期范围配置
|
||||
*/
|
||||
export interface DaDaterangeOption {
|
||||
value?: {
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 下拉列表-项内容
|
||||
*/
|
||||
export interface DaCellItemOption extends DaDropdownMenuListOption {
|
||||
/**
|
||||
* 右侧子标题
|
||||
*/
|
||||
suffix?: string
|
||||
}
|
||||
/**
|
||||
* 筛选-项内容
|
||||
*/
|
||||
export interface DaFilterItemOption {
|
||||
/**
|
||||
* 筛选标题
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* 筛选类型,可选 radio 单选按钮、checkbox 多选按钮、slider 滑动选择器
|
||||
*/
|
||||
type: 'radio' | 'checkbox' | 'slider'
|
||||
/**
|
||||
* 筛选项prop
|
||||
*/
|
||||
prop: string
|
||||
/**
|
||||
* 已选内容
|
||||
*/
|
||||
value?: string | number | string[] | number[]
|
||||
/**
|
||||
* 筛选项-slider子项组件prop,具体参考 https://uniapp.dcloud.net.cn/component/slider.html
|
||||
*/
|
||||
componentProp?: object
|
||||
/**
|
||||
* 筛选项-子项
|
||||
*/
|
||||
options?: DaDropdownMenuListOption[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 级联-项内容
|
||||
*/
|
||||
export interface DaPickerItem extends DaDropdownMenuListOption {
|
||||
isActived: boolean
|
||||
/**
|
||||
* 子项
|
||||
*/
|
||||
children?: DaPickerItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单列表选择项
|
||||
*/
|
||||
export interface DaDropdownMenuListOption {
|
||||
/**
|
||||
* 选择项标题
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* 选择项内容
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单项
|
||||
*/
|
||||
export interface DaDropdownMenuListItem extends DaCellOption, DaClickOption, DaSortOption, DaFilterOption, DaPickerOption {
|
||||
/**
|
||||
* 菜单标题
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* 菜单类型
|
||||
* 可选:cell 下拉选择、click 点击、sort 排序、filter 复杂筛选、picker 级联、daterange 日期范围
|
||||
*/
|
||||
type: 'cell' |'click' | 'sort' | 'filter' | 'picker'| 'daterange'
|
||||
/**
|
||||
* 菜单项prop
|
||||
*/
|
||||
prop: string
|
||||
/**
|
||||
* 菜单值
|
||||
*/
|
||||
value?: string
|
||||
/**
|
||||
* 菜单选项函数,优先级大于 options
|
||||
*/
|
||||
syncDataFn?: Function
|
||||
/**
|
||||
* 菜单选项
|
||||
*/
|
||||
options?: DaDropdownMenuListOption[] | DaFilterItemOption[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单列表
|
||||
*/
|
||||
export type DaDropdownMenuList = DaDropdownMenuListItem[]
|
207
src/components/da-dropdown/utils.ts
Normal file
207
src/components/da-dropdown/utils.ts
Normal file
@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 深拷贝内容
|
||||
* @param originData 拷贝对象
|
||||
* @author crlang(https://crlang.com)
|
||||
*/
|
||||
export function deepClone(originData) {
|
||||
const type = Object.prototype.toString.call(originData)
|
||||
let data
|
||||
if (type === '[object Array]') {
|
||||
data = []
|
||||
for (let i = 0; i < originData.length; i++) {
|
||||
data.push(deepClone(originData[i]))
|
||||
}
|
||||
} else if (type === '[object Object]') {
|
||||
data = {}
|
||||
for (const prop in originData) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (originData.hasOwnProperty(prop)) { // 非继承属性
|
||||
data[prop] = deepClone(originData[prop])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = originData
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export function getValueByKey(object, path, defaultVal = undefined) {
|
||||
console.log('object, path', object, path)
|
||||
// 先将path处理成统一格式
|
||||
let newPath = []
|
||||
if (Array.isArray(path)) {
|
||||
newPath = path
|
||||
} else {
|
||||
// 先将字符串中的'['、']'去除替换为'.',split分割成数组形式
|
||||
newPath = path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
|
||||
}
|
||||
|
||||
// 递归处理,返回最后结果
|
||||
return newPath.reduce((o, k) => {
|
||||
console.log(o, k) // 此处o初始值为下边传入的 object,后续值为每次取的内部值
|
||||
return (o || {})[k]
|
||||
}, object) || defaultVal
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理部分初始数据
|
||||
* @param data
|
||||
*/
|
||||
export function checkDataField(options, fields) {
|
||||
if (!fields || !options || options.length === 0) {
|
||||
return options
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const k = options[i]
|
||||
k.label = k[fields.label || 'label'] || null
|
||||
k.value = k[fields.value || 'value'] || null
|
||||
k.suffix = k[fields.suffix || 'suffix'] || null
|
||||
k.children = k[fields.children || 'children'] || null
|
||||
if (k.children?.length) {
|
||||
k.options = checkDataField(k.options)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数值-个位数补零
|
||||
* @param n 数值
|
||||
* @author crlang(https://crlang.com)
|
||||
*/
|
||||
export function formatNumber(n) {
|
||||
let s = parseInt(n)
|
||||
if (isNaN(s)) {
|
||||
s = '0'
|
||||
} else {
|
||||
s = s.toString()
|
||||
}
|
||||
return s[1] ? s : `0${s}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param date 时间对象
|
||||
* @param format 格式
|
||||
* @author crlang(https://crlang.com)
|
||||
*/
|
||||
export function formatTime(date, format) {
|
||||
const daDate = new Date(date.toString().length < 11 ? date * 1000 : date)
|
||||
const fromatsRule = ['y', 'm', 'd', 'h', 'i', 's']
|
||||
let tmp = []
|
||||
const year = daDate.getFullYear()
|
||||
const month = daDate.getMonth() + 1
|
||||
const day = daDate.getDate()
|
||||
const hour = daDate.getHours()
|
||||
const minute = daDate.getMinutes()
|
||||
const second = daDate.getSeconds()
|
||||
|
||||
if (format) {
|
||||
tmp.push(year, month, day, hour, minute, second)
|
||||
tmp = tmp.map(formatNumber)
|
||||
for (let i = 0; i < tmp.length; i++) {
|
||||
format = format.toLowerCase().replace(fromatsRule[i], tmp[i])
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某个时间范围
|
||||
*
|
||||
* @param v -1、-7、-14、-30、-60
|
||||
* @returns object {start: y-m-d,end: y-m-d}
|
||||
* @author crlang(https://crlang.com)
|
||||
*/
|
||||
export function getRangeDate(v) {
|
||||
const now = new Date()
|
||||
const nowTime = now.getTime()
|
||||
const oneDay = 24 * 60 * 60 * 1000
|
||||
const dateRange = { start: '', end: '' }
|
||||
const nowWeekDay = now.getDay() // 今天本周的第几天
|
||||
const nowDay = now.getDate() // 当前日
|
||||
const nowMonth = now.getMonth() // 当前月
|
||||
const nowYear = now.getFullYear() // 当前年
|
||||
|
||||
/**
|
||||
* 获得某个月的天数
|
||||
* @param month 当前月份
|
||||
*/
|
||||
const getMonthDays = function(month) {
|
||||
const monthStartDate = new Date(nowYear, month, 1)
|
||||
const monthEndDate = new Date(nowYear, month + 1, 1)
|
||||
const days = (monthEndDate - monthStartDate) / oneDay
|
||||
return days
|
||||
}
|
||||
|
||||
// 昨日
|
||||
if (v === '-1') {
|
||||
dateRange.start = formatTime(new Date(nowTime - oneDay), 'y-m-d')
|
||||
dateRange.end = dateRange.start
|
||||
// 本周
|
||||
} else if (v === '-7') {
|
||||
const weekStart = new Date(nowYear, nowMonth, nowDay - nowWeekDay + 1)
|
||||
const weekEnd = new Date(nowTime + oneDay) // 今日
|
||||
dateRange.start = formatTime(weekStart, 'y-m-d')
|
||||
dateRange.end = formatTime(weekEnd, 'y-m-d')
|
||||
// 上周
|
||||
} else if (v === '-14') {
|
||||
const weekStart = new Date(nowYear, nowMonth, nowDay - nowWeekDay - 6)
|
||||
const weekEnd = new Date(nowYear, nowMonth, nowDay - nowWeekDay)
|
||||
dateRange.start = formatTime(weekStart, 'y-m-d')
|
||||
dateRange.end = formatTime(weekEnd, 'y-m-d')
|
||||
// 本月
|
||||
} else if (v === '-30') {
|
||||
const monthStart = new Date(nowYear, nowMonth, 1)
|
||||
const monthEnd = new Date(nowTime + oneDay)
|
||||
dateRange.start = formatTime(monthStart, 'y-m-d')
|
||||
dateRange.end = formatTime(monthEnd, 'y-m-d')
|
||||
// 上月
|
||||
} else if (v === '-60') {
|
||||
const lastMonthDate = new Date() // 上月日期
|
||||
lastMonthDate.setDate(1)
|
||||
lastMonthDate.setMonth(lastMonthDate.getMonth() - 1)
|
||||
const lastMonth = lastMonthDate.getMonth()
|
||||
const lastMonthStart = new Date(nowMonth === 0 ? nowYear - 1 : nowYear, lastMonth, 1)
|
||||
const lastMonthEnd = new Date(nowMonth === 0 ? nowYear - 1 : nowYear, lastMonth, getMonthDays(lastMonth))
|
||||
dateRange.start = formatTime(lastMonthStart, 'y-m-d')
|
||||
dateRange.end = formatTime(lastMonthEnd, 'y-m-d')
|
||||
} else {
|
||||
// 传入 v 为整数是即为近 xx 天
|
||||
if (v > 0) {
|
||||
dateRange.start = formatTime(new Date(nowTime - oneDay * parseInt(v)), 'y-m-d')
|
||||
dateRange.end = formatTime(new Date(nowTime - oneDay), 'y-m-d') // 不含今天
|
||||
}
|
||||
}
|
||||
return dateRange
|
||||
}
|
||||
|
||||
export const menuInitOpts = {
|
||||
cell: {
|
||||
showArrow: true,
|
||||
},
|
||||
click: {
|
||||
},
|
||||
sort: {
|
||||
showSort: true,
|
||||
},
|
||||
filter: {
|
||||
showArrow: true,
|
||||
},
|
||||
picker: {
|
||||
showArrow: true,
|
||||
},
|
||||
daterange: {
|
||||
showQuick: true,
|
||||
showArrow: true,
|
||||
},
|
||||
slot: {
|
||||
showArrow: true,
|
||||
},
|
||||
search: {
|
||||
showSearch: true,
|
||||
},
|
||||
}
|
344
src/components/hpy-watermark.vue
Normal file
344
src/components/hpy-watermark.vue
Normal file
@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<view class="points">
|
||||
<view class="info-card">
|
||||
<view class="info-box">
|
||||
<view class="info-item" v-for="(item, index) in infoData" :key="index">
|
||||
<view><text class="grey">{{ item.key }}:</text>{{ item.value }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view
|
||||
scroll-x="true"
|
||||
class="content-scroll"
|
||||
:show-scrollbar="false"
|
||||
enable-flex
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in positionData"
|
||||
:key="index"
|
||||
:class="navCurIndex == index ? 'active' : ''"
|
||||
class="control-item"
|
||||
@click="changeTitle(index)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="collapse-box">
|
||||
<uni-collapse ref="collapseRef" v-model="collapseValue" accordion @change="changeCollapse">
|
||||
<uni-collapse-item v-for="(val, collapseIndex) in collapseData" :key="collapseIndex">
|
||||
<template v-slot:title>
|
||||
<uni-section class="section" :title="val.title" type="line"></uni-section>
|
||||
</template>
|
||||
<view class="content">
|
||||
<view class="content-item" v-for="(item, itemIndex) in val.children" :key="itemIndex">
|
||||
<view class="title">
|
||||
<view>{{ item.title }}</view>
|
||||
<uni-tag :inverted="true" :text="item.tag" :type="item.tag == '重点关注' ? 'error' : 'warning'" />
|
||||
</view>
|
||||
<view class="btn">
|
||||
<button size="mini" type="primary" @click="standard(item.standard)">巡检标准</button>
|
||||
<button size="mini" type="primary" @click="history(item.history)">巡检历史</button>
|
||||
</view>
|
||||
<view :class="item.selectedImg ? '' : 'input'" v-if="item.tag">
|
||||
<uni-easyinput class="textarea" type="textarea" v-model="item.described" placeholder="请输入" />
|
||||
<uni-file-picker class="picker" mode="grid" :sourceType="['camera']" file-mediatype="image" limit="9" v-model="item.imageValue" @progress="progress" @select="(e) => selectImg(item, e, collapseIndex, itemIndex)" @delete="deleteImg(item, $event)"><uni-icons type="camera-filled" color="#004894" size="50"></uni-icons></uni-file-picker>
|
||||
<!-- <WaterMarker :ref="el => { if (el) waterMarkRefs[`waterMarkRef_${collapseIndex}_${itemIndex}`] = el }"/> -->
|
||||
<hpy-watermark :ref="el => { if (el) waterMarkRefs[`waterMarkRef_${collapseIndex}_${itemIndex}`] = el }" @waterMark="(path) => waterMark(item, path)"></hpy-watermark>
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="state"><text>状态:</text><uni-data-checkbox class="checkbox" mode="button" v-model="item.radio" :localdata="localdata"></uni-data-checkbox></view>
|
||||
<view v-if="item.radio != 0">
|
||||
<view class="described">描述:<uni-easyinput class="textarea" type="textarea" v-model="item.described" placeholder="请输入" /></view>
|
||||
<view>照片:<uni-file-picker class="picker" mode="grid" :sourceType="['camera']" file-mediatype="image" limit="9" v-model="item.imageValue" @select="selectImg(item,$event)" @delete="deleteImg(item, $event)"><uni-icons type="camera-filled" color="#004894" size="50"></uni-icons></uni-file-picker></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-collapse-item>
|
||||
</uni-collapse>
|
||||
</view>
|
||||
<uni-popup ref="alertDialog" type="dialog">
|
||||
<uni-popup-dialog type="warning" cancelText="关闭" confirmText="同意" title="通知" content="欢迎使用 uni-popup!" @confirm="dialogConfirm"
|
||||
@close="dialogClose"></uni-popup-dialog>
|
||||
</uni-popup>
|
||||
<uni-popup ref="popup" background-color="#fff" @change="changePopup">
|
||||
<view class="popup-content"><text class="text">{{ popValue }}</text></view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, getCurrentInstance } from 'vue';
|
||||
import ImageWatermarkPicker from "@/components/image-watermark-picker.vue"
|
||||
import WaterMarker from '@/components/waterMarker.vue'
|
||||
import HpyWatermark from '@/uni_modules/hpy-watermark/components/hpy-watermark/hpy-watermark.vue'
|
||||
|
||||
let waterMarkRefs = reactive({})
|
||||
|
||||
// 弹出框内容
|
||||
let popValue = ref(null)
|
||||
let alertDialog = ref(null)
|
||||
|
||||
let navCurIndex = ref(0)
|
||||
// 详情信息
|
||||
let infoData = reactive([
|
||||
{ key: '点位名称', value: '厂外0.4KV供电点' },
|
||||
{ key: '点位编码', value: '' },
|
||||
])
|
||||
|
||||
// 标题信息
|
||||
let positionData = reactive([
|
||||
{ id: 1, title: "左岸" },
|
||||
{ id: 2, title: "右岸" },
|
||||
{ id: 3, title: "大坝" }
|
||||
])
|
||||
let collapseRef = ref(null)
|
||||
// 手风琴折叠面板默认展开内容
|
||||
let collapseValue = ref('0')
|
||||
// 手风琴数据
|
||||
let collapseData = reactive([
|
||||
{ id: 1, title: '左岸进水口配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 2, title: '左岸排风竖井配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 3, title: '左岸尾水管配电系统(南端)', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 4, title: '左岸尾水管配电系统(北端)', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 5, title: '左岸尾水洞出口配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 6, title: '左岸水垫塘渗漏排水配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 7, title: '左岸水垫塘检修排水配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 8, title: '控制管理楼及左岸出线场配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
])
|
||||
|
||||
// 单选数据
|
||||
let localdata = reactive([
|
||||
{ text: '正常', value: 0 },
|
||||
{ text: '待观察', value: 1 },
|
||||
{ text: '异常', value: 2 }
|
||||
])
|
||||
|
||||
// 切换标题
|
||||
const changeTitle = (index) => {
|
||||
navCurIndex.value = index
|
||||
}
|
||||
|
||||
// 切换手风琴面板
|
||||
const changeCollapse = () => {
|
||||
// alertDialog.value.open()
|
||||
}
|
||||
|
||||
// 弹出框
|
||||
const changePopup = () => {
|
||||
|
||||
}
|
||||
|
||||
// 确认
|
||||
const dialogConfirm = () => {
|
||||
|
||||
}
|
||||
|
||||
// 取消
|
||||
const dialogClose = () => {
|
||||
|
||||
}
|
||||
|
||||
const waterMark = (item, path) => {
|
||||
console.log(path);
|
||||
item.imageValue.push({ url: path });
|
||||
console.log(item.imageValue);
|
||||
};
|
||||
|
||||
// 选择图片
|
||||
const selectImg = (item, e, collapseIndex, itemIndex) => {
|
||||
item.selectedImg = true
|
||||
// 获取对应的ref key
|
||||
const refKey = `waterMarkRef_${collapseIndex}_${itemIndex}`
|
||||
const waterMarkerInstance = waterMarkRefs[refKey]
|
||||
console.log(waterMarkerInstance);
|
||||
|
||||
if (waterMarkerInstance) {
|
||||
// const imgFileArr = waterMarkerInstance.callAddWaterMark(e.tempFilePaths);
|
||||
// imgFileArr.forEach((el) => {
|
||||
// item.imageValue.push({
|
||||
// url: el,
|
||||
// extname: el.substring(el.lastIndexOf(".") + 1),
|
||||
// name: el,
|
||||
// });
|
||||
// })
|
||||
console.log(waterMarkerInstance);
|
||||
|
||||
var fillTexts = ["人员:张三", "地址:广东省珠海市香洲区XXX"];
|
||||
fillTexts.push("时间:");
|
||||
// 添加水印
|
||||
waterMarkerInstance.addWaterMark({
|
||||
filePaths: e.tempFilePaths,
|
||||
fillTexts
|
||||
});
|
||||
} else {
|
||||
console.warn('未找到对应的WaterMarker实例或方法未暴露', refKey, waterMarkerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const progress = (item, index) => {
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: item.map((o) => o.url),
|
||||
});
|
||||
}
|
||||
|
||||
// 删除选择的图片
|
||||
const deleteImg = (item,e) => {
|
||||
item.imageValue.splice(e.index, 1)
|
||||
console.log(item, e)
|
||||
|
||||
if (item.imageValue.length == 0) {
|
||||
item.selectedImg = false
|
||||
}
|
||||
}
|
||||
|
||||
// 巡检标准
|
||||
const standard = (val) => {
|
||||
popup.value.open()
|
||||
popValue.value = val
|
||||
}
|
||||
|
||||
// 巡检历史
|
||||
const history = (val) => {
|
||||
popup.value.open()
|
||||
popValue.value = val
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.points {
|
||||
.content-scroll {
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
white-space: nowrap;
|
||||
.control-item {
|
||||
width: 160rpx;
|
||||
height: 35rpx;
|
||||
line-height: 35rpx;
|
||||
display: inline-block;
|
||||
padding: 0 20rpx;
|
||||
margin-left: 30rpx;
|
||||
position: relative;
|
||||
&.active {
|
||||
padding: 15rpx 20rpx!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse-box {
|
||||
margin: 10rpx 30rpx;
|
||||
border-radius: 10rpx;
|
||||
padding: 10rpx;
|
||||
background: #fff;
|
||||
:deep(.uni-icons) {
|
||||
color: #1464BB!important;
|
||||
}
|
||||
}
|
||||
|
||||
.content-item {
|
||||
padding: 20rpx;
|
||||
margin: 0 20rpx 20rpx;
|
||||
background: #F1F7FD;
|
||||
border-radius: 10rpx;
|
||||
.title, .btn, .input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.title {
|
||||
color: #004895;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.btn {
|
||||
:deep(uni-button) {
|
||||
margin:30rpx 20rpx 30rpx 0!important;
|
||||
}
|
||||
}
|
||||
.input {
|
||||
.textarea {
|
||||
width: 400rpx;
|
||||
flex: none!important;
|
||||
}
|
||||
.picker {
|
||||
margin-left: 10rpx;
|
||||
:deep(.uni-file-picker__container) {
|
||||
width: 580rpx;
|
||||
height: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
// .warp {
|
||||
// display: block !important;
|
||||
// :deep(.uni-file-picker__container) {
|
||||
// width: auto !important;
|
||||
// height: auto !important;
|
||||
// }
|
||||
// }
|
||||
.state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.checkbox {
|
||||
:deep(.uni-label-pointer) {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.described {
|
||||
margin: 30rpx 0;
|
||||
}
|
||||
.textarea, .picker {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
275
src/components/image-watermark-picker.vue
Normal file
275
src/components/image-watermark-picker.vue
Normal file
@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<view class="image-picker">
|
||||
<uni-file-picker
|
||||
v-model="imageValue"
|
||||
:auto-upload="false"
|
||||
:title="title"
|
||||
:limit="limit"
|
||||
:image-styles="imageStyles"
|
||||
:file-mediatype="fileMediatype"
|
||||
:mode="mode"
|
||||
:sourceType="['camera']"
|
||||
@select="select"
|
||||
>
|
||||
<view v-if="fileMediatype === 'image'" class="form-item-column-center">
|
||||
<!-- <uni-icons type="image" size="30"></uni-icons>
|
||||
<view :style="{ marginTop: '5px' }">上传照片</view>
|
||||
<view>最多{{ limit }}张</view> -->
|
||||
<uni-icons type="camera-filled" color="#004894" size="50"></uni-icons>
|
||||
</view>
|
||||
</uni-file-picker>
|
||||
<view class="watermark-canvas">
|
||||
<canvas
|
||||
id="watermark-canvas"
|
||||
:style="{ width: canvasWidth, height: canvasHeight }"
|
||||
canvas-id="watermark-canvas"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ImageWatermarkPicker',
|
||||
props: {
|
||||
limit: {
|
||||
type: [Number, String],
|
||||
default: 1,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'grid',
|
||||
},
|
||||
fileMediatype: {
|
||||
type: String,
|
||||
default: 'image',
|
||||
},
|
||||
imageStyles: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
watermark: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
emits: ['input', 'update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
imageValue: [],
|
||||
canvasWidth: '1080px',
|
||||
canvasHeight: '2160px',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imageValue(newVal) {
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:modelValue', newVal)
|
||||
// #endif
|
||||
// #ifndef VUE3
|
||||
this.$emit('input', newVal)
|
||||
// #endif
|
||||
// this.$emit('change', newVal)
|
||||
},
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
handler(newVal) {
|
||||
this.imageValue = newVal
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
this.imageValue = newVal
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
checkImage(url) {
|
||||
const checkNum = 5
|
||||
let currentCheckNum = 1
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
process()
|
||||
function process() {
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: function (image) {
|
||||
resolve(image)
|
||||
},
|
||||
fail: function (err) {
|
||||
if (checkNum <= currentCheckNum) {
|
||||
uni.showToast({ title: '图片上传失败', icon: 'none' })
|
||||
reject(err)
|
||||
} else {
|
||||
currentCheckNum++
|
||||
const timer = setTimeout(() => {
|
||||
clearTimeout(timer)
|
||||
process()
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
async select(e) {
|
||||
for (let tempFile of e.tempFiles) {
|
||||
await this.watermarkProcess(tempFile)
|
||||
}
|
||||
},
|
||||
async watermarkProcess(tempFile) {
|
||||
const { name, size, extname, uuid, path } = tempFile
|
||||
let url = null
|
||||
// 添加水印
|
||||
if (this.watermark) {
|
||||
url = await this.addWatermark(path)
|
||||
}
|
||||
// 上传图片
|
||||
// url = await this.uploadFile(path)
|
||||
// 检测图片,确保图片存在
|
||||
await this.checkImage(url)
|
||||
this.imageValue = [
|
||||
...this.imageValue,
|
||||
{
|
||||
name,
|
||||
extname,
|
||||
url,
|
||||
size,
|
||||
uuid,
|
||||
},
|
||||
]
|
||||
},
|
||||
async addWatermark(tempFilePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src: tempFilePath,
|
||||
success: async (res) => {
|
||||
// 设置画布高度和宽度
|
||||
this.canvasWidth = `${res.width}px`
|
||||
this.canvasHeight = `${res.height}px`
|
||||
await this.sleep(1000) // 某些平台 canvas 渲染慢,需要等待
|
||||
this.$nextTick(() => {
|
||||
const ctx = uni.createCanvasContext('watermark-canvas', this)
|
||||
ctx.clearRect(0, 0, res.width, res.height)
|
||||
ctx.beginPath()
|
||||
ctx.drawImage(tempFilePath, 0, 0, res.width, res.height)
|
||||
|
||||
// 水印 字体大小,颜色,内容,位置
|
||||
ctx.beginPath()
|
||||
ctx.setFontSize(24)
|
||||
ctx.setFillStyle('rgba(250,250,250,0.8)')
|
||||
ctx.fillText('我是水印1', 60, res.height - 90)
|
||||
ctx.fillText('我是水印2', 60, res.height - 60)
|
||||
})
|
||||
// 开始绘制 (canvas -> 临时文件路径)
|
||||
ctx.draw(false, async () => {
|
||||
await this.sleep(1000) // 某些平台 canvas 渲染慢,需要等待
|
||||
|
||||
uni.canvasToTempFilePath(
|
||||
{
|
||||
canvasId: 'watermark-canvas',
|
||||
destWidth: res.width,
|
||||
destHeight: res.height,
|
||||
fileType: 'jpg',
|
||||
quality: 0.8,
|
||||
success: (fileRes) => {
|
||||
resolve(fileRes.tempFilePath)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('[Error draw]', err)
|
||||
uni.showToast({ title: err.errMsg, icon: 'none' })
|
||||
reject()
|
||||
},
|
||||
},
|
||||
this,
|
||||
)
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('[Error getImageInfo]', err)
|
||||
uni.showToast({ title: err.errMsg, icon: 'none' })
|
||||
reject()
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
async uploadFile(path) {
|
||||
const formData = {
|
||||
'params.bizType': 'xxx',
|
||||
'params.tags': 'xxx',
|
||||
'meta.code': 'xxxx',
|
||||
'meta.client': 'uniapp',
|
||||
'meta.tag': 'xxx',
|
||||
'meta.time': new Date().getTime(),
|
||||
}
|
||||
|
||||
const res = await uni.uploadFile({
|
||||
url: `${process.env.VUE_APP_BASE_URL}/file/upload`,
|
||||
filePath: path,
|
||||
name: 'params.files',
|
||||
formData,
|
||||
header: {
|
||||
Authorization: uni.getStorageSync('accessToken'),
|
||||
},
|
||||
})
|
||||
|
||||
return JSON.parse(res[1].data).data.filePaths[0]
|
||||
},
|
||||
sleep(millisecond) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, millisecond)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.image-picker {
|
||||
position: relative;
|
||||
|
||||
.form-item-column-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.watermark-canvas {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
147
src/components/waterMarker.vue
Normal file
147
src/components/waterMarker.vue
Normal file
@ -0,0 +1,147 @@
|
||||
<!--
|
||||
* @Author: XHC
|
||||
* @Date: 2025-06-06 15:36:04
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-06-06 16:38:39
|
||||
* @Description:
|
||||
-->
|
||||
<template>
|
||||
<canvas v-show="waterMarkParams.display" canvas-id="waterMarkCanvas" :style="canvasStyle" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, computed, nextTick, defineExpose } from 'vue';
|
||||
|
||||
let waterMarkParams = reactive({
|
||||
display: false, // 控制 canvas 创建与销毁
|
||||
canvasWidth: 300, // 默认宽度
|
||||
canvasHeight: 225, // 默认高度
|
||||
contentHeight: 170, // 将要被绘制到图像中的矩形的高度(px)
|
||||
})
|
||||
let username= ref("YourUsername") // 假设的用户名
|
||||
// 画布
|
||||
const canvasStyle = computed(() => {
|
||||
return {
|
||||
position: "fixed", // 移除到屏幕外
|
||||
left: "9999px",
|
||||
width: waterMarkParams.canvasWidth + "px",
|
||||
height: waterMarkParams.canvasHeight + "px",
|
||||
}
|
||||
|
||||
})
|
||||
// 因为有可能在相册中选择多个图片,所以这里要依次生成水印
|
||||
async function callAddWaterMark(imgPathArr) {
|
||||
let results = [];
|
||||
if (imgPathArr.length > 0) {
|
||||
let addIndex = 0;
|
||||
while (addIndex < imgPathArr.length) {
|
||||
const tempFilePath = await addWaterMark(imgPathArr[addIndex]);
|
||||
results.push(tempFilePath);
|
||||
addIndex = addIndex + 1;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// 添加水印
|
||||
function addWaterMark(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 获取图片信息,配置 canvas 尺寸
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
// 修复部分手机(如红米9)手机屏幕比较窄拍摄出来的图片水印压缩着覆盖的问题
|
||||
waterMarkParams.canvasWidth = Math.max(res.width, 886);
|
||||
waterMarkParams.canvasHeight = res.height;
|
||||
waterMarkParams.display = true;
|
||||
console.log("当前图片信息waterMarkParams:", waterMarkParams);
|
||||
// 等待 canvas 元素创建
|
||||
nextTick(() => {
|
||||
let context = uni.createCanvasContext("waterMarkCanvas", this);
|
||||
/* 绘制 */
|
||||
const {
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
contentHeight
|
||||
} =
|
||||
waterMarkParams;
|
||||
// 绘制前清空画布
|
||||
context.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
// 将图片src放到cancas内,宽高必须为图片大小
|
||||
context.drawImage(
|
||||
src,
|
||||
0,
|
||||
0,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
canvasWidth,
|
||||
canvasHeight
|
||||
);
|
||||
// 设置边框的透明度
|
||||
context.setGlobalAlpha(0.3);
|
||||
context.beginPath();
|
||||
// 绘制底部的白色背景
|
||||
context.rect(
|
||||
0,
|
||||
canvasHeight - contentHeight,
|
||||
canvasWidth,
|
||||
contentHeight
|
||||
);
|
||||
// context.setFillStyle("white"); // 白色背景
|
||||
context.fill();
|
||||
// 设置文字的透明度
|
||||
context.setGlobalAlpha(1);
|
||||
// 3.绘制底部的文字
|
||||
context.setFontSize(32);
|
||||
context.setTextAlign("left");
|
||||
context.setFillStyle("white"); // 显示字为白色
|
||||
context.fillText(`拍摄人:${username.value}`, 50, canvasHeight -
|
||||
120);
|
||||
context.fillText(
|
||||
`拍摄时间:${new Date()
|
||||
}`,
|
||||
50,
|
||||
canvasHeight - 70
|
||||
);
|
||||
// 一定要加上一个定时器否则进入到页面第一次可能会无法正常拍照,后几次才正常
|
||||
setTimeout(() => {
|
||||
// 本次绘画完重开开始绘画,并且在绘画完毕之后再保存图片,不然页面可能会出现白屏等情况
|
||||
context.draw(false, () => {
|
||||
console.log("!!!!!开始绘画", canvasWidth,
|
||||
canvasHeight);
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: "waterMarkCanvas",
|
||||
fileType: "jpg",
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
destWidth: canvasWidth,
|
||||
destHeight: canvasHeight,
|
||||
success: ({
|
||||
tempFilePath
|
||||
}) => {
|
||||
console.log("绘制成功",
|
||||
tempFilePath
|
||||
);
|
||||
waterMarkParams
|
||||
.display =
|
||||
false;
|
||||
resolve(
|
||||
tempFilePath
|
||||
);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
console.log(err);
|
||||
},
|
||||
},
|
||||
this
|
||||
);
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露方法给父组件调用
|
||||
defineExpose({ callAddWaterMark })
|
||||
</script>
|
20
src/package.json
Normal file
20
src/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "da-dropdown",
|
||||
"name": "da-dropdown 下拉筛选菜单(支持主题色、功能丰富,Vue3版)",
|
||||
"displayName": "da-dropdown 下拉筛选菜单(支持主题色、功能丰富,Vue3版)",
|
||||
"version": "2.2.2",
|
||||
"description": "一个基于 Vue3 的头部导航栏下拉筛选菜单组件,兼容App、H5、微信小程序、支付宝小程序、抖音小程序等。。。",
|
||||
"keywords": [
|
||||
"dropdown",
|
||||
"导航栏",
|
||||
"筛选",
|
||||
"下拉菜单",
|
||||
"da系列"
|
||||
],
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
]
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ const submit = (ref) => {
|
||||
.tips {
|
||||
border-radius: 10rpx;
|
||||
background: #ffffff3f;
|
||||
margin: 30rpx;
|
||||
margin: 0 30rpx 30rpx;
|
||||
padding: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: XHC
|
||||
* @Date: 2025-05-19 11:07:37
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-29 16:55:28
|
||||
* @LastEditTime: 2025-06-06 17:40:38
|
||||
* @Description: 首页
|
||||
-->
|
||||
<template>
|
||||
@ -80,18 +80,21 @@
|
||||
</view>
|
||||
<view class="category-content">
|
||||
<view>
|
||||
<uni-card class="list-card" v-for="(item, index) in listData" :key="index" @click="goToStartInspection(item)">
|
||||
<uni-card class="list-card" v-for="(item, index) in listData.taskList" :key="index" @click="goToStartInspection(item)">
|
||||
<view class="head">
|
||||
<text class="title">{{ item.routeName }}</text>
|
||||
<uni-tag :inverted="true" :text="item.state == 0 ? '已巡检' : '未巡检'" :type="item.state == 0 ? 'success' : 'error'" />
|
||||
<text class="title">{{ item.title }}</text>
|
||||
<!-- <uni-tag :inverted="true" :text="item.state == 0 ? '已巡检' : '未巡检'" :type="item.state == 0 ? 'success' : 'error'" /> -->
|
||||
<uni-tag :inverted="true" :text="item.state" :type="item.state == '已巡检' ? 'success' : 'error'" />
|
||||
</view>
|
||||
<view class="num grey">线路编号:{{ item.routeNumber }}</view>
|
||||
<view class="num grey">线路编号:{{ item.num }}</view>
|
||||
<view class="info-box grey">
|
||||
<view class="info"> <uni-icons fontFamily="iconfont" color="#808185">{{''}}</uni-icons> {{ item.patrolPersonnel }}</view>
|
||||
<view class="info"> <uni-icons fontFamily="iconfont" color="#808185">{{''}}</uni-icons> {{ item.info }}</view>
|
||||
<text class="info">任务 {{ item.task }}</text>
|
||||
</view>
|
||||
</uni-card>
|
||||
<uni-load-more status="more" />
|
||||
<view v-if="listData.isLoadMore">
|
||||
<uni-load-more :status="listData.loadStatus" ></uni-load-more>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view v-if="categoryCurIndex == 1"></view>
|
||||
<view v-if="categoryCurIndex == 2"></view>
|
||||
@ -102,15 +105,22 @@
|
||||
<view v-if="navCurIndex == 2">自动分部</view>
|
||||
<view v-if="navCurIndex == 3">保护分部</view>
|
||||
</view>
|
||||
<!-- 回到顶部 -->
|
||||
<view class="top-back" @click="topBack" v-if="isShow">
|
||||
<uni-icons type="up" size="16" color="#fff"></uni-icons>
|
||||
<text>顶部</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, onMounted, reactive, ref } from "vue";
|
||||
import { onReachBottom, onLoad, onShow, onPullDownRefresh } from "@dcloudio/uni-app";
|
||||
import { onReachBottom, onLoad, onShow, onPullDownRefresh, onPageScroll } from "@dcloudio/uni-app";
|
||||
import Dashboard from "@/components/echarts/dashboard.vue";
|
||||
import { getRouteList } from '@/api/api.js'
|
||||
import { getRouteList, getRoutePage } from '@/api/api.js'
|
||||
|
||||
|
||||
let isShow = ref(false)
|
||||
// 导航栏
|
||||
let navCurIndex = ref(0)
|
||||
const departments = reactive([
|
||||
@ -146,54 +156,85 @@ const category = reactive([
|
||||
])
|
||||
|
||||
// 列表数据
|
||||
let listData = ref([
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '已巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '已巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '未巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '未巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
// { title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
])
|
||||
let listData = reactive({
|
||||
page: {//请求参数
|
||||
size: 10, // 条数
|
||||
num: 1, // 页数
|
||||
},
|
||||
loadStatus:'loading', //加载样式:more-加载前样式,loading-加载中样式,nomore-没有数据样式
|
||||
isLoadMore:false, //是否加载中
|
||||
taskList: [
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '已巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '已巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '未巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '未巡检', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
{ title: '左岸白班机组', state: '巡检中', num: 'BHT-YX-XJ-L1.2', info: '白班每班一次,由当班值执行', task: '5/32' },
|
||||
]//数据
|
||||
})
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh(() => {
|
||||
nextTick(() => {
|
||||
console.log('下拉刷新完成');
|
||||
queryList({})
|
||||
queryList(listData.page)
|
||||
// 停止下拉刷新动画
|
||||
uni.stopPullDownRefresh();
|
||||
});
|
||||
})
|
||||
|
||||
//上拉触底
|
||||
onReachBottom(() => {
|
||||
loadMore()
|
||||
if(!listData.isLoadMore){
|
||||
listData.isLoadMore = true
|
||||
listData.filtrate.num += 1
|
||||
queryList(listData.page)
|
||||
}
|
||||
})
|
||||
|
||||
// 根据滚动距离以推断是否有显示返回顶部功能
|
||||
onPageScroll((e) => {
|
||||
console.log('Scroll position:', e.scrollTop);
|
||||
isShow.value = e.scrollTop >= 200
|
||||
});
|
||||
|
||||
onLoad(() => {
|
||||
queryList({})
|
||||
queryList(listData.page)
|
||||
})
|
||||
|
||||
const queryList = (data) => {
|
||||
console.log(data)
|
||||
getRouteList(data).then((res) => {
|
||||
getRoutePage(data).then((res) => {
|
||||
console.log(res.data);
|
||||
|
||||
// if(res.records.length!==0){
|
||||
// //首次加载10条数据,后进行拼接
|
||||
// state.taskList = state.taskList.concat(res.records)
|
||||
// //判断接口返回数据量小于请求数据量,则表示此为最后一页
|
||||
// if(res.records.length<state.filtrate.size){
|
||||
// state.isLoadMore=true
|
||||
// state.loadStatus='nomore'
|
||||
// }else{
|
||||
// state.isLoadMore=false
|
||||
// }
|
||||
// }else{
|
||||
// state.isLoadMore=true
|
||||
// state.loadStatus='nomore'
|
||||
// }
|
||||
|
||||
if (res.code == 200) {
|
||||
listData.value = res.data
|
||||
}
|
||||
// if (res.code == 200) {
|
||||
// listData.taskList = res.data
|
||||
// }
|
||||
}).catch((err) => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const loadMore = () => {
|
||||
|
||||
}
|
||||
|
||||
// 切换导航
|
||||
const changeTitle = (index) => {
|
||||
navCurIndex.value = index
|
||||
@ -236,6 +277,14 @@ const goToStartInspection = (item) => {
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// 点击绑定的事件
|
||||
const topBack = () => {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0, // 滚动到顶部
|
||||
duration: 300 // 动画时长 300ms
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: XHC
|
||||
* @Date: 2025-05-19 15:18:26
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-29 16:14:30
|
||||
* @LastEditTime: 2025-06-05 17:18:03
|
||||
* @Description: 知识库
|
||||
-->
|
||||
<template>
|
||||
@ -19,7 +19,7 @@
|
||||
>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<uni-icons fontFamily="iconfont" color="#fff" :size="24">{{''}}</uni-icons>
|
||||
<uni-icons fontFamily="iconfont" color="#fff" :size="24" @click="screening">{{''}}</uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-box" v-if="titleCurIndex == 0">
|
||||
@ -29,7 +29,7 @@
|
||||
<view class="info-box grey">
|
||||
<uni-row :gutter="30">
|
||||
<uni-col :span="16">
|
||||
<view class="info"><uni-icons fontFamily="iconfont" color="#1469BC">{{''}}</uni-icons> <text>{{ item.departments }}</text></view>
|
||||
<view class="info"><uni-icons fontFamily="iconfont" color="#1469BC">{{''}}</uni-icons> <text class="info-text">{{ item.departments }}</text></view>
|
||||
</uni-col>
|
||||
<uni-col :span="8">
|
||||
<view class="info"><uni-icons fontFamily="iconfont" color="#808185">{{''}}</uni-icons> {{ item.time }}</view>
|
||||
@ -38,13 +38,19 @@
|
||||
</view>
|
||||
</uni-card>
|
||||
</view>
|
||||
|
||||
<DaDropdown
|
||||
ref="daDropdownRef"
|
||||
v-model:dropdownMenu="dropdownMenuList"
|
||||
@confirm="handleConfirm"
|
||||
@close="handleClose"
|
||||
@open="handleOpen">
|
||||
</DaDropdown>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import DaDropdown from '@/components/da-dropdown/index.vue'
|
||||
// 标题
|
||||
let titleCurIndex = ref(0)
|
||||
let titleList = reactive([
|
||||
@ -55,6 +61,70 @@ let titleList = reactive([
|
||||
{ id: 5, name: "发电机" },
|
||||
])
|
||||
|
||||
const daDropdownRef = ref(null)
|
||||
const menuVal = ref(null)
|
||||
const dropdownMenuList = ref([
|
||||
{
|
||||
// title: '筛选',
|
||||
type: 'filter',
|
||||
prop: 'god5',
|
||||
// 默认选中单选2、多选2、3、滑动30
|
||||
// value: { ft1: '2', ft2: ['2', '3'], ft3: 30 },
|
||||
options: [
|
||||
{
|
||||
title: '单选',
|
||||
type: 'radio',
|
||||
prop: 'ft1',
|
||||
options: [
|
||||
{ label: '单选1', value: '1' },
|
||||
{ label: '单选2', value: '2' },
|
||||
{ label: '单选3', value: '3' },
|
||||
{ label: '单选4', value: '4' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '多选',
|
||||
type: 'checkbox',
|
||||
prop: 'ft2',
|
||||
options: [
|
||||
{ label: '多选1', value: '1' },
|
||||
{ label: '多选2', value: '2' },
|
||||
{ label: '多选3', value: '3' },
|
||||
{ label: '多选4', value: '4' },
|
||||
{ label: '多选5', value: '5' },
|
||||
],
|
||||
},
|
||||
// {
|
||||
// title: '滑块',
|
||||
// type: 'slider',
|
||||
// prop: 'ft3',
|
||||
// componentProps: {
|
||||
// min: 0,
|
||||
// max: 100,
|
||||
// step: 1,
|
||||
// showValue: true,
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
|
||||
])
|
||||
|
||||
function handleConfirm(v, selectedValue) {
|
||||
console.log('handleConfirm ==>', v, selectedValue)
|
||||
}
|
||||
function handleClose(v, callbackMenuList) {
|
||||
console.log('handleClose ==>', v, callbackMenuList)
|
||||
}
|
||||
function handleOpen(v) {
|
||||
console.log('handleOpen ==>', v)
|
||||
}
|
||||
|
||||
function handleMenuVal() {
|
||||
menuVal.value = daDropdownRef.value?.getMenuValue()
|
||||
console.log('已选数据', menuVal.value)
|
||||
}
|
||||
|
||||
// 列表数据
|
||||
const listData = reactive([
|
||||
{ title: '电站运行巡检作业指导书', num: 'Q/BHTHP.ZDS 7008-2024', departments: '水力发电厂运行部', time: '2025-02-20' },
|
||||
@ -76,10 +146,19 @@ const search = () => {
|
||||
const changeTitle = (index) => {
|
||||
titleCurIndex.value = index
|
||||
}
|
||||
|
||||
const screening = () => {
|
||||
// picker.value.show()
|
||||
console.log(daDropdownRef.value);
|
||||
|
||||
daDropdownRef.value?.handleMenuClick()
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.knowledgeBase {
|
||||
// position: relative;
|
||||
.search-title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@ -96,10 +175,25 @@ const changeTitle = (index) => {
|
||||
}
|
||||
|
||||
.list-box {
|
||||
.title {
|
||||
width: 100%;
|
||||
}
|
||||
.info {
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.da-dropdown-menu) {
|
||||
position: absolute!important;
|
||||
width: 50rpx;
|
||||
height: 60rpx;
|
||||
top: 100rpx!important;
|
||||
right: 40rpx!important;
|
||||
opacity: 0;
|
||||
}
|
||||
:deep(.da-dropdown-content) {
|
||||
top: 170rpx!important;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -2,7 +2,7 @@
|
||||
* @Author: XHC
|
||||
* @Date: 2025-05-19 15:19:14
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-23 15:30:08
|
||||
* @LastEditTime: 2025-06-05 17:46:03
|
||||
* @Description: 消息
|
||||
-->
|
||||
<template>
|
||||
@ -11,7 +11,7 @@
|
||||
<view class="notice-list" v-for="(item, index) in noticeList" :key="index">
|
||||
<view class="title">
|
||||
<uni-icons fontFamily="iconfont" color="#1571F3" :size="24">{{''}}</uni-icons>
|
||||
{{ item.title }}
|
||||
<text class="title-text">{{ item.title }}</text>
|
||||
</view>
|
||||
<view class="info">{{ item.info }}</view>
|
||||
<view class="bottom">
|
||||
@ -20,11 +20,19 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 回到顶部 -->
|
||||
<view class="top-back" @click="topBack" v-if="isShow">
|
||||
<uni-icons type="up" size="16" color="#fff"></uni-icons>
|
||||
<text>顶部</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import { ref, reactive } from 'vue';
|
||||
import { onPageScroll } from "@dcloudio/uni-app";
|
||||
|
||||
let isShow = ref(false)
|
||||
|
||||
// 通知列表
|
||||
const noticeList = reactive([
|
||||
@ -34,7 +42,31 @@ const noticeList = reactive([
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
{ title: '系统通知', info: '即将开展“控制楼巡检”任务,请…', time: '2025-02-20' },
|
||||
])
|
||||
|
||||
// 根据滚动距离以推断是否有显示返回顶部功能
|
||||
onPageScroll((e) => {
|
||||
isShow.value = e.scrollTop >= 300
|
||||
});
|
||||
|
||||
// 点击绑定的事件
|
||||
const topBack = () => {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0, // 滚动到顶部
|
||||
duration: 300 // 动画时长 300ms
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -53,6 +85,10 @@ const noticeList = reactive([
|
||||
color: #004291;
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
.title-text {
|
||||
display: inline-block;
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
.info, .bottom {
|
||||
color: #75777A;
|
||||
@ -61,7 +97,12 @@ const noticeList = reactive([
|
||||
}
|
||||
.info {
|
||||
border-bottom: 3rpx solid #fff;
|
||||
}
|
||||
}
|
||||
.title-text, .info {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.bottom, .detail {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: XHC
|
||||
* @Date: 2025-05-19 15:19:14
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-30 17:01:11
|
||||
* @LastEditTime: 2025-06-03 09:23:19
|
||||
* @Description: 我的
|
||||
-->
|
||||
<template>
|
||||
@ -83,9 +83,9 @@ const goToPages = (index) => {
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
// uni.navigateTo({
|
||||
// url: '/pages/points/points'
|
||||
// });
|
||||
uni.navigateTo({
|
||||
url: '/pages/points/points'
|
||||
});
|
||||
break
|
||||
default:
|
||||
break;
|
||||
|
@ -24,50 +24,61 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="collapse-box" v-if="navCurIndex == 0">
|
||||
<uni-collapse ref="collapse" v-model="collapseValue" accordion @change="change">
|
||||
<uni-collapse-item v-for="(item, index) in collapsetitle" :key="index">
|
||||
<view class="collapse-box">
|
||||
<uni-collapse ref="collapseRef" v-model="collapseValue" accordion @change="changeCollapse">
|
||||
<uni-collapse-item v-for="(val, collapseIndex) in collapseData" :key="collapseIndex">
|
||||
<template v-slot:title>
|
||||
<uni-section class="section" :title="item.title" type="line"></uni-section>
|
||||
<uni-section class="section" :title="val.title" type="line"></uni-section>
|
||||
</template>
|
||||
<view class="content">
|
||||
<view class="content-item" v-for="(item, index) in contentList" :key="index">
|
||||
<view class="content-item" v-for="(item, itemIndex) in val.children" :key="itemIndex">
|
||||
<view class="title">
|
||||
<view>{{ item.title }}</view>
|
||||
<view>{{ item.title }}</view>
|
||||
<uni-tag :inverted="true" :text="item.tag" :type="item.tag == '重点关注' ? 'error' : 'warning'" />
|
||||
</view>
|
||||
<view class="btn">
|
||||
<button size="mini" type="primary" @click="standard">巡检标准</button>
|
||||
<button size="mini" type="primary" @click="history">巡检历史</button>
|
||||
<button size="mini" type="primary" @click="standard(item.standard)">巡检标准</button>
|
||||
<button size="mini" type="primary" @click="history(item.history)">巡检历史</button>
|
||||
</view>
|
||||
<view class="input" :class="item.selectedImg ? 'warp' : ''" v-if="item.tag">
|
||||
<view :class="item.selectedImg ? '' : 'input'" v-if="item.tag">
|
||||
<uni-easyinput class="textarea" type="textarea" v-model="item.described" placeholder="请输入" />
|
||||
<uni-file-picker class="picker" file-mediatype="image" :limit="9" v-model="item.imageValue" @select="selectImg(item)" @delete="deleteImg(e,item)"><uni-icons type="camera-filled" color="#004894" size="50"></uni-icons></uni-file-picker>
|
||||
<uni-file-picker class="picker" mode="grid" :sourceType="['camera']" file-mediatype="image" limit="9" v-model="item.imageValue" @progress="progress" @select="(e) => selectImg(item, e, collapseIndex, itemIndex)" @delete="deleteImg(item, $event)"><uni-icons type="camera-filled" color="#004894" size="50"></uni-icons></uni-file-picker>
|
||||
<!-- <WaterMarker :ref="el => { if (el) waterMarkRefs[`waterMarkRef_${collapseIndex}_${itemIndex}`] = el }"/> -->
|
||||
<hpy-watermark :ref="el => { if (el) waterMarkRefs[`waterMarkRef_${collapseIndex}_${itemIndex}`] = el }" @waterMark="(path) => waterMark(item, path)"></hpy-watermark>
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="state"><text>状态:</text><uni-data-checkbox class="checkbox" mode="button" v-model="item.radio" :localdata="localdata"></uni-data-checkbox></view>
|
||||
<view v-if="item.radio != 0">
|
||||
<view class="described">描述:<uni-easyinput class="textarea" type="textarea" v-model="item.described" placeholder="请输入" /></view>
|
||||
<view>照片:<uni-file-picker class="picker" limit="9" v-model="item.imageValue"><uni-icons type="camera-filled" color="#004894" size="50"></uni-icons></uni-file-picker></view>
|
||||
<view>照片:<uni-file-picker class="picker" mode="grid" :sourceType="['camera']" file-mediatype="image" limit="9" v-model="item.imageValue" @select="selectImg(item,$event)" @delete="deleteImg(item, $event)"><uni-icons type="camera-filled" color="#004894" size="50"></uni-icons></uni-file-picker></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</uni-collapse-item>
|
||||
</uni-collapse>
|
||||
</view>
|
||||
<uni-popup ref="popup" background-color="#fff" @change="change">
|
||||
<view class="popup-content" :class="{ 'popup-height': type === 'left' || type === 'right' }"><text
|
||||
class="text">popup 内容</text></view>
|
||||
<uni-popup ref="alertDialog" type="dialog">
|
||||
<uni-popup-dialog type="warning" cancelText="关闭" confirmText="同意" title="通知" content="欢迎使用 uni-popup!" @confirm="dialogConfirm"
|
||||
@close="dialogClose"></uni-popup-dialog>
|
||||
</uni-popup>
|
||||
<uni-popup ref="popup" background-color="#fff" @change="changePopup">
|
||||
<view class="popup-content"><text class="text">{{ popValue }}</text></view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import { ref, reactive, nextTick, getCurrentInstance } from 'vue';
|
||||
import ImageWatermarkPicker from "@/components/image-watermark-picker.vue"
|
||||
import WaterMarker from '@/components/waterMarker.vue'
|
||||
import HpyWatermark from '@/uni_modules/hpy-watermark/components/hpy-watermark/hpy-watermark.vue'
|
||||
|
||||
let popup = ref(null)
|
||||
let waterMarkRefs = reactive({})
|
||||
|
||||
// 弹出框内容
|
||||
let popValue = ref(null)
|
||||
let alertDialog = ref(null)
|
||||
|
||||
let navCurIndex = ref(0)
|
||||
// 详情信息
|
||||
@ -82,27 +93,67 @@ let positionData = reactive([
|
||||
{ id: 2, title: "右岸" },
|
||||
{ id: 3, title: "大坝" }
|
||||
])
|
||||
|
||||
let collapseRef = ref(null)
|
||||
// 手风琴折叠面板默认展开内容
|
||||
let collapseValue = ref('0')
|
||||
// 手风琴标题
|
||||
let collapsetitle = reactive([
|
||||
{ id: 1, title: '左岸进水口配电系统' },
|
||||
{ id: 2, title: '左岸排风竖井配电系统' },
|
||||
{ id: 3, title: '左岸尾水管配电系统(南端)' },
|
||||
{ id: 4, title: '左岸尾水管配电系统(北端)' },
|
||||
{ id: 5, title: '左岸尾水洞出口配电系统' },
|
||||
{ id: 6, title: '左岸水垫塘渗漏排水配电系统' },
|
||||
{ id: 7, title: '左岸水垫塘检修排水配电系统' },
|
||||
{ id: 8, title: '控制管理楼及左岸出线场配电系统' },
|
||||
])
|
||||
|
||||
// 手风琴内容数据
|
||||
let contentList = ref([
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常' }
|
||||
// 手风琴数据
|
||||
let collapseData = reactive([
|
||||
{ id: 1, title: '左岸进水口配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 2, title: '左岸排风竖井配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 3, title: '左岸尾水管配电系统(南端)', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 4, title: '左岸尾水管配电系统(北端)', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 5, title: '左岸尾水洞出口配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 6, title: '左岸水垫塘渗漏排水配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 7, title: '左岸水垫塘检修排水配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
{ id: 8, title: '控制管理楼及左岸出线场配电系统', children: [
|
||||
{ title: '环境温湿度(°C/%)', tag: '重点关注', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: 'Ⅰ段干式变三相绕组温度', tag: '请确认', described: '', standard: '巡检标准', history: '巡检历史', imageValue: [], selectedImg: false },
|
||||
{ title: '带电显示装置及电磁锁正常;铁芯端部无散片、流胶;', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '盘柜无异常声音、气味;无放电、过热现象外观正常;开关状态及指示灯正常',radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] },
|
||||
{ title: '备自投及指示灯正常', radio: 0, described: '', standard: '巡检标准', history: '巡检历史', imageValue: [] }
|
||||
] },
|
||||
])
|
||||
|
||||
// 单选数据
|
||||
@ -117,34 +168,95 @@ const changeTitle = (index) => {
|
||||
navCurIndex.value = index
|
||||
}
|
||||
|
||||
const change = () => {
|
||||
// 切换手风琴面板
|
||||
const changeCollapse = () => {
|
||||
// alertDialog.value.open()
|
||||
}
|
||||
|
||||
// 弹出框
|
||||
const changePopup = () => {
|
||||
|
||||
}
|
||||
|
||||
// 确认
|
||||
const dialogConfirm = () => {
|
||||
|
||||
}
|
||||
|
||||
// 取消
|
||||
const dialogClose = () => {
|
||||
|
||||
}
|
||||
|
||||
const waterMark = (item, path) => {
|
||||
console.log(path);
|
||||
item.imageValue.push({
|
||||
name:"file.png",
|
||||
extname:"png",
|
||||
url: path,
|
||||
// ...
|
||||
});
|
||||
console.log(item.imageValue);
|
||||
};
|
||||
|
||||
// 选择图片
|
||||
const selectImg = (item) => {
|
||||
console.log(item);
|
||||
const selectImg = (item, e, collapseIndex, itemIndex) => {
|
||||
item.selectedImg = true
|
||||
// 获取对应的ref key
|
||||
const refKey = `waterMarkRef_${collapseIndex}_${itemIndex}`
|
||||
const waterMarkerInstance = waterMarkRefs[refKey]
|
||||
if (waterMarkerInstance) {
|
||||
// const imgFileArr = waterMarkerInstance.callAddWaterMark(e.tempFilePaths);
|
||||
// imgFileArr.forEach((el) => {
|
||||
// item.imageValue.push({
|
||||
// url: el,
|
||||
// extname: el.substring(el.lastIndexOf(".") + 1),
|
||||
// name: el,
|
||||
// });
|
||||
// })
|
||||
console.log(waterMarkerInstance);
|
||||
|
||||
var fillTexts = ["人员:张三", "地址:广东省珠海市香洲区XXX"];
|
||||
fillTexts.push("时间:");
|
||||
// 添加水印
|
||||
waterMarkerInstance.addWaterMark({
|
||||
filePaths: e.tempFilePaths,
|
||||
fillTexts
|
||||
});
|
||||
} else {
|
||||
console.warn('未找到对应的WaterMarker实例或方法未暴露', refKey, waterMarkerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const progress = (item, index) => {
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: item.map((o) => o.url),
|
||||
});
|
||||
}
|
||||
|
||||
// 删除选择的图片
|
||||
const deleteImg = (e,item) => {
|
||||
console.log(item);
|
||||
console.log(item.imageValue);
|
||||
console.log(item.imageValue.length);
|
||||
const deleteImg = (item,e) => {
|
||||
item.imageValue.splice(e.index, 1)
|
||||
console.log(item, e)
|
||||
|
||||
// if (item.imageValue.length == 0) {
|
||||
// item.selectedImg = false
|
||||
// }
|
||||
if (item.imageValue.length == 0) {
|
||||
item.selectedImg = false
|
||||
}
|
||||
}
|
||||
|
||||
// 巡检标准
|
||||
const standard = () => {
|
||||
const standard = (val) => {
|
||||
popup.value.open()
|
||||
popValue.value = val
|
||||
}
|
||||
|
||||
const history = () => {
|
||||
// 巡检历史
|
||||
const history = (val) => {
|
||||
popup.value.open()
|
||||
popValue.value = val
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -208,9 +320,13 @@ const history = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.warp {
|
||||
display: block !important;
|
||||
}
|
||||
// .warp {
|
||||
// display: block !important;
|
||||
// :deep(.uni-file-picker__container) {
|
||||
// width: auto !important;
|
||||
// height: auto !important;
|
||||
// }
|
||||
// }
|
||||
.state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: XHC
|
||||
* @Date: 2025-05-23 16:46:57
|
||||
* @LastEditors: XHC
|
||||
* @LastEditTime: 2025-05-29 17:06:12
|
||||
* @LastEditTime: 2025-06-06 17:41:44
|
||||
* @Description: 开始巡检
|
||||
-->
|
||||
<template>
|
||||
@ -78,7 +78,7 @@
|
||||
<view class="inspection-points">
|
||||
<view class="title">
|
||||
<uni-section title="巡检点位:" type="line"><text>3/32</text></uni-section>
|
||||
<uni-icons type="scan" size="26" @click="scanQRCode" ></uni-icons>
|
||||
<uni-icons type="scan" size="26" @click="scanQRCode" v-if="infoData.state != '已巡检'"></uni-icons>
|
||||
</view>
|
||||
<view class="list-box">
|
||||
<view class="list-item" v-for="(item, index) in listData" :key="index" @click="scanQRCode">
|
||||
@ -153,8 +153,10 @@ const queryPointList = () => {
|
||||
|
||||
// 扫描二维码
|
||||
const scanQRCode = () => {
|
||||
if (infoData.state == '已巡检') return
|
||||
uni.scanCode({
|
||||
// 扫码成功的回调函数
|
||||
onlyFromCamera: true,
|
||||
success: (res) => {
|
||||
// 这里res.result就是二维码的内容
|
||||
console.log('扫描结果:', res.result);
|
||||
|
5
src/uni_modules/hpy-watermark/changelog.md
Normal file
5
src/uni_modules/hpy-watermark/changelog.md
Normal file
@ -0,0 +1,5 @@
|
||||
## 1.0.6(2023-04-08)
|
||||
去掉无用依赖
|
||||
## 1.0.5(2023-04-08)
|
||||
- 修复H5有些图片出现半截空白
|
||||
- 有问题请描述使用场景,最好能把图片贴出来,以便更好的优化兼容性
|
@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<view class="watermark-content">
|
||||
<canvas canvas-id="watermarkCanvas" id="watermarkCanvas" :style="{width:canvasWidth + 'px', height:canvasHeight + 'px'}"></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// export default {
|
||||
// name:'hpy-watermark',
|
||||
import { ref } from "vue";
|
||||
|
||||
const emit = defineEmits(['waterMark'])
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 文字文字位置(默认:左下角)可选值:左上角:topLeft、右上角:topRight、左下角:bottomLeft、右下角:bottomRight
|
||||
*/
|
||||
markAlign:{
|
||||
type:String,
|
||||
default:function(){
|
||||
return 'bottomLeft'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 设置文本的水平对齐方式,默认:start,文本在指定的位置开始。
|
||||
* end 文本在指定的位置结束。
|
||||
* center 文本的中心被放置在指定的位置。
|
||||
* left 文本左对齐。
|
||||
* right 文本右对齐。
|
||||
*/
|
||||
textAlign:{
|
||||
type:String,
|
||||
default:function(){
|
||||
return 'start';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 设置文本的垂直对齐方式,默认:alphabetic文本基线是普通的字母基线。
|
||||
* top 文本基线是 em 方框的顶端。
|
||||
* hanging 文本基线是悬挂基线。
|
||||
* middle 文本基线是 em 方框的正中。
|
||||
* ideographic 文本基线是表意基线。
|
||||
* bottom 文本基线是 em 方框的底端。
|
||||
*/
|
||||
textBaseline:{
|
||||
type:String,
|
||||
default:function(){
|
||||
return 'alphabetic';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 文字大小
|
||||
*/
|
||||
fontSize:{
|
||||
type:[Number, String],
|
||||
default:40
|
||||
},
|
||||
/**
|
||||
* 文字颜色
|
||||
*/
|
||||
fontColor:{
|
||||
type:String,
|
||||
default:function(){
|
||||
return '#FFFFFF'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 阴影颜色
|
||||
*/
|
||||
shadowColor:{
|
||||
type:String,
|
||||
default:function(){
|
||||
return 'rgba(0, 0, 0, 1.0)';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 阴影边框大小
|
||||
*/
|
||||
shadowWidth:{
|
||||
type:[Number, String],
|
||||
default:2
|
||||
},
|
||||
/**
|
||||
* 图片的质量,取值范围为 (0, 1],不在范围内时当作1处理
|
||||
*/
|
||||
quality:{
|
||||
type:[Number, String],
|
||||
default:1
|
||||
},
|
||||
/**
|
||||
* 目标文件的类型,只支持 'jpg' 或 'png'。默认为 'png'
|
||||
*/
|
||||
fileType:{
|
||||
type:String,
|
||||
default:function(){
|
||||
return 'png'
|
||||
}
|
||||
}
|
||||
})
|
||||
let canvasWidth = ref(0)
|
||||
let canvasHeight = ref(0)
|
||||
/**
|
||||
* 增加水印
|
||||
* @param {Object} {filePaths:['图片地址1', '图片地址2'], fillTexts:['水印1', '水印2']}
|
||||
*/
|
||||
async function addWaterMark({ filePaths = [], fillTexts = [] }) {
|
||||
console.log('开始添加水印', filePaths, fillTexts);
|
||||
|
||||
uni.showLoading({title:'图片处理中···'});
|
||||
try{
|
||||
for (const filePath of filePaths) {
|
||||
await drawImage(filePath, fillTexts.reverse());
|
||||
}
|
||||
}catch(e){
|
||||
// TODO handle the exception
|
||||
}finally{
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 绘制单个图片
|
||||
*/
|
||||
async function drawImage(filePath, fillTexts){
|
||||
const ctx = uni.createCanvasContext('watermarkCanvas', this);
|
||||
return new Promise(resolve => {
|
||||
uni.getImageInfo({
|
||||
src: filePath,
|
||||
success: (image) => {
|
||||
console.log(image);
|
||||
|
||||
canvasWidth.value = image.width;
|
||||
canvasHeight.value = image.height;
|
||||
ctx.clearRect(0, 0, image.width, image.height);
|
||||
setTimeout(()=>{
|
||||
ctx.drawImage(image.path, 0, 0, image.width, image.height);
|
||||
ctx.setFontSize(props.fontSize);
|
||||
ctx.setFillStyle(props.fontColor);
|
||||
// 设置阴影
|
||||
let shadowWidth = Number(props.shadowWidth + "");
|
||||
if(shadowWidth > 0){
|
||||
ctx.shadowColor = props.shadowColor;
|
||||
ctx.shadowOffsetX = shadowWidth;
|
||||
ctx.shadowOffsetY = shadowWidth;
|
||||
}
|
||||
// 设置水平对齐方式
|
||||
ctx.textAlign = props.textAlign;
|
||||
// 设置垂直对齐方式
|
||||
ctx.textBaseline = props.textBaseline;
|
||||
const maxText = fillTexts.reduce((text, val) => {
|
||||
return text.length >= val.length ? text : val;
|
||||
});
|
||||
fillTexts.forEach((mark, index) => {
|
||||
if(props.markAlign == "bottomRight"){
|
||||
ctx.fillText(mark, image.width - (ctx.measureText(maxText).width+60), image.height - (index*60+60));
|
||||
}else if(props.markAlign == "topLeft"){
|
||||
ctx.fillText(mark, 20, (index*60+60));
|
||||
}else if(props.markAlign == "topRight"){
|
||||
ctx.fillText(mark, image.width - (ctx.measureText(maxText).width+60), (index*60+60));
|
||||
}else{
|
||||
ctx.fillText(mark, 20, image.height - (index*60+60));
|
||||
}
|
||||
});
|
||||
ctx.draw(false, (() => {
|
||||
setTimeout(()=>{
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: 'watermarkCanvas',
|
||||
fileType:props.fileType,
|
||||
quality:Number(props.quality + "" || "1"),
|
||||
success: (res) => {
|
||||
console.log(res);
|
||||
|
||||
emit('waterMark', res.tempFilePath);
|
||||
},
|
||||
fail:(err) => {
|
||||
console.log(err)
|
||||
},
|
||||
complete: () => {
|
||||
resolve();
|
||||
}
|
||||
}, this);
|
||||
}, 300);
|
||||
})());
|
||||
}, 200);
|
||||
},
|
||||
fail: (e) => {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
addWaterMark
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.watermark-content{width: 0;height: 0;overflow: hidden;}
|
||||
</style>
|
83
src/uni_modules/hpy-watermark/package.json
Normal file
83
src/uni_modules/hpy-watermark/package.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"id": "hpy-watermark",
|
||||
"displayName": "文字水印",
|
||||
"version": "1.0.6",
|
||||
"description": "图片增加文字水印,支持拍照和相册选取多张",
|
||||
"keywords": [
|
||||
"水印",
|
||||
"图片水印",
|
||||
"文字水印",
|
||||
"watermark"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": ""
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
201
src/uni_modules/hpy-watermark/readme.md
Normal file
201
src/uni_modules/hpy-watermark/readme.md
Normal file
@ -0,0 +1,201 @@
|
||||
|
||||
## 图片增加文字水印
|
||||
|
||||
> **组件名:hpy-watermark
|
||||
> 图片增加文字水印,支持拍照和相册选取多张
|
||||
|
||||
## API
|
||||
|
||||
## Props
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>属性名</th>
|
||||
<th>类型</th>
|
||||
<th>默认值</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>markAlign</td>
|
||||
<td>String</td>
|
||||
<td>左下角</td>
|
||||
<td>文字文字位置(默认:左下角)可选值:左上角:topLeft、右上角:topRight、左下角:bottomLeft、右下角:bottomRight</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fontSize</td>
|
||||
<td>Number</td>
|
||||
<td>40</td>
|
||||
<td>文字大小,默认:40</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fontColor</td>
|
||||
<td>String</td>
|
||||
<td>白色</td>
|
||||
<td>文字颜色,默认:#FFFFFF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>quality</td>
|
||||
<td>Number</td>
|
||||
<td>1</td>
|
||||
<td>图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fileType</td>
|
||||
<td>String</td>
|
||||
<td>jpg</td>
|
||||
<td>目标文件的类型,只支持 'jpg' 或 'png'。默认为 'jpg'</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>shadowColor</td>
|
||||
<td>String</td>
|
||||
<td>黑色</td>
|
||||
<td>阴影颜色,默认:rgba(0, 0, 0, 1.0)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>shadowWidth</td>
|
||||
<td>Number</td>
|
||||
<td>2</td>
|
||||
<td>阴影边框大小,默认:2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>textAlign</td>
|
||||
<td>String</td>
|
||||
<td>start</td>
|
||||
<td>设置文本的水平对齐方式,默认:start,文本在指定的位置开始。 start、end、center、left、right</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>textBaseline</td>
|
||||
<td>String</td>
|
||||
<td>alphabetic</td>
|
||||
<td>设置文本的垂直对齐方式,默认:alphabetic文本基线是普通的字母基线。top、hanging、middle、ideographic、bottom</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## methods
|
||||
<table>
|
||||
<tr>
|
||||
<th>参数名</th>
|
||||
<th>类型</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>addWaterMark</td>
|
||||
<td>Object</td>
|
||||
<td>{filePaths:['图片地址1', '图片地址2'], fillTexts:['水印文字1', '水印文字2']}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 使用示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<view>
|
||||
<button @click="chooseImage">选择照片</button>
|
||||
<!-- 增加水印 -->
|
||||
<hpy-watermark ref="watermark" @waterMark="waterMark"></hpy-watermark>
|
||||
<view class="ul">
|
||||
<view class="li" v-for="(item, index) in imageList" :key="index">
|
||||
<image :src="item" class="img" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
```javascript
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imageList:[]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: this.limit, // 限制的图片数量
|
||||
sizeType: ['compressed'], // original 原图,compressed 压缩图,默认二者都有
|
||||
sourceType: ['album', 'camera'],// album 从相册选图,camera 使用相机,默认二者都有
|
||||
success: (res) => {
|
||||
var imgPathList = res.tempFilePaths;
|
||||
if(imgPathList.length > 0){
|
||||
this.addImages(imgPathList);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('chooseImage fail', err)
|
||||
if("chooseImage:fail cancel" == err.errMsg){
|
||||
uni.showToast({
|
||||
icon:'none',
|
||||
title:'取消了选择'
|
||||
});
|
||||
}else{
|
||||
// #ifdef MP
|
||||
uni.getSetting({
|
||||
success: (res) => {
|
||||
let authStatus = res.authSetting['scope.album'];
|
||||
if (!authStatus) {
|
||||
uni.showModal({
|
||||
title: '授权失败',
|
||||
content: '系统上传需要从您的相册获取图片,请在设置界面打开相关权限',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.openSetting();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 添加图片
|
||||
addImages(filePaths){
|
||||
if(filePaths.length > 0){
|
||||
var fillTexts = ["人员:张三", "地址:广东省珠海市香洲区XXX"];
|
||||
fillTexts.push("时间:" + this.getNowTime());
|
||||
// 添加水印
|
||||
this.$refs.watermark.addWaterMark({
|
||||
filePaths,
|
||||
fillTexts
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 水印添加回调,在H5平台下,filePath 为 base64
|
||||
*/
|
||||
waterMark(filePath){
|
||||
this.imageList.push(filePath);
|
||||
},
|
||||
/**
|
||||
* 获取当前时间
|
||||
*/
|
||||
getNowTime(){
|
||||
var date = new Date(),
|
||||
year = date.getFullYear(),
|
||||
month = date.getMonth() + 1,
|
||||
day = date.getDate(),
|
||||
hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
|
||||
minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
|
||||
second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
|
||||
month >= 1 && month <= 9 ? (month = "0" + month) : "";
|
||||
day >= 0 && day <= 9 ? (day = "0" + day) : "";
|
||||
return (year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
```style
|
||||
<style scoped>
|
||||
.ul{border: rgb(221, 221, 221) solid 1px; text-align: center; margin-right: 12px; position: relative }
|
||||
.ul .li .img{display:block; width: 80px; height: 80px;}
|
||||
</style>
|
||||
```
|
Loading…
x
Reference in New Issue
Block a user