| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- <template>
- <view class="universal-list-page">
- <!-- 筛选表单和操作区域 -->
- <view class="filter-section" v-if="fieldsList.length > 0 || showScopeFilter || buttonList.length > 0">
- <view class="filter-row">
- <!-- 有 cbName 的字段用 SsSelect 下拉选择器 -->
- <SsSelect
- v-for="field in fieldsList"
- :key="field.name"
- v-model="searchParams[field.name]"
- :placeholder="field.desc"
- :options="fieldOptions[field.name] || []"
- :loading="fieldLoading[field.name]"
- width="auto"
- minWidth="200rpx"
- class="filter-select"
- @change="(value) => handleFieldChange(field.name, value)"
- />
- <!-- 管理类别筛选(条件:isReady=="1" && !isMultipleObject) -->
- <SsSearchButton
- v-if="showScopeFilter"
- :text="scopeText"
- :options="scopeOptions"
- @change="handleScopeChange"
- />
- <!-- 操作按钮 -->
- <SsSearchButton
- v-for="(btn, index) in buttonList"
- :key="index"
- :text="btn.buttonName"
- @click="handleButtonClick(btn)"
- />
- </view>
- </view>
- <!-- 列表区域 -->
- <view class="list-section">
- <view v-if="loading && list.length === 0" class="loading-state">
- <text>加载中...</text>
- </view>
-
- <view v-else-if="list.length === 0" class="empty-state">
- <text>暂无数据</text>
- </view>
-
- <view v-else>
- <SsCard
- v-for="(item, index) in list"
- :key="item.uniqueId || index"
- :item="item"
- @click="handleItemClick(item)"
- >
- <!-- 动态渲染卡片内容 -->
- <view class="card-content">
- <!-- 主标题 (first) -->
- <view class="card-header" v-if="item.first">
- <view class="card-title">
- {{ formatFieldValueWrapper(item.first) }}
- </view>
- </view>
- <!-- 描述 (second) -->
- <view class="card-description" v-if="item.second">
- {{ formatFieldValueWrapper(item.second) }}
- </view>
- <!-- 属性列表 (third) -->
- <view class="card-attributes" v-if="item.third && item.third.length > 0">
- <view
- v-for="(group, groupIndex) in item.third"
- :key="groupIndex"
- class="attribute-group"
- >
- <view
- v-for="(attr, attrIndex) in group"
- :key="attrIndex"
- class="attribute-item"
- >
- <text class="attr-label">{{ attr.field.desc }}:</text>
- <text class="attr-value">{{ formatFieldValueWrapper(attr) }}</text>
- </view>
- </view>
- </view>
- </view>
- </SsCard>
- </view>
- </view>
- <!-- 加载更多提示 -->
- <view v-if="loading && list.length > 0" class="load-more-tip">
- <text>加载中...</text>
- </view>
-
- <view v-else-if="!hasMore && list.length > 0" class="no-more-tip">
- <text>没有更多数据了</text>
- </view>
- </view>
- </template>
- <script setup>
- import { computed, ref } from 'vue'
- import { onReachBottom, onLoad } from '@dcloudio/uni-app'
- import SsSearchButton from '@/components/SsSearchButton/index.vue'
- import SsCard from '@/components/SsCard/index.vue'
- import SsSelect from '@/components/SsSelect/index.vue'
- import { commonApi } from '@/api/common'
- import { formatFieldValue, getDictTranslation } from '@/utils/fieldFormatter'
- import { goTo } from '@/utils/navigation'
- // 页面参数(从路由传入)
- const pageConfig = ref({
- ssServ: '', // 服务名称
- title: '', // 页面标题
- baseParams: {}, // 基础查询参数
- })
- // 列表数据
- const list = ref([])
- const buttonList = ref([])
- const fieldsList = ref([])
- const loading = ref(false)
- const hasMore = ref(true)
- const currentPage = ref(1)
- const pageSize = ref(10)
- // 搜索相关
- const searchParams = ref({management:99})
- const fieldOptions = ref({}) // 存储每个字段的选项
- const fieldLoading = ref({}) // 存储每个字段的加载状态
- // 管理类别相关
- const showScopeFilter = ref(false) // 是否显示管理类别筛选
- const scopeOptions = ref([
- { text: '所有', value: 99 },
- { text: '管理', value: 2 },
- { text: '创建', value: 1 },
- { text: '已办', value: 3 },
- { text: '停用', value: 55 }
- ])
- const scopeText = computed(() => {
- const selectedOption = scopeOptions.value.find(option => option.value === searchParams.value.management)
- return selectedOption ? selectedOption.text : '所有'
- })
- // 字典缓存
- const dictCache = ref(new Map())
- /**
- * 格式化字段值的包装函数
- */
- const formatFieldValueWrapper = (fieldObj) => {
- return formatFieldValue(fieldObj, dictCache.value, handleDictTranslation)
- }
- /**
- * 处理字典翻译的包装函数
- */
- const handleDictTranslation = (cbName, value, cacheKey) => {
- getDictTranslation(cbName, value, cacheKey, dictCache.value, () => {
- // 触发页面更新
- list.value = [...list.value]
- })
- }
- /**
- * 加载字段选项
- */
- const loadFieldOptions = async (fields) => {
- for (const field of fields) {
- if (field.cbName) {
- // 有 cbName 的字段需要加载下拉选项
- fieldLoading.value[field.name] = true
- try {
- const result = await commonApi.getDictOptionsByCbName(field.cbName)
- console.log(`字段 ${field.name} 字典数据:`, result)
- if (result.data && result.data.result) {
- const resultData = result.data.result
- // 转换为 SsSelect 需要的格式:[{n:"显示名称", v:"值"}]
- const options = Object.keys(resultData).map(key => ({
- n: resultData[key], // 显示名称
- v: key // 值
- }))
- // 在前面插入清空选项
- fieldOptions.value[field.name] = [
- { n: "全部", v: "" }, // 清空条件的选项
- ...options
- ]
- }
- } catch (error) {
- console.error(`加载字段 ${field.name} 选项失败:`, error)
- fieldOptions.value[field.name] = []
- } finally {
- fieldLoading.value[field.name] = false
- }
- } else {
- // 没有 cbName 的字段暂时不处理(文本输入、日期等)
- fieldOptions.value[field.name] = []
- }
- }
- }
- /**
- * 加载数据
- */
- const loadData = async (pageNo = 1, isLoadMore = false) => {
- if (loading.value) return
-
- loading.value = true
-
- try {
- const params = {
- pageNo,
- rowNumPer: pageSize.value,
- ...pageConfig.value.baseParams,
- ...searchParams.value
- }
- const result = await commonApi.universalQuery(pageConfig.value.ssServ, params)
- console.log(`通用列表查询结果 [${pageConfig.value.ssServ}]:`, result)
-
- if (result.data) {
- const {
- objectList = [],
- ssPaging,
- buttonList: buttons = [],
- fieldsList: fields = [],
- isReady = "0",
- isMultipleObject = false
- } = result.data
- if (isLoadMore) {
- list.value = [...list.value, ...objectList]
- } else {
- list.value = objectList
- buttonList.value = buttons
- fieldsList.value = fields
- // 判断是否显示管理类别筛选:isReady=="1" && !isMultipleObject
- showScopeFilter.value = isReady == "1" && !isMultipleObject && true
- console.log('管理类别筛选判断:', { isReady, isMultipleObject, showScopeFilter: showScopeFilter.value })
- // 加载字段选项
- await loadFieldOptions(fields)
- }
- // 更新分页信息
- if (ssPaging) {
- currentPage.value = ssPaging.pageNo
- hasMore.value = (ssPaging.pageNo * ssPaging.rowNumPer) < ssPaging.rowNum
- }
-
- }
- } catch (error) {
- console.error('加载数据失败:', error)
- uni.showToast({
- title: '加载失败',
- icon: 'error'
- })
- } finally {
- loading.value = false
- }
- }
- /**
- * 加载更多数据
- */
- const loadMore = async () => {
- if (!hasMore.value || loading.value) return
-
- await loadData(currentPage.value + 1, true)
- }
- /**
- * 搜索字段值改变
- */
- const handleFieldChange = (fieldName, value) => {
- searchParams.value[fieldName] = value
- console.log(`搜索字段 ${fieldName} 改变:`, value)
- // 立即执行搜索
- handleSearch()
- }
- /**
- * 管理类别改变
- */
- const handleScopeChange = (option) => {
- searchParams.value.management = option.value
- console.log('管理类别改变:', option)
- // 立即执行搜索
- handleSearch()
- }
- /**
- * 执行搜索
- */
- const handleSearch = () => {
- console.log('执行搜索,参数:', searchParams.value)
- // 重置页码并重新加载数据
- currentPage.value = 1
- loadData(1, false)
- }
- /**
- * 操作按钮点击
- */
- const handleButtonClick = (button) => {
- console.log('操作按钮点击2:', button)
- const parts = button.function.dest.split('_')
- const dir = parts[0]
- const mobilePage = `/pages/${dir}/${button.function.dest}`
-
- goTo(mobilePage)
- }
- /**
- * 列表项点击
- */
- const handleItemClick = (item) => {
- // 如果有service配置,执行相应操作
- if (item.service && item.service.play) {
- // const { service: serviceName, param } = item.service.play
- // console.log(`执行服务: ${serviceName}`, param)
- goTo(`/pages/clyy/clyy_play`)
- // TODO: 根据service配置跳转到相应页面
- }
- }
- // 使用onLoad获取页面参数
- onLoad((options) => {
- console.log('页面参数:', options)
- // 解析参数
- pageConfig.value = {
- ssServ: options.ssServ || '',
- title: options.title || '通用列表',
- baseParams: options.baseParams ? JSON.parse(decodeURIComponent(options.baseParams)) : {}
- }
- console.log('解析后的页面配置:', pageConfig.value)
- // 加载数据
- if (pageConfig.value.ssServ) {
- loadData()
- } else {
- console.error('缺少必要参数: ssServ')
- uni.showToast({
- title: '参数错误',
- icon: 'error'
- })
- }
- })
- // 使用uni-app的页面生命周期监听滚动到底部
- onReachBottom(() => {
- loadMore()
- })
- </script>
- <style lang="scss" scoped>
- ss-select{
- width: auto !important;
- }
- .universal-list-page {
- min-height: 100vh;
- background-color: #f5f5f5;
- }
- .page-header {
- background: #fff;
- padding: 20rpx 30rpx;
- border-bottom: 1rpx solid #eee;
-
- .page-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- }
- }
- .filter-section {
- padding: 20rpx 30rpx;
- // margin-bottom: 20rpx;
- .filter-row {
- display: flex;
- justify-content: flex-end;
- align-items: flex-end;
- flex-wrap: wrap;
- gap: 20rpx;
- }
- // SsSelect 在筛选区域的样式
- .filter-select {
-
- :deep(.ss-select) {
- height:36px;
- border: 1rpx solid #ddd;
- border-radius: 8rpx;
- padding: 15rpx 20rpx;
- background: #fff;
- min-height: auto;
- }
- }
- }
- .list-section {
- padding: 0 30rpx;
- }
- .card-content {
- .card-header {
- margin-bottom: 20rpx;
-
- .card-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- }
-
- .card-description {
- font-size: 28rpx;
- color: #666;
- margin-bottom: 15rpx;
- }
-
- .attribute-group {
- display: flex;
- flex-wrap: wrap;
- column-gap: 20rpx;
- }
-
- .attribute-item {
- display: flex;
- margin-bottom: 10rpx;
-
- .attr-label {
- font-size: 26rpx;
- color: #999;
- }
-
- .attr-value {
- font-size: 26rpx;
- color: #333;
- flex: 1;
- }
- }
- }
- .loading-state,
- .empty-state {
- text-align: center;
- padding: 100rpx 0;
- color: #999;
- }
- .load-more-tip,
- .no-more-tip {
- text-align: center;
- padding: 30rpx 0;
- color: #999;
- font-size: 24rpx;
- }
- </style>
|