lifanagju_citu 8 ヶ月 前
コミット
b5d61ea3c7

+ 87 - 0
components/FilterList/index.vue

@@ -0,0 +1,87 @@
+<template>
+  <view class="labelColor itemBox">
+    <view class="item" v-for="item in filterList" :key="item[props.idValue]" @click="handleClick(item)">
+      <view class="">{{ item[labelValue] }}</view>
+      <!-- <icon type="success" size="14"/> -->
+       <view class="iconBox"><uni-icons type="bottom" color="#999" size="18"/></view>
+    </view>
+    <uni-popup ref="popupRef" type="bottom" border-radius="10px 10px 0 0">
+      <view class="popup">
+        <view v-for="val in itemObj?.selectOptions" :key="val[props.selectIdValue]">{{ val[selectLabelValue] }}</view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+<script setup>
+import { ref, watch } from 'vue'
+const props = defineProps({
+  list: { type: Array, default: () => [] },
+  idValue: { type: String, default: 'id' },
+  labelValue: { type: String, default: 'label' },
+  selectIdValue: { type: String, default: 'id' },
+  selectLabelValue: { type: String, default: 'label' },
+  useApiData: { type: Boolean, default: true },
+  lazy: { type: Boolean, default: false },
+})
+
+const popupRef = ref()
+let itemObj = {}
+const handleClick = (item) => {
+  itemObj = item
+  popupRef.value.open('bottom')
+}
+
+const selectData = {
+  行业: [{ label: '行业' }],
+  城市: [{ label: '城市' }],
+  工作性质: [{ label: '工作性质' }],
+  月薪范围: [{ label: '月薪范围' }],
+  工作经验: [{ label: '工作经验' }],
+}
+
+const getData = (e) => {
+  // api
+  e.selectOptions = selectData[e[props.idValue]] || []
+}
+
+const setItemSelectData = () => {
+  filterList.value.forEach(e => {
+    getData(e)
+  })
+}
+
+const filterList = ref([])
+watch(() => props.list, 
+  (newVal) => {
+    filterList.value = newVal ? [...newVal] : []
+    if (filterList.value.length && !props.lazy) setItemSelectData()
+  },
+  { immediate: true },
+  // { deep: true }
+)
+
+</script>
+
+<style scoped lang="scss">
+.labelColor { color: #5c5c5c; }
+.marginR5 { margin-right: 5px; }
+.itemBox {
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  .item {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    margin: 5px 0;
+    .iconBox {
+      margin: 2px 13px 0 4px;
+    }
+  }
+}
+.popup {
+  height: 50vh;
+  background-color: #fff;
+  text-align: center;
+}
+</style>

+ 164 - 0
components/SearchBar/index.vue

@@ -0,0 +1,164 @@
+<template>
+  <view
+    class="search-content ss-flex ss-col-center ss-row-between"
+    @tap="click"
+    :style="[
+      {
+        borderRadius: radius + 'px',
+        background: elBackground,
+        height: height + 'px',
+        width: width,
+      },
+    ]"
+    :class="[{ 'border-content': navbar }]"
+  >
+    <view class="ss-flex ss-col-center" v-if="navbar">
+      <view class="search-icon _icon-search ss-m-l-10" :style="[{ color: props.iconColor }]"></view>
+      <view class="search-input ss-flex-1 ss-line-1" :style="[{ color: fontColor, width: width }]">
+        {{ placeholder }}
+      </view>
+    </view>
+    <uni-search-bar
+      v-if="!navbar"
+      class="ss-flex-1"
+      :radius="data.borderRadius"
+      :placeholder="data.placeholder"
+      cancelButton="none"
+      clearButton="none"
+      @confirm="onSearch"
+      v-model="state.searchVal"
+    />
+    <view class="keyword-link ss-flex">
+      <view v-for="(item, index) in data.hotKeywords" :key="index">
+        <view
+          class="ss-m-r-16"
+          :style="[{ color: data.textColor }]"
+          @tap.stop="sheep.$router.go('/pages/goods/list', { keyword: item })"
+          >{{ item }}</view
+        >
+      </view>
+    </view>
+    <view v-if="data.hotKeywords && data.hotKeywords.length && navbar" class="ss-flex">
+      <button
+        class="ss-reset-button keyword-btn"
+        v-for="(item, index) in data.hotKeywords"
+        :key="index"
+        :style="[{ color: data.textColor, marginRight: '10rpx' }]"
+      >
+        {{ item }}
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  /**
+   * 基础组件 - 搜索栏
+   *
+   * @property {String} elBackground 			- 输入框背景色
+   * @property {String} iconColor 			- 图标颜色
+   * @property {String} fontColor 		  	- 字体颜色
+   * @property {Number} placeholder 			- 默认placeholder
+   * @property {Number} topRadius 			- 组件上圆角
+   * @property {Number} bottomRadius 			- 组件下圆角
+   *
+   * @slot keywords							- 关键字
+   * @event {Function} click 					- 点击组件时触发
+   */
+
+  import { computed, reactive } from 'vue';
+  import sheep from '@/sheep';
+
+  // 组件数据
+  const state = reactive({
+    searchVal: '',
+  });
+
+  // 事件页面
+  const emits = defineEmits(['click']);
+
+  // 接收参数
+  const props = defineProps({
+    data: {
+      type: Object,
+      default: () => ({}),
+    },
+    // 输入框背景色
+    elBackground: {
+      type: String,
+      default: '',
+    },
+    height: {
+      type: Number,
+      default: 36,
+    },
+    // 图标颜色
+    iconColor: {
+      type: String,
+      default: '#b0b3bf',
+    },
+    // 字体颜色
+    fontColor: {
+      type: String,
+      default: '#b0b3bf',
+    },
+    // placeholder
+    placeholder: {
+      type: String,
+      default: '这是一个搜索框',
+    },
+    radius: {
+      type: Number,
+      default: 10,
+    },
+    width: {
+      type: String,
+      default: '100%',
+    },
+    navbar: {
+      type: Boolean,
+      default: true,
+    },
+  });
+
+  // 点击
+  const click = () => {
+    emits('click');
+  };
+
+  function onSearch(e) {
+    if (e.value) {
+      sheep.$router.go('/pages/goods/list', { keyword: e.value });
+      setTimeout(() => {
+        state.searchVal = '';
+      }, 100);
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .border-content {
+    border: 2rpx solid #eee;
+  }
+
+  .search-content {
+    flex: 1;
+    // height: 80rpx;
+    position: relative;
+
+    .search-icon {
+      font-size: 38rpx;
+      margin-right: 20rpx;
+    }
+
+    .keyword-link {
+      position: absolute;
+      right: 16rpx;
+      top: 18rpx;
+    }
+
+    .search-input {
+      font-size: 28rpx;
+    }
+  }
+</style>

+ 48 - 0
components/SwiperAd/index.vue

@@ -0,0 +1,48 @@
+<template>
+	<view v-if="!props.hide" :style="`width: ${props.width}; height: ${props.height};`">
+		<view v-if="props.list?.length">
+			<swiper
+        class="swiper"
+        circular
+        :indicator-dots="props.indicatorDots"
+        :autoplay="props.autoplay"
+        :interval="props.interval"
+        :duration="props.duration"
+      >
+				<swiper-item v-for="(item, index) in list" :key="'swiperItem'+index">
+					<view>
+            <image
+              :mode="strType ? props.mode : item.mode"
+              :src="strType ? item : item[props.imgUrlKey]"
+               :style="`width: ${props.width}; height: ${props.height};`"
+              @error="imageError"
+            ></image>
+          </view>
+				</swiper-item>
+			</swiper>
+		</view>
+	</view>
+</template>
+<script setup>
+const props = defineProps({
+  list: { type: Array, default: () => [] },
+  indicatorDots: { type: Boolean, default: true }, // 是否显示面板指示点
+  autoplay: { type: Boolean, default: true }, // 是否自动切换
+  interval: { type: Number, default: 3000 }, // 自动切换时间间隔
+  duration: { type: Number, default: 500 }, // 滑动动画时长
+  strType: { type: Boolean, default: true }, // 数组类型或者对象类型
+  imgUrlKey: { type: String, default: 'src' },
+  mode: { type: String, default: 'aspectFill' }, // 图片裁剪、缩放的模式。aspectFill保持纵横比缩放图片,只保证图片的短边能完全显示出来
+  hide: { type: Boolean, default: false }, // 隐藏
+  width: { type: String, default: '100%' },
+  height: { type: String, default: '150px' },
+})
+
+const imageError = (e) => {
+  console.error('image发生error事件,携带值为' + e?.detail?.errMsg)
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 47 - 2
pages/index/position.vue

@@ -1,10 +1,55 @@
 <template>
-  <view>position</view>
+  <view class="box">
+    <uni-search-bar
+      class="ss-flex-1 white-bgc"
+      radius="8"
+      placeholder="请输入关键字"
+      cancelButton="none"
+      :focus="false"
+      @confirm="onSearch($event.value)"
+    />
+    <view class="block">
+      <SwiperAd class="pb-10" :list="swiperAdList"></SwiperAd>
+      <FilterList class="pb-10" :list="filterList" idValue="label"></FilterList>
+    </view>
+  </view>
 </template>
 
 <script setup>
+import SwiperAd from '@/components/SwiperAd'
+// import SearchBar from '@/components/SearchBar'
+import FilterList from '@/components/FilterList'
+import { ref } from 'vue'
+
+const swiperAdList = ref([
+  'https://img.bosszhipin.com/beijin/activity/img/20240829/488f35070cc7d0b615328d1e05fe62df4b7d0ebc36568cfb80c0fe17b37418f00945b742138a9e17.jpg.webp',
+  'https://img.bosszhipin.com/beijin/activity/img/20240829/488f35070cc7d0b615328d1e05fe62df4b7d0ebc36568cfb80c0fe17b37418f00945b742138a9e17.jpg.webp',
+  'https://img.bosszhipin.com/beijin/activity/img/20240829/488f35070cc7d0b615328d1e05fe62df4b7d0ebc36568cfb80c0fe17b37418f00945b742138a9e17.jpg.webp',
+])
+const filterList = ref([
+  { label: '行业' },
+  { label: '城市' },
+  { label: '工作性质' },
+  { label: '月薪范围' },
+  { label: '工作经验' },
+])
+
+const onSearch = (val) => {
+  console.log('onSearch', val)
+}
+
 </script>
 
 <style scoped lang="scss">
-
+.pb-10 { padding-bottom: 10px; }
+.box {
+  height: 100vh;
+  background-color: #f2f4f7;
+  // background-color: var(--default-bgc);
+  .block{
+    margin-bottom: 10px;
+    padding: 0 10px;
+    background-color: #fff;
+  }
+}
 </style>