【招生小程序】 优化# 主账号-个人:封装查询组件

master
kaeery 2025-03-06 21:00:02 +08:00
parent 402b87b8b7
commit 56aa0125b8
12 changed files with 647 additions and 597 deletions

View File

@ -0,0 +1,58 @@
<template>
<u-popup :show="show" mode="bottom" :customStyle="{ padding: '20px' }" round="10px">
<view>
<dateDropdownPicker
ref="dateDropdownPickerRef"
:dropdownItem="dropdownMenuDateList"
@reset="handleReset"
@confirm="handleConfirm"
/>
</view>
</u-popup>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import dateDropdownPicker from '@/components/date-dropdown/daterange.vue'
import { useToggle } from '@/hooks/useLockFn'
import { formateDate, getCurDate } from '@/utils/util'
const { show, handleToggle } = useToggle()
const emit = defineEmits(['confirm', 'reset'])
const dateDropdownPickerRef = ref<InstanceType<typeof dateDropdownPicker>>()
const dropdownMenuDateList = ref({
showQuick: true,
title: '日期范围',
type: 'daterange',
prop: 'god6',
value: { start: getCurDate('start', 'YYYY-MM-DD'), end: getCurDate('end', 'YYYY-MM-DD') }
})
const openPopup = () => {
handleToggle()
}
//
const setDefaultValue = (payload: any) => {
if (payload.createTimeStart || payload.createTimeEnd) {
dropdownMenuDateList.value.value = {
start: formateDate(payload.createTimeStart, 'start', 'YYYY-MM-DD'),
end: formateDate(payload.createTimeEnd, 'end', 'YYYY-MM-DD')
}
dateDropdownPickerRef.value.initData(dropdownMenuDateList.value, false, payload.dateTag)
}
}
const handleConfirm = (value: string) => {
emit('confirm', value)
}
const handleReset = () => {
emit('reset')
}
defineExpose({
openPopup,
setDefaultValue,
handleToggle
})
</script>
<style scoped></style>

View File

