9433cfb9创建于 2025年12月31日历史提交
<template>
  <waterflow :id="id" class="grid" :bounces="false" :scroll-y="true" :custom-nested-scroll="true"
  	main-axis-gap="8px"	cross-axis-gap="8px" cross-axis-count=2
    @scrolltolower="loadData(null)" associative-container="nested-scroll-view">
    <flow-item class="flow-item" v-for="(item, _) in datagrid" :key="item.plugin_id" type=1>
      <view class="flow-item-icon">
        <image class="flow-item-icon-image" :src="item.plugin_img_link" mode="aspectFit"></image>
      </view>
      <view class="flow-item-fill">
        <view class="flex-row">
          <text class="title">{{item.plugin_name}}</text>
        </view>
        <view>
          <text class="description-text">{{item.plugin_intro}}</text>
        </view>
        <text class="icon-star">{{convertToStarUnicode(item.score)}}</text>
        <view class="tag-grid">
          <text class="tag-item" v-for="(item2, index2) in item.tags.slice(0,1)" :key="index2">{{item2}}</text>
        </view>
        <view class="flex-row update-date">
          <text class="author">{{item.author_name}}</text>
        </view>
        <view class="flex-row update-date">
          <text class="update-date-text">更新日期</text>
          <text class="update-date-value">{{item.update_date}}</text>
        </view>
      </view>
    </flow-item type="20">
    <flow-item slot="load-more" class="loading" type=6>
      <uni-loading :loading="loading" color="#999" :text="loadingText"></uni-loading>
    </flow-item>
  </waterflow>
</template>

<script setup lang="ts">
  const SERVER_URL = "https://unidemo.dcloud.net.cn/plugin/uniappx-plugin-list"
  const PAGE_SIZE = 10; // 最大值 10

  type flowItem = {
    plugin_id : number,
    plugin_img_link : string,
    plugin_name : string,
    plugin_intro : string,
    score : number,
    tags : Array<string>,
    update_date : string,
    author_name : string,
  }

  type ResponseDataType = {
    code : number,
    data : flowItem[]
  }

  const props = defineProps({
    type: { type: String, default: '' },
    preload: { type: Boolean, default: false },
    id: { type: String, default: '' }
  })

  const loading = ref(false);
  const datagrid = ref([] as flowItem[]);
  const isEnded = ref(false);
  const loadingError = ref('');
  const currentPage = ref(1);

  const loadingText = computed(() : string => {
    if (loading.value) {
      return "加载中..."
    } else if (isEnded.value) {
      return "没有更多了"
    } else if (loadingError.value.length > 0) {
      return loadingError.value
    } else {
      return ""
    }
  });

  function loadData(loadComplete : (() => void) | null) {
    if (loading.value || isEnded.value) {
      return
    }
    loading.value = true

    uni.request<ResponseDataType>({
      url: SERVER_URL,
      data: {
        type: props.type,
        page: currentPage.value,
        page_size: PAGE_SIZE
      },
      success: (res) => {
        const responseData = res.data
        if (responseData == null) {
          return
        }

        datagrid.value.push(...responseData.data)

        if (responseData.data.length == 0) {
          isEnded.value = true
        } else {
          currentPage.value++
        }
      },
      fail: (err) => {
        loadingError.value = err.errMsg
      },
      complete: () => {
        loading.value = false
        if (loadComplete != null) {
          loadComplete()
        }
      }
    })
  }

  function refreshData(loadComplete : (() => void) | null) {
    datagrid.value.length = 0
    currentPage.value = 1
    loadData(loadComplete)
  }

  // score 0 ~ 50
  function convertToStarUnicode(score : number) : string {
    const fill_code = '\ue879'
    const half_code = '\ue87a'
    const null_code = '\ue87b'

    const fillStarCount = parseInt(score / 10 % 10 + '')
    const halfStarCount = score % 10 >= 5 ? 1 : 0
    const nullStarCount = 5 - fillStarCount - halfStarCount

    let result = ''
    if (fillStarCount > 0) { result += fill_code.repeat(fillStarCount) }
    if (halfStarCount > 0) { result += half_code.repeat(halfStarCount) }
    if (nullStarCount > 0) { result += null_code.repeat(nullStarCount) }

    return result
  }

  onMounted(() => {
    if (props.preload) {
      loadData(null)
    }
  });

  defineExpose({
    refreshData,
    loadData
  });
</script>

<style>
  @font-face {
    font-family: "UtsStarIcons";
    src: url('@/static/fonts/icon-star.ttf');
  }

  .grid {
    flex: 1;
    background-color: #f7f7f7;
  }

  .flow-item {
    flex-direction: column;
    border-radius: 5px;
    background-color: #ffffff;
  }

  .flow-item-icon {
    position: relative;
  }

  .flow-item-icon-image {
    width: 100%;
  }

  .flow-item-fill {
    flex: 1;
    padding: 5px;
  }

  .description-text {
    font-size: 13px;
    color: #666;
    line-height: 19px;
  }

  .icon-star {
    font-family: "UtsStarIcons";
    font-size: 16px;
    font-style: normal;
    color: #ffca3e;
    letter-spacing: 3px;
  }

  .tag-grid {
    flex-direction: row;
    margin-top: 5px;
  }

  .tag-item {
    font-size: 12px;
    background-color: #EFF9F0;
    color: #639069;
    border-radius: 20px;
    margin-right: 5px;
    padding: 2px 5px;
  }

  .update-date {
    margin-top: 10px;
  }

  .update-date-text {
    font-size: 12px;
    color: #888888;
  }

  .update-date-value {
    font-size: 12px;
    color: #777777;
    margin-left: 5px;
  }

  .author {
    font-size: 12px;
    color: #008000;
  }

  .loading {
    padding: 30px;
    align-items: center;
    height: 80px;
  }

  .flex-row {
    flex-direction: row;
  }
</style>