|
|
@@ -11,10 +11,10 @@
|
|
|
<!-- 选项列表 -->
|
|
|
<view class="ss-options" v-show="isOpen">
|
|
|
<!-- 加载状态 -->
|
|
|
- <view
|
|
|
- v-if="loading"
|
|
|
- class="option-item loading-item"
|
|
|
- >
|
|
|
+ <view
|
|
|
+ v-if="finalLoading"
|
|
|
+ class="option-item loading-item"
|
|
|
+ >
|
|
|
<text class="loading-text">加载中...</text>
|
|
|
</view>
|
|
|
<!-- 无选项 -->
|
|
|
@@ -41,9 +41,10 @@
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
-<script setup>
|
|
|
-import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|
|
-import Icon from '@/components/icon/index.vue';
|
|
|
+<script setup>
|
|
|
+import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|
|
+import request from '@/utils/request'
|
|
|
+import Icon from '@/components/icon/index.vue';
|
|
|
// Props 定义
|
|
|
const props = defineProps({
|
|
|
// 选项数组
|
|
|
@@ -96,22 +97,135 @@ const props = defineProps({
|
|
|
type: String,
|
|
|
default: '100%'
|
|
|
},
|
|
|
- minWidth:{
|
|
|
- type:String,
|
|
|
- default:'unset'
|
|
|
- }
|
|
|
-})
|
|
|
+ minWidth:{
|
|
|
+ type:String,
|
|
|
+ default:'unset'
|
|
|
+ },
|
|
|
+ // 对齐PC端 ss-objp:支持组件内部按 codebook 拉取下拉选项
|
|
|
+ cb: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ url: {
|
|
|
+ type: String,
|
|
|
+ default: '/service?ssServ=loadObjpOpt&objectpickerdropdown1=1'
|
|
|
+ },
|
|
|
+ inp: {
|
|
|
+ type: [Boolean, String],
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ filter: {
|
|
|
+ type: [Object, String],
|
|
|
+ default: null
|
|
|
+ },
|
|
|
+ autoSelectFirst: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ }
|
|
|
+})
|
|
|
|
|
|
// Emits 定义
|
|
|
const emit = defineEmits(['update:modelValue', 'change', 'search', 'clear'])
|
|
|
|
|
|
// 响应式数据
|
|
|
-const isOpen = ref(false)
|
|
|
-const selectedValue = ref(props.modelValue)
|
|
|
-const searchKeyword = ref('')
|
|
|
-
|
|
|
-// 计算属性
|
|
|
-const optionsList = computed(() => props.options || [])
|
|
|
+const isOpen = ref(false)
|
|
|
+const selectedValue = ref(props.modelValue)
|
|
|
+const searchKeyword = ref('')
|
|
|
+const remoteOptions = ref([])
|
|
|
+const internalLoading = ref(false)
|
|
|
+
|
|
|
+const parseFilterObj = () => {
|
|
|
+ if (!props.filter) return {}
|
|
|
+ if (typeof props.filter === 'object') return props.filter
|
|
|
+ if (typeof props.filter === 'string') {
|
|
|
+ try {
|
|
|
+ const obj = JSON.parse(props.filter)
|
|
|
+ return obj && typeof obj === 'object' ? obj : {}
|
|
|
+ } catch (_) {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {}
|
|
|
+}
|
|
|
+
|
|
|
+const normalizeResultToOptions = (respData) => {
|
|
|
+ const raw = respData || {}
|
|
|
+ if (Array.isArray(raw.resultList)) {
|
|
|
+ return raw.resultList.map((it) => {
|
|
|
+ if (it && typeof it === 'object') return it
|
|
|
+ return { n: String(it || ''), v: String(it || '') }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (raw.result && typeof raw.result === 'object') {
|
|
|
+ return Object.keys(raw.result).map((key) => ({
|
|
|
+ n: raw.result[key],
|
|
|
+ v: key
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ if (Array.isArray(raw.objectList)) {
|
|
|
+ return raw.objectList.map((it) => {
|
|
|
+ if (it && typeof it === 'object') return it
|
|
|
+ return { n: String(it || ''), v: String(it || '') }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return []
|
|
|
+}
|
|
|
+
|
|
|
+const maybeAutoSelectFirst = (opts) => {
|
|
|
+ if (!props.autoSelectFirst) return
|
|
|
+ if (!Array.isArray(opts) || opts.length === 0) return
|
|
|
+ if (selectedValue.value !== undefined && selectedValue.value !== null && selectedValue.value !== '') return
|
|
|
+ const first = opts[0]
|
|
|
+ if (!first || typeof first !== 'object') return
|
|
|
+ const value = first[props.mapping.value]
|
|
|
+ if (value === undefined || value === null || value === '') return
|
|
|
+ selectedValue.value = value
|
|
|
+ emit('update:modelValue', value)
|
|
|
+ emit('change', value, first)
|
|
|
+}
|
|
|
+
|
|
|
+const needRemoteData = computed(() => !!String(props.cb || '').trim())
|
|
|
+
|
|
|
+const loadRemoteOptions = async () => {
|
|
|
+ if (!needRemoteData.value) return
|
|
|
+
|
|
|
+ internalLoading.value = true
|
|
|
+ try {
|
|
|
+ const data = {
|
|
|
+ objectpickerparam: JSON.stringify({
|
|
|
+ input: String(props.inp === true || props.inp === 'true'),
|
|
|
+ codebook: String(props.cb || ''),
|
|
|
+ ...parseFilterObj()
|
|
|
+ }),
|
|
|
+ objectpickertype: 1,
|
|
|
+ objectpickersearchAll: 1
|
|
|
+ }
|
|
|
+ const resp = await request.post(
|
|
|
+ props.url || '/service?ssServ=loadObjpOpt&objectpickerdropdown1=1',
|
|
|
+ data,
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ formData: true,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ const opts = normalizeResultToOptions(resp?.data)
|
|
|
+ remoteOptions.value = opts
|
|
|
+ maybeAutoSelectFirst(opts)
|
|
|
+ } catch (error) {
|
|
|
+ remoteOptions.value = []
|
|
|
+ console.error('[SsSelect] loadRemoteOptions failed', props.cb, error)
|
|
|
+ } finally {
|
|
|
+ internalLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 计算属性
|
|
|
+const optionsList = computed(() => {
|
|
|
+ if (Array.isArray(props.options) && props.options.length > 0) return props.options
|
|
|
+ if (needRemoteData.value) return remoteOptions.value
|
|
|
+ return []
|
|
|
+})
|
|
|
+const finalLoading = computed(() => !!props.loading || internalLoading.value)
|
|
|
|
|
|
const displayText = computed(() => {
|
|
|
if (!selectedValue.value) return props.placeholder
|
|
|
@@ -182,12 +296,22 @@ const handleGlobalClose = () => {
|
|
|
closeDropdown()
|
|
|
}
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- uni.$on('closeAllSelects', handleGlobalClose)
|
|
|
-
|
|
|
- // 点击页面其他地方关闭下拉框
|
|
|
- uni.$on('pageClick', closeDropdown)
|
|
|
-})
|
|
|
+onMounted(() => {
|
|
|
+ uni.$on('closeAllSelects', handleGlobalClose)
|
|
|
+
|
|
|
+ // 点击页面其他地方关闭下拉框
|
|
|
+ uni.$on('pageClick', closeDropdown)
|
|
|
+
|
|
|
+ loadRemoteOptions()
|
|
|
+})
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [props.cb, props.url, props.filter],
|
|
|
+ () => {
|
|
|
+ if (!needRemoteData.value) return
|
|
|
+ loadRemoteOptions()
|
|
|
+ }
|
|
|
+)
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
uni.$off('closeAllSelects', handleGlobalClose)
|
|
|
@@ -198,13 +322,14 @@ onUnmounted(() => {
|
|
|
defineExpose({
|
|
|
setValue,
|
|
|
getValue: () => selectedValue.value,
|
|
|
- getSelectedOption: () => {
|
|
|
- return optionsList.value.find(
|
|
|
- option => option[props.mapping.value] === selectedValue.value
|
|
|
- )
|
|
|
- }
|
|
|
-})
|
|
|
-</script>
|
|
|
+ getSelectedOption: () => {
|
|
|
+ return optionsList.value.find(
|
|
|
+ option => option[props.mapping.value] === selectedValue.value
|
|
|
+ )
|
|
|
+ },
|
|
|
+ reloadOptions: loadRemoteOptions
|
|
|
+})
|
|
|
+</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
/* ss暂用下拉框样式 */
|