|
|
@@ -0,0 +1,500 @@
|
|
|
+<template>
|
|
|
+ <page-meta :page-style="showAddDialog ? 'overflow:hidden;' : 'overflow:visible;'" />
|
|
|
+
|
|
|
+ <view class="entity-selector-page">
|
|
|
+ <view class="header">
|
|
|
+ <view class="search-box">
|
|
|
+ <uni-icons type="search" size="22" color="#b8b8b8" />
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="keyword"
|
|
|
+ class="search-input"
|
|
|
+ :placeholder="searchPlaceholder"
|
|
|
+ confirm-type="search"
|
|
|
+ @input="handleSearch"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <button class="add-btn" @click="openAddDialog">新增</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="list-wrapper">
|
|
|
+ <view v-for="item in list" :key="item.id" class="list-item" @click="selectItem(item)">
|
|
|
+ <view class="radio" :class="{ 'radio--active': selected?.id === item.id }">
|
|
|
+ <view v-if="selected?.id === item.id" class="radio__dot" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="item-content">
|
|
|
+ <text class="item-name">{{ item.pharmacyName }}</text>
|
|
|
+ <text v-if="item.address" class="item-address">
|
|
|
+ {{ item.address }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <uni-load-more v-if="loading" status="loading" />
|
|
|
+
|
|
|
+ <uni-load-more v-else-if="finished && list.length > 0" status="noMore" />
|
|
|
+
|
|
|
+ <view v-if="!loading && list.length === 0" class="empty"> 暂无数据 </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <wd-popup
|
|
|
+ v-model="showAddDialog"
|
|
|
+ position="center"
|
|
|
+ root-portal
|
|
|
+ round
|
|
|
+ lock-scroll
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ custom-style="width: 656rpx; overflow: hidden; background: #fff; border-radius: 24rpx;"
|
|
|
+ @close="resetAddForm"
|
|
|
+ >
|
|
|
+ <view class="add-dialog">
|
|
|
+ <view class="add-dialog__body">
|
|
|
+ <view class="add-dialog__title">{{ addTitle }}</view>
|
|
|
+
|
|
|
+ <view
|
|
|
+ class="add-dialog__input-wrapper"
|
|
|
+ :class="{ 'add-dialog__input-wrapper--error': Boolean(addError) }"
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ v-model="addName"
|
|
|
+ class="add-dialog__input"
|
|
|
+ :placeholder="addPlaceholder"
|
|
|
+ :focus="showAddDialog"
|
|
|
+ maxlength="100"
|
|
|
+ confirm-type="done"
|
|
|
+ @input="clearAddError"
|
|
|
+ @confirm="confirmAdd"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <text v-if="addError" class="add-dialog__error">
|
|
|
+ {{ addError }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="add-dialog__actions">
|
|
|
+ <view
|
|
|
+ class="add-dialog__action add-dialog__action--cancel"
|
|
|
+ hover-class="add-dialog__action--hover"
|
|
|
+ @click="closeAddDialog"
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view
|
|
|
+ class="add-dialog__action add-dialog__action--confirm"
|
|
|
+ hover-class="add-dialog__action--hover"
|
|
|
+ @click="confirmAdd"
|
|
|
+ >
|
|
|
+ 确认
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </wd-popup>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, getCurrentInstance, reactive, ref } from 'vue'
|
|
|
+
|
|
|
+import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
|
|
+
|
|
|
+import { debounce } from '@/plugins/debounce'
|
|
|
+
|
|
|
+import { listWmDaPharmacyByNameApi } from '@/services/modules/task/entitySelector'
|
|
|
+import type {
|
|
|
+ ListWmDaPharmacyByNameRequest,
|
|
|
+ PharmacyItem,
|
|
|
+} from '@/services/modules/task/entitySelector/type'
|
|
|
+
|
|
|
+const aliasConfig = {
|
|
|
+ pharmacyName: {
|
|
|
+ entityName: '药店',
|
|
|
+ },
|
|
|
+} as const
|
|
|
+
|
|
|
+type PageAlias = keyof typeof aliasConfig
|
|
|
+
|
|
|
+interface PageQuery {
|
|
|
+ alias?: string
|
|
|
+}
|
|
|
+
|
|
|
+const pageAlias = ref<PageAlias>('pharmacyName')
|
|
|
+const keyword = ref('')
|
|
|
+const list = ref<PharmacyItem[]>([])
|
|
|
+const selected = ref<PharmacyItem>()
|
|
|
+const loading = ref(false)
|
|
|
+const finished = ref(false)
|
|
|
+
|
|
|
+const pagination = reactive({
|
|
|
+ current: 1,
|
|
|
+ size: 20,
|
|
|
+})
|
|
|
+
|
|
|
+const showAddDialog = ref(false)
|
|
|
+const addName = ref('')
|
|
|
+const addError = ref('')
|
|
|
+
|
|
|
+let requestVersion = 0
|
|
|
+
|
|
|
+const entityName = computed(() => aliasConfig[pageAlias.value].entityName)
|
|
|
+const searchPlaceholder = computed(() => `搜索${entityName.value}名称`)
|
|
|
+const addTitle = computed(() => `新增${entityName.value}`)
|
|
|
+const addPlaceholder = computed(() => `请输入${entityName.value}名称`)
|
|
|
+
|
|
|
+const loadList = async (reset = false) => {
|
|
|
+ if (!reset && (loading.value || finished.value)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reset) {
|
|
|
+ requestVersion += 1
|
|
|
+ pagination.current = 1
|
|
|
+ list.value = []
|
|
|
+ finished.value = false
|
|
|
+ }
|
|
|
+
|
|
|
+ const version = requestVersion
|
|
|
+ const currentPage = pagination.current
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ const params: ListWmDaPharmacyByNameRequest = {
|
|
|
+ current: currentPage,
|
|
|
+ size: pagination.size,
|
|
|
+ name: keyword.value.trim(),
|
|
|
+ }
|
|
|
+
|
|
|
+ const { data } = await listWmDaPharmacyByNameApi(params)
|
|
|
+
|
|
|
+ if (version !== requestVersion) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const records = data.records ?? []
|
|
|
+
|
|
|
+ list.value = reset ? records : [...list.value, ...records]
|
|
|
+
|
|
|
+ finished.value = currentPage >= data.pages || records.length < pagination.size
|
|
|
+
|
|
|
+ if (!finished.value) {
|
|
|
+ pagination.current = currentPage + 1
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ if (version === requestVersion) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '列表加载失败,请稍后重试',
|
|
|
+ icon: 'none',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ if (version === requestVersion) {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSearch = debounce(
|
|
|
+ () => {
|
|
|
+ void loadList(true)
|
|
|
+ },
|
|
|
+ {
|
|
|
+ wait: 500,
|
|
|
+ leading: false,
|
|
|
+ trailing: true,
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+interface EventChannelLike {
|
|
|
+ emit: (eventName: string, payload?: unknown) => void
|
|
|
+}
|
|
|
+
|
|
|
+interface PageProxyWithEventChannel {
|
|
|
+ getOpenerEventChannel?: () => EventChannelLike | undefined
|
|
|
+}
|
|
|
+const pageInstance = getCurrentInstance()
|
|
|
+
|
|
|
+const getOpenerEventChannel = () => {
|
|
|
+ const proxy = pageInstance?.proxy as PageProxyWithEventChannel | null
|
|
|
+
|
|
|
+ return proxy?.getOpenerEventChannel?.()
|
|
|
+}
|
|
|
+
|
|
|
+const selectItem = (item: PharmacyItem) => {
|
|
|
+ const eventChannel = getOpenerEventChannel()
|
|
|
+ selected.value = item
|
|
|
+
|
|
|
+ eventChannel?.emit('pharmacyNameSelect', item)
|
|
|
+ uni.navigateBack()
|
|
|
+}
|
|
|
+
|
|
|
+const openAddDialog = () => {
|
|
|
+ addName.value = ''
|
|
|
+ addError.value = ''
|
|
|
+ showAddDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const closeAddDialog = () => {
|
|
|
+ showAddDialog.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const resetAddForm = () => {
|
|
|
+ addName.value = ''
|
|
|
+ addError.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+const clearAddError = () => {
|
|
|
+ if (addError.value) {
|
|
|
+ addError.value = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const confirmAdd = () => {
|
|
|
+ const name = addName.value.trim()
|
|
|
+
|
|
|
+ if (!name) {
|
|
|
+ addError.value = `${entityName.value}名称不能为空`
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.$emit('entity-add', {
|
|
|
+ alias: pageAlias.value,
|
|
|
+ name,
|
|
|
+ })
|
|
|
+
|
|
|
+ showAddDialog.value = false
|
|
|
+}
|
|
|
+
|
|
|
+onLoad((query?: PageQuery) => {
|
|
|
+ if (query?.alias === 'pharmacyName') {
|
|
|
+ pageAlias.value = query.alias
|
|
|
+ }
|
|
|
+
|
|
|
+ void loadList(true)
|
|
|
+})
|
|
|
+
|
|
|
+onReachBottom(() => {
|
|
|
+ void loadList()
|
|
|
+})
|
|
|
+
|
|
|
+onPullDownRefresh(async () => {
|
|
|
+ try {
|
|
|
+ await loadList(true)
|
|
|
+ } finally {
|
|
|
+ uni.stopPullDownRefresh()
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.entity-selector-page {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.header {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ left: 0;
|
|
|
+ z-index: 100;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 24rpx;
|
|
|
+ height: 120rpx;
|
|
|
+ padding: 20rpx 32rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1rpx solid #f2f3f5;
|
|
|
+}
|
|
|
+
|
|
|
+.search-box {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ align-items: center;
|
|
|
+ min-width: 0;
|
|
|
+ height: 72rpx;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: #f5f6f7;
|
|
|
+ border-radius: 36rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.search-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ height: 72rpx;
|
|
|
+ margin-left: 16rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.add-btn {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 152rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ line-height: 72rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #fff;
|
|
|
+ background: #3c9cff;
|
|
|
+ border-radius: 36rpx;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ border: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.list-wrapper {
|
|
|
+ padding-top: 120rpx;
|
|
|
+ padding-bottom: env(safe-area-inset-bottom);
|
|
|
+}
|
|
|
+
|
|
|
+.list-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 112rpx;
|
|
|
+ padding: 24rpx 32rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1rpx solid #f2f3f5;
|
|
|
+}
|
|
|
+
|
|
|
+.radio {
|
|
|
+ display: flex;
|
|
|
+ flex-shrink: 0;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 38rpx;
|
|
|
+ height: 38rpx;
|
|
|
+ margin-right: 24rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ border: 2rpx solid #d5d7da;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.radio--active {
|
|
|
+ border-color: #3c9cff;
|
|
|
+}
|
|
|
+
|
|
|
+.radio__dot {
|
|
|
+ width: 20rpx;
|
|
|
+ height: 20rpx;
|
|
|
+ background: #3c9cff;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.item-content {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.item-name,
|
|
|
+.item-address {
|
|
|
+ display: block;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+.item-name {
|
|
|
+ font-size: 32rpx;
|
|
|
+ line-height: 44rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.item-address {
|
|
|
+ margin-top: 8rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ line-height: 38rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.empty {
|
|
|
+ padding: 120rpx 32rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ text-align: center;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog {
|
|
|
+ width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__body {
|
|
|
+ padding: 42rpx 32rpx 32rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__title {
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+ font-size: 36rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 50rpx;
|
|
|
+ text-align: center;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__input-wrapper {
|
|
|
+ height: 82rpx;
|
|
|
+ padding: 0 22rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ border: 2rpx solid #dcdfe6;
|
|
|
+ border-radius: 10rpx;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__input-wrapper--error {
|
|
|
+ border-color: #ee0a24;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__input {
|
|
|
+ width: 100%;
|
|
|
+ height: 78rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__error {
|
|
|
+ display: block;
|
|
|
+ margin-top: 12rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ line-height: 34rpx;
|
|
|
+ color: #ee0a24;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__actions {
|
|
|
+ display: flex;
|
|
|
+ height: 104rpx;
|
|
|
+ border-top: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__action {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 34rpx;
|
|
|
+ line-height: 104rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__action--cancel {
|
|
|
+ color: #666;
|
|
|
+ border-right: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__action--confirm {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #3c9cff;
|
|
|
+}
|
|
|
+
|
|
|
+.add-dialog__action--hover {
|
|
|
+ background: #f5f6f7;
|
|
|
+}
|
|
|
+</style>
|