@ -1,6 +1,6 @@
<template>
<view class="flex flex-col h-screen">
<view class="flex items-center justify-between">
<view class="flex items-center justify-between relative">
<view class="flex-1 overflow-auto">
<u-tabs
:list="tabs"
@ -12,7 +12,7 @@
@change="handleChangeTab"
></u-tabs>
</view>
<view class="mr-[32rpx]">
<view class="mr-[32rpx]" @click="handleShowPopup">
<TIcon name="icon-filter" color="#999" />
</view>
</view>
@ -37,6 +37,7 @@
/>
</z-paging>
</view>
<date-popup ref="datePopupRef" @confirm="handleConfirmDate" @reset="handleResetDate" />
</view>
</template>
@ -44,10 +45,13 @@
import { onLoad } from '@dcloudio/uni-app'
import { ref, shallowRef } from 'vue'
import clueCard from './components/clue-card.vue'
import datePopup from './components/date-popup.vue'
import { useZPaging } from '@/hooks/useZPaging'
import { apiTeleClueList } from '@/api/clue'
import { computed } from 'vue'
import { debounce } from 'lodash-es'
import { nextTick } from 'vue'
import { formatDate, formateDate, getCurDate } from '@/utils/util'
const queryParams = ref({
likeWork: ''
@ -83,13 +87,47 @@ const searchChange = debounce(() => {
}, 300)
const type = ref('')
onLoad(option => {
if (option.id) {
uni.setNavigationBarTitle({
title: '张三'
})
type.value = option.type ?? ''
}
const dateTagFlag = ref('')
onLoad(async options => {
setFormData(options)
dateTagFlag.value = options?.dateTag ?? ''
await nextTick()
datePopupRef.value?.setDefaultValue({ ...form.value, dateTag: dateTagFlag.value })
// if (option.id) {
// uni.setNavigationBarTitle({
// title: ''
// })
type.value = options.type ?? ''
// }
})
const form = ref()
//
const setFormData = options => {
form.value = Object.keys(options).reduce((acc, key) => {
if (key == 'id' || key == 'dateTag' || key == 'type') {
return acc
}
acc[key] = decodeURIComponent(options[key])
return acc
}, {})
}
const datePopupRef = ref<InstanceType<typeof datePopup>>()
const handleShowPopup = () => {
datePopupRef.value?.openPopup()
}
const handleConfirmDate = (date: { [key: string]: string }) => {
const { start, end } = date
form.value.createTimeStart = formateDate(start, 'start', 'YYYY-MM-DD')
form.value.createTimeEnd = formateDate(end, 'end', 'YYYY-MM-DD')
datePopupRef.value?.handleToggle(true)
}
const handleResetDate = () => {
form.value.createTimeStart = getCurDate('start', 'YYYY-MM-DD')
form.value.createTimeEnd = getCurDate('end', 'YYYY-MM-DD')
console.log(form.value)
datePopupRef.value?.handleToggle(true)
}
</script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<template>
<view>
<view class="flex flex-col h-screen">
<u-tabs
:list="tabs"
:scrollable="false"
@ -18,20 +18,30 @@
@change="handleChangeTab"
></u-tabs>
<dropdownPicker ref="dropdownPickerRef" v-model="form" @refresh-page="refreshPage" />
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</template>
<template v-if="!loading && data.length > 0">
<TTable :columns="columns" :data="data">
<template #index="scope">
<text class="px-[20rpx] py-[6rpx] rounded-[4px]" :style="rankStyle(scope.row)">
{{ scope.row }}
</text>
</template>
</TTable>
</template>
<view class="flex-1 overflow-auto">
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</template>
<template v-else-if="!loading && data.length > 0">
<TTable :columns="columns" :data="data">
<template #index="scope">
<text
class="px-[20rpx] py-[6rpx] rounded-[4px]"
:style="rankStyle(scope.row)"
>
{{ scope.row }}
</text>
</template>
</TTable>
</template>
<template v-else-if="!loading && data.length == 0">
<div class="pb-3">
<w-empty />
</div>
</template>
</view>
</view>
</template>
@ -45,7 +55,7 @@ import { IForm } from '@/components/widgets/admin/team/index.vue'
const dropdownPickerRef = ref<InstanceType<typeof dropdownPicker>>()
const form = ref<IForm>()
const { tabs, activeTab, columns, data, loading, rankStyle, handleChangeTab, fetchData } = useRank({
const { tabs, columns, data, loading, rankStyle, handleChangeTab, fetchData } = useRank({
width: 160,
callback: () => {
fetchData(form.value)
@ -63,6 +73,7 @@ onLoad(async options => {
)
fetchData(form.value)
})
//
const setFormData = options => {
form.value = Object.keys(options).reduce((acc, key) => {
if (key == 'dropdownIndex' || key == 'dateTag') {

View File

@ -0,0 +1,170 @@
<template>
<view class="bg-white">
<u-dropdown ref="uDropdownRef" menu-icon="arrow-down-fill">
<u-dropdown-item title="岗位">
<view class="bg-white">
<position-tabs
@reset="handleReset('position')"
@confirm="value => handleConfirm('position', value)"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="组织">
<view class="bg-white">
<DropdownPicker
:dropdownItem="dropdownMenuOrgList"
@reset="() => handleReset('user')"
@confirm="value => handleConfirm('user', value)"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="日期">
<view class="bg-white p-[20rpx]">
<dateDropdownPicker
:dropdownItem="dropdownMenuDateList"
@reset="() => handleReset('date')"
@confirm="
(value, dropIndex, dateTag) => handleConfirm('date', value, dateTag)
"
/>
</view>
</u-dropdown-item>
</u-dropdown>
</view>
</template>
<script setup lang="ts">
import positionTabs from './position-tabs.vue'
import DropdownPicker from '../../orga-picker.vue'
import dateDropdownPicker from '@/components/date-dropdown/daterange.vue'
import { PropType, computed, ref } from 'vue'
import { apiOrganizationAllList } from '@/api/admin'
import { IForm } from '../../team/index.vue'
import { formateDate, getCurDate } from '@/utils/util'
const props = defineProps({
modelValue: {
type: Object as PropType<IForm>,
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue', 'refreshPage', 'confirm', 'reset'])
const localValue = computed({
get() {
return props.modelValue
},
set(newValue) {
return emit('update:modelValue', newValue)
}
})
const uDropdownRef = ref()
const dropdownMenuDateList = ref({
dropdownIndex: 3,
showQuick: true,
title: '日期范围',
type: 'daterange',
prop: 'god6',
value: { start: getCurDate('start', 'YYYY-MM-DD'), end: getCurDate('end', 'YYYY-MM-DD') }
})
const dropdownMenuOrgList = ref<any[]>([])
//
const handleConfirm = (type: string, item, dateTag?: string) => {
switch (type) {
case 'user':
localValue.value = {
...localValue.value,
userId: item.value
}
break
case 'date':
localValue.value = {
...localValue.value,
createTimeStart: formateDate(item.start, 'start'),
createTimeEnd: formateDate(item.end, 'end')
}
break
case 'position':
localValue.value = {
...localValue.value,
postId: item
}
break
default:
break
}
emit('refreshPage')
emit('confirm', dateTag)
closeDropDown()
}
//
const handleReset = (type: string) => {
switch (type) {
case 'user':
localValue.value = {
...localValue.value,
userId: ''
}
break
case 'date':
localValue.value = {
...localValue.value,
createTimeStart: getCurDate('start'),
createTimeEnd: getCurDate('end')
}
break
case 'position':
localValue.value = {
...localValue.value,
postId: 0
}
break
default:
break
}
emit('refreshPage')
emit('reset')
closeDropDown()
}
const closeDropDown = () => {
uDropdownRef.value.close()
}
//
const fetchAllOrganizationList = async () => {
try {
const result = await apiOrganizationAllList()
dropdownMenuOrgList.value = renameFields(result)
} catch (error) {}
}
const renameFields = (data: any[]): any[] => {
return data.map(item => {
const newItem = { ...item }
newItem.label = item.name
newItem.value = item.id
if (newItem.organizationVoList) {
newItem.children = renameFields(newItem.organizationVoList)
delete newItem.organizationVoList
}
if (newItem.userVos) {
newItem.userVos.forEach(item => {
item.label = item.username
item.value = item.id
})
newItem.children = newItem.children
? [...newItem.children, ...newItem.userVos]
: newItem.userVos
delete newItem.userVos
}
if (!newItem.children.length) {
newItem.disabled = true
}
return newItem
})
}
fetchAllOrganizationList()
</script>
<style scoped></style>

View File

@ -35,7 +35,6 @@ const handleCellClick = (item: any) => {
const { id } = item
postId.value = id
emit('update:modelValue', id)
emit('confirm', item)
}
const handleReset = () => {
postId.value = 0

View File

@ -32,7 +32,9 @@
<text>{{ parseText(key) }}数据</text>
<text class="text-primary" @click="handleMore(key)"></text>
</view>
<view class="flex gap-[12rpx] flex-wrap bg-[#f1f1f1] mx-[24rpx] py-[24rpx]">
<view
class="flex gap-[12rpx] flex-wrap bg-[#FAFAFE] mx-[24rpx] py-[24rpx] rounded-[12rpx]"
>
<view
class="w-[30%] flex justify-center items-center flex-col gap-[12rpx]"
v-for="(item, indey) in value"

View File

@ -1,36 +1,6 @@
<template>
<view class="flex-1 h-full flex flex-col">
<view class="bg-white">
<u-dropdown ref="uDropdownRef" menu-icon="arrow-down-fill">
<u-dropdown-item title="岗位">
<view class="bg-white">
<position-tabs
v-model="postId"
@reset="handleReset('position')"
@confirm="value => handleConfirm('position', value)"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="组织">
<view class="bg-white">
<DropdownPicker
:dropdownItem="dropdownMenuOrgList"
@reset="() => handleReset('organization')"
@confirm="value => handleConfirm('organization', value)"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="日期">
<view class="bg-white p-[20rpx]">
<dateDropdownPicker
:dropdownItem="dropdownMenuList2"
@reset="() => handleReset('date')"
@confirm="value => handleConfirm('date', value)"
/>
</view>
</u-dropdown-item>
</u-dropdown>
</view>
<dropdownPicker v-model="form" @confirm="confirm" @reset-page="resetPage" />
<view class="flex-1 mt-3 px-[32rpx] overflow-auto bg-[#FAFAFE]">
<!-- <z-paging
ref="paging"
@ -38,123 +8,70 @@
@query="queryList"
:fixed="false"
height="100%"
>
<template v-for="(item, index) in dataList" :key="`unique_${index}`">
<telesale-card :item="item" @handleCardClick="handleCardClick" />
</template>
</z-paging> -->
> -->
<template v-for="(item, index) in dataList" :key="`unique_${index}`">
<telesale-card :item="item" @handleCardClick="handleCardClick" />
</template>
<!-- </z-paging> -->
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import positionTabs from './components/position-tabs.vue'
import DropdownPicker from '../orga-picker.vue'
import telesaleCard from './components/telesale-card.vue'
import dateDropdownPicker from '@/components/date-dropdown/daterange.vue'
import { useZPaging } from '@/hooks/useZPaging'
import { apiTeleClueList } from '@/api/clue'
import { apiOrganizationAllList } from '@/api/admin'
import { getCurDate } from '@/utils/util'
import dropdownPicker from './components/dropdown-picker.vue'
const dropdownMenuList2 = {
showQuick: true,
title: '日期范围',
type: 'daterange',
prop: 'god6'
// 2022-01-012022-02-01
// value: { start: '2022-01-01', end: '2022-02-01' },
}
const postId = ref()
const dropdownMenuOrgList = ref([])
const form = ref({
postId: 0,
createTimeStart: getCurDate('start'),
createTimeEnd: getCurDate('end'),
userId: ''
})
const queryParams = ref({
likeWork: ''
})
const dataList = ref([])
const dataList = ref([{ id: 1, postId: '5,6', studentName: '张三' }])
const { paging, queryList, refresh, changeApi, setParams } = useZPaging(
queryParams.value,
apiTeleClueList,
() => {}
)
const dateTagFlag = ref() //
const confirm = (dateTag?: string) => {
console.log(form.value, dateTag)
dateTagFlag.value = dateTag
}
const resetPage = () => {}
//
const handleCardClick = (type: string, item: any) => {
const { id } = item
console.log(form.value)
const params = {
...form.value,
dateTag: encodeURIComponent(dateTagFlag.value),
id,
type
}
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
switch (type) {
case 'telesale':
case 'recruitsale':
uni.navigateTo({
url: '/bundle/pages/clue-list/index?id=' + id + '&type=' + type
url: '/bundle/pages/clue-list/index?id=' + queryString
})
break
default:
break
}
}
//
const handleConfirm = (type: string, item) => {
switch (type) {
case 'organization':
console.log(item)
break
case 'date':
console.log(item)
break
case 'position':
console.log(item)
break
default:
break
}
}
//
const handleReset = (type: string) => {
switch (type) {
case 'organization':
break
case 'date':
break
case 'position':
break
default:
break
}
}
const fetchAllOrganizationList = async () => {
try {
const result = await apiOrganizationAllList()
dropdownMenuOrgList.value = renameFields(result)
console.log(dropdownMenuOrgList.value)
} catch (error) {}
}
const renameFields = (data: any[]): any[] => {
return data.map(item => {
const newItem = { ...item }
newItem.label = item.name
newItem.value = item.id
if (newItem.organizationVoList) {
newItem.children = renameFields(newItem.organizationVoList)
delete newItem.organizationVoList
}
if (newItem.userVos) {
newItem.userVos.forEach(item => {
item.label = item.username
item.value = item.id
})
newItem.children = newItem.children
? [...newItem.children, ...newItem.userVos]
: newItem.userVos
delete newItem.userVos
}
if (!newItem.children.length) {
newItem.disabled = true
}
return newItem
})
}
fetchAllOrganizationList()
</script>
<style scoped lang="scss"></style>

View File

@ -6,51 +6,6 @@
@refresh-page="refreshPage"
@confirm="confirm"
/>
<!-- <view class="bg-white">
<u-dropdown ref="uDropdownRef" menu-icon="arrow-down-fill">
<u-dropdown-item title="团队">
<view class="bg-white">
<selectDropdown
ref="teamDropdownRef"
:dropdownItem="dropdownMenuTeamList"
:dropdownIndex="1"
@reset="() => handleReset('team')"
@confirm="
(value, dropdownIndex) =>
handleConfirm('team', value, dropdownIndex)
"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="组织">
<view class="bg-white">
<selectDropdown
ref="orgDropdownRef"
:dropdownItem="dropdownMenuAllOrgaList"
:dropdownIndex="2"
@reset="() => handleReset('organization')"
@confirm="
(value, dropdownIndex) =>
handleConfirm('organization', value, dropdownIndex)
"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="日期">
<view class="bg-white p-[20rpx]">
<dateDropdownPicker
:dropdownItem="dropdownMenuDateList"
:dropdownIndex="3"
@reset="() => handleReset('date')"
@confirm="
(value, dropdownIndex, dateTag) =>
handleConfirm('date', value, dropdownIndex, dateTag)
"
/>
</view>
</u-dropdown-item>
</u-dropdown>
</view> -->
<filter-value :form="form" :activeTab="activeTab" :organizationList="organizationList" />
<data-overview ref="dataOverviewRef" />
<converted-overview ref="convertedOverviewRef" />
@ -65,12 +20,9 @@ import dataOverview from './components/data-overview.vue'
import convertedOverview from './components/converted-overview.vue'
import rank from './components/rank.vue'
import clueStatus from './components/clue-status.vue'
import dateDropdownPicker from '@/components/date-dropdown/daterange.vue'
import dropdownPicker from '../dropdown-picker.vue'
import selectDropdown from '@/components/select-dropdown/index.vue'
import filterValue from '../filter-value.vue'
import { apiOrganizationByIdList, apiOrganizationList, apiOrganizationTeamList } from '@/api/admin'
import { formateDate, getCurDate } from '@/utils/util'
import { getCurDate } from '@/utils/util'
import { AdminTabEnum } from '@/enums'
export interface IForm {
@ -78,6 +30,7 @@ export interface IForm {
createTimeStart: string
createTimeEnd: string
userId: string
postId?: number
}
defineProps({
@ -87,44 +40,17 @@ defineProps({
}
})
const dropdownPickerRef = ref<InstanceType<typeof dropdownPicker>>()
const teamDropdownRef = ref<InstanceType<typeof selectDropdown>>()
const orgDropdownRef = ref<InstanceType<typeof selectDropdown>>()
const clueStatusRef = ref<InstanceType<typeof clueStatus>>()
const rankRef = ref<InstanceType<typeof rank>>()
const convertedOverviewRef = ref<InstanceType<typeof convertedOverview>>()
const dataOverviewRef = ref<InstanceType<typeof dataOverview>>()
const uDropdownRef = ref()
const organizationList = ref([])
const defaultValue = ref() //
const form = ref<IForm>({
organizationId: null,
createTimeStart: getCurDate('start'),
createTimeEnd: getCurDate('end'),
userId: ''
})
const dropdownMenuDateList = {
showQuick: true,
title: '日期范围',
type: 'daterange',
prop: 'god6',
value: { start: getCurDate('start', 'YYYY-MM-DD'), end: getCurDate('end', 'YYYY-MM-DD') }
}
const dropdownMenuTeamList = ref({
title: '下拉',
type: 'cell',
prop: 'god1',
showAll: true,
showIcon: true,
options: []
})
const dropdownMenuAllOrgaList = ref({
title: '下拉',
type: 'cell',
prop: 'god1',
showAll: true,
showIcon: true,
options: []
})
const menuIndexArr = ref<number[]>([])
const refreshPage = () => {
fetchAllData()
@ -133,55 +59,10 @@ const confirm = payload => {
menuIndexArr.value = Array.from(new Set([...menuIndexArr.value, payload.dropdownIndex]))
rankRef.value?.setSelectedIndexArr(menuIndexArr.value, payload?.dateTag)
}
//
const handleConfirm = (type: string, item, dropdownIndex: number, dateTag?: string) => {
switch (type) {
case 'organization':
case 'team':
//
dropdownIndex == 1
? orgDropdownRef.value.clearSelect()
: teamDropdownRef.value.clearSelect()
form.value.organizationId = item.value
break
case 'date':
form.value.createTimeStart = formateDate(item.start, 'start')
form.value.createTimeEnd = formateDate(item.end, 'end')
break
default:
break
}
fetchAllData()
closeDropDown()
menuIndexArr.value = Array.from(new Set([...menuIndexArr.value, dropdownIndex]))
rankRef.value?.setSelectedIndexArr(menuIndexArr.value, dateTag)
}
//
const handleReset = (type: string) => {
switch (type) {
case 'organization':
break
case 'date':
form.value.createTimeStart = getCurDate('start')
form.value.createTimeEnd = getCurDate('end')
break
case 'team':
form.value.organizationId = defaultValue.value
break
default:
break
}
fetchAllData()
closeDropDown()
}
const closeDropDown = () => {
uDropdownRef.value.close()
}
const refreshData = () => {
rankRef.value?.fetchData(form.value)
}
onMounted(async () => {
// await fetchOrganizationList()
await dropdownPickerRef.value?.fetchOrganizationList()
organizationList.value = dropdownPickerRef.value?.organizationList
fetchAllData()
@ -192,45 +73,5 @@ const fetchAllData = () => {
rankRef.value?.fetchData(form.value)
clueStatusRef.value?.fetchData(form.value)
}
//
const fetchOrganizationList = async () => {
try {
const result = await apiOrganizationList()
organizationList.value = result ?? []
if (result.length > 0) {
form.value.organizationId = result[0].id
defaultValue.value = result[0].id
}
} catch (error) {}
}
//
const fetchTeamList = async () => {
try {
const result = await apiOrganizationTeamList()
dropdownMenuTeamList.value.options =
result.map(item => {
return {
label: item.name,
value: item.id
}
}) ?? []
} catch (error) {}
}
//
const fetchAllOrganizationList = async () => {
try {
const result = await apiOrganizationByIdList()
dropdownMenuAllOrgaList.value.options =
result.map(item => {
return {
label: item.name,
value: item.id
}
}) ?? []
} catch (error) {}
}
// fetchTeamList()
// fetchAllOrganizationList()
</script>
<style scoped></style>

View File

@ -115,7 +115,7 @@ export function useRank({ width, callback }: { width: number; callback?: () => v
// 岗位
export function usePositions() {
const positionList = ref()
const postId = ref()
const postId = ref(0)
const fetchPositions = async () => {
try {
const result = await postLists()
@ -126,7 +126,7 @@ export function usePositions() {
value: item.id
}
})
if (positionList.value.length > 0) postId.value = positionList.value[0].id
// if (positionList.value.length > 0) postId.value = positionList.value[0].id
} catch (error) {}
}

View File

@ -24,6 +24,7 @@ export default {
round: 0,
zoom: true,
bgColor: '',
overlayOpacity: 0.5
overlayOpacity: 0.5,
top: 0
}
}

View File

@ -76,6 +76,10 @@ export const props = defineMixin({
overlayOpacity: {
type: [Number, String],
default: () => defProps.popup.overlayOpacity
},
top: {
type: Number,
default: () => defProps.popup.top
}
}
})

View File

@ -1,310 +1,319 @@
<template>
<view class="u-popup" :class="[customClass]">
<u-overlay
:show="show"
@click="overlayClick"
v-if="overlay"
:zIndex="zIndex"
:duration="overlayDuration"
:customStyle="overlayStyle"
:opacity="overlayOpacity"
></u-overlay>
<u-transition
:show="show"
:customStyle="transitionStyle"
:mode="position"
:duration="duration"
@afterEnter="afterEnter"
@click="clickHandler"
>
<view
class="u-popup__content"
:style="[contentStyle]"
@tap.stop="noop"
>
<u-status-bar v-if="safeAreaInsetTop"></u-status-bar>
<slot></slot>
<view
v-if="closeable"
@tap.stop="close"
class="u-popup__content__close"
:class="['u-popup__content__close--' + closeIconPos]"
hover-class="u-popup__content__close--hover"
hover-stay-time="150"
>
<u-icon
name="close"
color="#909399"
size="18"
bold
></u-icon>
</view>
<u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom>
</view>
</u-transition>
</view>
<view class="u-popup" :class="[customClass]">
<u-overlay
:show="show"
@click="overlayClick"
v-if="overlay"
:zIndex="zIndex"
:duration="overlayDuration"
:customStyle="overlayStyle"
:opacity="overlayOpacity"
></u-overlay>
<u-transition
:show="show"
:customStyle="transitionStyle"
:mode="position"
:duration="duration"
@afterEnter="afterEnter"
@click="clickHandler"
>
<view class="u-popup__content" :style="[contentStyle]" @tap.stop="noop">
<u-status-bar v-if="safeAreaInsetTop"></u-status-bar>
<slot></slot>
<view
v-if="closeable"
@tap.stop="close"
class="u-popup__content__close"
:class="['u-popup__content__close--' + closeIconPos]"
hover-class="u-popup__content__close--hover"
hover-stay-time="150"
>
<u-icon name="close" color="#909399" size="18" bold></u-icon>
</view>
<u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom>
</view>
</u-transition>
</view>
</template>
<script>
import { props } from './props';
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import { addUnit, addStyle, deepMerge, sleep, sys } from '../../libs/function/index';
/**
* popup 弹窗
* @description 弹出层容器用于展示弹窗信息提示等内容支持上右和中部弹出组件只提供容器内部内容由用户自定义
* @tutorial https://ijry.github.io/uview-plus/components/popup.html
* @property {Boolean} show 是否展示弹窗 (默认 false )
* @property {Boolean} overlay 是否显示遮罩 默认 true
* @property {String} mode 弹出方向默认 'bottom'
* @property {String | Number} duration 动画时长单位ms 默认 300
* @property {String | Number} overlayDuration 遮罩层动画时长单位ms 默认 350
* @property {Boolean} closeable 是否显示关闭图标默认 false
* @property {Object | String} overlayStyle 自定义遮罩的样式
* @property {String | Number} overlayOpacity 遮罩透明度0-1之间默认 0.5
* @property {Boolean} closeOnClickOverlay 点击遮罩是否关闭弹窗 默认 true
* @property {String | Number} zIndex 层级 默认 10075
* @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离 默认 true
* @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离状态栏高度 默认 false
* @property {String} closeIconPos 自定义关闭图标位置默认 'top-right'
* @property {String | Number} round 圆角值默认 0
* @property {Boolean} zoom 当mode=center时 是否开启缩放默认 true
* @property {Object} customStyle 组件的样式对象形式
* @event {Function} open 弹出层打开
* @event {Function} close 弹出层收起
* @example <u-popup v-model="show"><text>出淤泥而不染濯清涟而不妖</text></u-popup>
*/
export default {
name: 'u-popup',
mixins: [mpMixin, mixin, props],
data() {
return {
overlayDuration: this.duration + 50
}
},
watch: {
show(newValue, oldValue) {
if (newValue === true) {
// #ifdef MP-WEIXIN
const children = this.$children
this.retryComputedComponentRect(children)
// #endif
}
}
},
computed: {
transitionStyle() {
const style = {
zIndex: this.zIndex,
position: 'fixed',
display: 'flex',
}
style[this.mode] = 0
if (this.mode === 'left') {
return deepMerge(style, {
bottom: 0,
top: 0,
})
} else if (this.mode === 'right') {
return deepMerge(style, {
bottom: 0,
top: 0,
})
} else if (this.mode === 'top') {
return deepMerge(style, {
left: 0,
right: 0
})
} else if (this.mode === 'bottom') {
return deepMerge(style, {
left: 0,
right: 0,
})
} else if (this.mode === 'center') {
return deepMerge(style, {
alignItems: 'center',
'justify-content': 'center',
top: 0,
left: 0,
right: 0,
bottom: 0
})
}
},
contentStyle() {
const style = {}
// safeAreaInsets
// 使cssnvuecssiPhoneX
const {
safeAreaInsets
} = sys()
if (this.mode !== 'center') {
style.flex = 1
}
// transparent
if (this.bgColor) {
style.backgroundColor = this.bgColor
}
if(this.round) {
const value = addUnit(this.round)
if(this.mode === 'top') {
style.borderBottomLeftRadius = value
style.borderBottomRightRadius = value
} else if(this.mode === 'bottom') {
style.borderTopLeftRadius = value
style.borderTopRightRadius = value
} else if(this.mode === 'center') {
style.borderRadius = value
}
}
return deepMerge(style, addStyle(this.customStyle))
},
position() {
if (this.mode === 'center') {
return this.zoom ? 'fade-zoom' : 'fade'
}
if (this.mode === 'left') {
return 'slide-left'
}
if (this.mode === 'right') {
return 'slide-right'
}
if (this.mode === 'bottom') {
return 'slide-up'
}
if (this.mode === 'top') {
return 'slide-down'
}
},
},
emits: ["open", "close", "click", "update:show"],
methods: {
//
overlayClick() {
if (this.closeOnClickOverlay) {
this.$emit('update:show', false)
this.$emit('close')
}
},
close(e) {
this.$emit('update:show', false)
this.$emit('close')
},
afterEnter() {
this.$emit('open')
},
clickHandler() {
// u-transition
if(this.mode === 'center') {
this.overlayClick()
}
this.$emit('click')
},
// #ifdef MP-WEIXIN
retryComputedComponentRect(children) {
//
const names = ['u-calendar-month', 'u-album', 'u-collapse-item', 'u-dropdown', 'u-index-item', 'u-index-list',
'u-line-progress', 'u-list-item', 'u-rate', 'u-read-more', 'u-row', 'u-row-notice', 'u-scroll-list',
'u-skeleton', 'u-slider', 'u-steps-item', 'u-sticky', 'u-subsection', 'u-swipe-action-item', 'u-tabbar',
'u-tabs', 'u-tooltip'
]
//
for (let i = 0; i < children.length; i++) {
const child = children[i]
//
const grandChild = child.$children
// init
if (names.includes(child.$options.name) && typeof child?.init === 'function') {
//
sleep(50).then(() => {
child.init()
})
}
//
if (grandChild.length) {
this.retryComputedComponentRect(grandChild)
}
}
}
// #endif
}
}
import { props } from './props'
import { mpMixin } from '../../libs/mixin/mpMixin'
import { mixin } from '../../libs/mixin/mixin'
import { addUnit, addStyle, deepMerge, sleep, sys } from '../../libs/function/index'
/**
* popup 弹窗
* @description 弹出层容器用于展示弹窗信息提示等内容支持上右和中部弹出组件只提供容器内部内容由用户自定义
* @tutorial https://ijry.github.io/uview-plus/components/popup.html
* @property {Boolean} show 是否展示弹窗 (默认 false )
* @property {Boolean} overlay 是否显示遮罩 默认 true
* @property {String} mode 弹出方向默认 'bottom'
* @property {String | Number} duration 动画时长单位ms 默认 300
* @property {String | Number} overlayDuration 遮罩层动画时长单位ms 默认 350
* @property {Boolean} closeable 是否显示关闭图标默认 false
* @property {Object | String} overlayStyle 自定义遮罩的样式
* @property {String | Number} overlayOpacity 遮罩透明度0-1之间默认 0.5
* @property {Boolean} closeOnClickOverlay 点击遮罩是否关闭弹窗 默认 true
* @property {String | Number} zIndex 层级 默认 10075
* @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离 默认 true
* @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离状态栏高度 默认 false
* @property {String} closeIconPos 自定义关闭图标位置默认 'top-right'
* @property {String | Number} round 圆角值默认 0
* @property {Boolean} zoom 当mode=center时 是否开启缩放默认 true
* @property {Object} customStyle 组件的样式对象形式
* @event {Function} open 弹出层打开
* @event {Function} close 弹出层收起
* @example <u-popup v-model="show"><text>出淤泥而不染濯清涟而不妖</text></u-popup>
*/
export default {
name: 'u-popup',
mixins: [mpMixin, mixin, props],
data() {
return {
overlayDuration: this.duration + 50
}
},
watch: {
show(newValue, oldValue) {
if (newValue === true) {
// #ifdef MP-WEIXIN
const children = this.$children
this.retryComputedComponentRect(children)
// #endif
}
}
},
computed: {
transitionStyle() {
const style = {
zIndex: this.zIndex,
position: 'fixed',
display: 'flex'
}
style[this.mode] = 0
if (this.mode === 'left') {
return deepMerge(style, {
bottom: 0,
top: 0
})
} else if (this.mode === 'right') {
return deepMerge(style, {
bottom: 0,
top: 0
})
} else if (this.mode === 'top') {
return deepMerge(style, {
left: 0,
right: 0,
top: this.top + 'px'
})
} else if (this.mode === 'bottom') {
return deepMerge(style, {
left: 0,
right: 0
})
} else if (this.mode === 'center') {
return deepMerge(style, {
alignItems: 'center',
'justify-content': 'center',
top: 0,
left: 0,
right: 0,
bottom: 0
})
}
},
contentStyle() {
const style = {}
// safeAreaInsets
// 使cssnvuecssiPhoneX
const { safeAreaInsets } = sys()
if (this.mode !== 'center') {
style.flex = 1
}
// transparent
if (this.bgColor) {
style.backgroundColor = this.bgColor
}
if (this.round) {
const value = addUnit(this.round)
if (this.mode === 'top') {
style.borderBottomLeftRadius = value
style.borderBottomRightRadius = value
} else if (this.mode === 'bottom') {
style.borderTopLeftRadius = value
style.borderTopRightRadius = value
} else if (this.mode === 'center') {
style.borderRadius = value
}
}
return deepMerge(style, addStyle(this.customStyle))
},
position() {
if (this.mode === 'center') {
return this.zoom ? 'fade-zoom' : 'fade'
}
if (this.mode === 'left') {
return 'slide-left'
}
if (this.mode === 'right') {
return 'slide-right'
}
if (this.mode === 'bottom') {
return 'slide-up'
}
if (this.mode === 'top') {
return 'slide-down'
}
}
},
emits: ['open', 'close', 'click', 'update:show'],
methods: {
//
overlayClick() {
if (this.closeOnClickOverlay) {
this.$emit('update:show', false)
this.$emit('close')
}
},
close(e) {
this.$emit('update:show', false)
this.$emit('close')
},
afterEnter() {
this.$emit('open')
},
clickHandler() {
// u-transition
if (this.mode === 'center') {
this.overlayClick()
}
this.$emit('click')
},
// #ifdef MP-WEIXIN
retryComputedComponentRect(children) {
//
const names = [
'u-calendar-month',
'u-album',
'u-collapse-item',
'u-dropdown',
'u-index-item',
'u-index-list',
'u-line-progress',
'u-list-item',
'u-rate',
'u-read-more',
'u-row',
'u-row-notice',
'u-scroll-list',
'u-skeleton',
'u-slider',
'u-steps-item',
'u-sticky',
'u-subsection',
'u-swipe-action-item',
'u-tabbar',
'u-tabs',
'u-tooltip'
]
//
for (let i = 0; i < children.length; i++) {
const child = children[i]
//
const grandChild = child.$children
// init
if (names.includes(child.$options.name) && typeof child?.init === 'function') {
//
sleep(50).then(() => {
child.init()
})
}
//
if (grandChild.length) {
this.retryComputedComponentRect(grandChild)
}
}
}
// #endif
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-popup-flex:1 !default;
$u-popup-content-background-color: #fff !default;
@import '../../libs/css/components.scss';
$u-popup-flex: 1 !default;
$u-popup-content-background-color: #fff !default;
.u-popup {
flex: $u-popup-flex;
.u-popup {
flex: $u-popup-flex;
&__content {
background-color: $u-popup-content-background-color;
position: relative;
&__content {
background-color: $u-popup-content-background-color;
position: relative;
&--round-top {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
&--round-top {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
&--round-left {
border-top-left-radius: 0;
border-top-right-radius: 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 10px;
}
&--round-left {
border-top-left-radius: 0;
border-top-right-radius: 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 10px;
}
&--round-right {
border-top-left-radius: 10px;
border-top-right-radius: 0;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 0;
}
&--round-right {
border-top-left-radius: 10px;
border-top-right-radius: 0;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 0;
}
&--round-bottom {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
&--round-bottom {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
&--round-center {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
&--round-center {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
&__close {
position: absolute;
&__close {
position: absolute;
&--hover {
opacity: 0.4;
}
}
&--hover {
opacity: 0.4;
}
}
&__close--top-left {
top: 15px;
left: 15px;
}
&__close--top-left {
top: 15px;
left: 15px;
}
&__close--top-right {
top: 15px;
right: 15px;
}
&__close--top-right {
top: 15px;
right: 15px;
}
&__close--bottom-left {
bottom: 15px;
left: 15px;
}
&__close--bottom-left {
bottom: 15px;
left: 15px;
}
&__close--bottom-right {
right: 15px;
bottom: 15px;
}
}
}
&__close--bottom-right {
right: 15px;
bottom: 15px;
}
}
}
</style>