浏览代码

添加组件

lex-xin 3 月之前
父节点
当前提交
45e0eaa617
共有 52 个文件被更改,包括 2287 次插入10 次删除
  1. 2 1
      miniprogram/app.json
  2. 7 0
      miniprogram/common-components/w-area/index.json
  3. 1 0
      miniprogram/common-components/w-area/index.less
  4. 213 0
      miniprogram/common-components/w-area/index.ts
  5. 3 0
      miniprogram/common-components/w-area/index.wxml
  6. 7 0
      miniprogram/common-components/w-checkbox/index.json
  7. 39 0
      miniprogram/common-components/w-checkbox/index.less
  8. 67 0
      miniprogram/common-components/w-checkbox/index.ts
  9. 18 0
      miniprogram/common-components/w-checkbox/index.wxml
  10. 7 0
      miniprogram/common-components/w-datetime-picker/index.json
  11. 66 0
      miniprogram/common-components/w-datetime-picker/index.less
  12. 187 0
      miniprogram/common-components/w-datetime-picker/index.ts
  13. 4 0
      miniprogram/common-components/w-datetime-picker/index.wxml
  14. 6 0
      miniprogram/common-components/w-dialog/index.json
  15. 105 0
      miniprogram/common-components/w-dialog/index.less
  16. 112 0
      miniprogram/common-components/w-dialog/index.ts
  17. 17 0
      miniprogram/common-components/w-dialog/index.wxml
  18. 4 0
      miniprogram/common-components/w-empty/index.json
  19. 22 0
      miniprogram/common-components/w-empty/index.less
  20. 33 0
      miniprogram/common-components/w-empty/index.ts
  21. 6 0
      miniprogram/common-components/w-empty/index.wxml
  22. 6 0
      miniprogram/common-components/w-field/index.json
  23. 72 0
      miniprogram/common-components/w-field/index.less
  24. 145 0
      miniprogram/common-components/w-field/index.ts
  25. 33 0
      miniprogram/common-components/w-field/index.wxml
  26. 9 0
      miniprogram/common-components/w-picker/index.json
  27. 66 0
      miniprogram/common-components/w-picker/index.less
  28. 161 0
      miniprogram/common-components/w-picker/index.ts
  29. 7 0
      miniprogram/common-components/w-picker/index.wxml
  30. 二进制
      miniprogram/common-components/w-radio/images/icon-active.png
  31. 二进制
      miniprogram/common-components/w-radio/images/icon-normal.png
  32. 7 0
      miniprogram/common-components/w-radio/index.json
  33. 34 0
      miniprogram/common-components/w-radio/index.less
  34. 51 0
      miniprogram/common-components/w-radio/index.ts
  35. 7 0
      miniprogram/common-components/w-radio/index.wxml
  36. 10 0
      miniprogram/common-components/w-search-picker/index.json
  37. 86 0
      miniprogram/common-components/w-search-picker/index.less
  38. 165 0
      miniprogram/common-components/w-search-picker/index.ts
  39. 19 0
      miniprogram/common-components/w-search-picker/index.wxml
  40. 二进制
      miniprogram/common-components/w-search/images/icon-search.png
  41. 6 0
      miniprogram/common-components/w-search/index.json
  42. 52 0
      miniprogram/common-components/w-search/index.less
  43. 69 0
      miniprogram/common-components/w-search/index.ts
  44. 5 0
      miniprogram/common-components/w-search/index.wxml
  45. 1 1
      miniprogram/config.ts
  46. 1 1
      miniprogram/pages/buyerInformation/index.wxml
  47. 15 0
      miniprogram/pages/test-components/index.json
  48. 32 0
      miniprogram/pages/test-components/index.less
  49. 223 0
      miniprogram/pages/test-components/index.ts
  50. 64 0
      miniprogram/pages/test-components/index.wxml
  51. 8 0
      miniprogram/pages/test-components/select.wxs
  52. 7 7
      project.private.config.json

+ 2 - 1
miniprogram/app.json

@@ -8,7 +8,8 @@
     "pages/orders/order-result",
     "pages/protocol/register",
     "pages/download/download",
-    "pages/buyerInformation/index"
+    "pages/buyerInformation/index",
+    "pages/test-components/index"
   ],
   "window": {
     "navigationBarTextStyle": "black",

+ 7 - 0
miniprogram/common-components/w-area/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-popup": "@vant/weapp/popup/index",
+    "van-area": "@vant/weapp/area/index"
+  }
+}

+ 1 - 0
miniprogram/common-components/w-area/index.less

@@ -0,0 +1 @@
+/* components/w-area/index.wxss */

+ 213 - 0
miniprogram/common-components/w-area/index.ts

@@ -0,0 +1,213 @@
+// components/w-area/index.ts
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    show: {
+      type: Boolean,
+      value: false,
+      observer: "_show",
+    },
+    /** 选中的数据 */
+    value: {
+      type: Object as any,
+      value: {},
+      observer: "_value",
+    },
+    /**
+     * 示例
+     * [{
+     *  id: xxx,
+     *  code: xxx,
+     *  name: xxx,
+     *  area: [{
+     *    id: xxx,
+     *    code: xxx,
+     *    name: xxx,
+     *    area: []
+     *  }]
+     * }]
+     */
+    columns: {
+      type: Array as any,
+      value: [],
+      observer: "_columns",
+    },
+    zIndex: {
+      type: Number,
+      value: 100,
+    },
+    // 数据分隔符
+    separator: {
+      type: String,
+      value: "/",
+    },
+    /**
+     * 可见的选项个数
+     */
+    visibleItemCount: {
+      type: Number,
+      value: 6,
+    },
+    itemHeight: {
+      type: Number,
+      value: 46,
+    },
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    innerShow: false,
+    showAfterLeave: false,
+    areaList: [] as any,
+    cacheArea: [] as { cityCode: string; shiftCityCode: string }[], // 临时存储的对应关系
+    provinceCode: null as any, // 省
+    cityCode: null, // 市
+    regionCode: null, // 区
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    submitArea(event: any) {
+      const selectedOptions: any = event.detail.values || [];
+      const provinceCode = selectedOptions[0].code || null;
+      const provinceName = selectedOptions[0].name || "";
+      const cityCode = selectedOptions[1].code || null;
+      const cityName = selectedOptions[1].name || "";
+      const regionCode = selectedOptions[2]?.code || null;
+      const regionName = selectedOptions[2]?.name || "";
+      const separator = this.data.separator;
+
+      const formatAfterCityCode = this.formateCityCode(regionCode, cityCode);
+
+      const codes: any = {
+        provinceCode,
+        cityCode: formatAfterCityCode,
+        regionCode,
+      };
+      this.setData({
+        show: false,
+        value: codes,
+      });
+
+      this.triggerEvent("input", codes);
+      this.triggerEvent("confirm", {
+        ...codes,
+        name:
+          (provinceName || "") +
+          (cityName ? separator + cityName : "") +
+          (regionName ? separator + regionName : ""),
+      });
+    },
+    onClose() {
+      this.triggerEvent("cancel");
+    },
+    onAfterLeave() {
+      this.setData({
+        showAfterLeave: true,
+      });
+    },
+    onBeforeEnter() {
+      this.setData({
+        showAfterLeave: false,
+      });
+    },
+    formateArea(area: any[]) {
+      const province_list: { [_: string]: string } = {};
+      const city_list: { [_: string]: string } = {};
+      const county_list: { [_: string]: string } = {};
+      area.forEach((item: any) => {
+        province_list[item.code] = item.name;
+      });
+      area.forEach((item: any) => {
+        item.areas &&
+          item.areas.forEach((city: any, index: number) => {
+            let code = city.code + "";
+            // 某些数据不标准 这里需要转换一下
+            if (code[4] !== "0" || code[5] !== "0") {
+              // 现在把区域的数据改为市的
+              const newCode =
+                code.substring(0, 2) +
+                (index < 10
+                  ? `a${index}`
+                  : index < 20
+                  ? `b${index - 10}`
+                  : index < 30
+                  ? `c${index - 20}`
+                  : `d${index - 30}`) +
+                "00";
+              this.data.cacheArea.push({
+                cityCode: code,
+                shiftCityCode: newCode,
+              });
+              code = newCode;
+            }
+            city_list[code] = city.name;
+          });
+      });
+      area.forEach((item: any) => {
+        item.areas &&
+          item.areas.forEach((city: any) => {
+            city.areas &&
+              city.areas.forEach((county: any) => {
+                county_list[county.code] = county.name;
+              });
+          });
+      });
+      return {
+        province_list,
+        city_list,
+        county_list,
+      };
+    },
+    // 转换
+    formateCityCode(
+      regionCode: string | null,
+      cityCode: string | null,
+      reverse?: boolean
+    ) {
+      if (!regionCode && cityCode) {
+        const cityCodeObj = this.data.cacheArea.find((item: any) => {
+          return item[reverse ? "cityCode" : "shiftCityCode"] == cityCode;
+        });
+        return cityCodeObj
+          ? cityCodeObj[reverse ? "shiftCityCode" : "cityCode"]
+          : "";
+      }
+      return cityCode;
+    },
+    formatValue() {
+      const value = this.data.value;
+      if(!value || value && !value.provinceCode) return
+      this.setData({
+        provinceCode: value.provinceCode || null,
+        cityCode: (this.formateCityCode(value.regionCode, value.cityCode, true) ||
+          null) as any,
+        regionCode: value.regionCode,
+      });
+    },
+    _show() {
+      this.setData({
+        innerShow: this.data.show,
+      });
+    },
+    _value() {
+      this.formatValue()
+    },
+    // 数组初始化,为了处理只有两层数据
+    _columns() {
+      if (this.data.columns.length <= 0) return;
+      const areaList = this.formateArea(this.data.columns);
+      this.setData({
+        areaList,
+      }, () => {
+        this.formatValue()
+      });
+    },
+  },
+});

+ 3 - 0
miniprogram/common-components/w-area/index.wxml

@@ -0,0 +1,3 @@
+<van-popup round="{{true}}" lock-scroll="{{true}}" z-index="{{zIndex}}" show="{{innerShow}}" position="bottom" safe-area-inset-bottom="{{false}}" bind:close="onClose" bind:after-leave="onAfterLeave" bind:before-enter="onBeforeEnter">
+  <van-area wx:if="{{ !showAfterLeave }}" id='area1' areaList="{{areaList}}" visible-item-count="{{ visibleItemCount }}" item-height="{{ itemHeight }}" value="{{  regionCode || cityCode }}" bind:cancel="onClose" bind:confirm="submitArea" />
+</van-popup>

+ 7 - 0
miniprogram/common-components/w-checkbox/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-checkbox": "@vant/weapp/checkbox/index",
+    "van-checkbox-group": "@vant/weapp/checkbox-group/index"
+  }
+}

+ 39 - 0
miniprogram/common-components/w-checkbox/index.less

@@ -0,0 +1,39 @@
+/* components/w-checkbox/index.wxss */
+.gender-section {
+  display: flex;
+
+  .van-checkbox {
+    margin-right: 40rpx !important;
+    display: flex;
+    align-items: center;
+
+    .van-checkbox__label {
+      padding-left: 8rpx;
+    }
+  }
+
+  .icon-img {
+    display: block;
+    width: 36rpx;
+    height: 36rpx;
+  }
+
+  .icon-img {
+    width: 36rpx;
+    height: 36rpx;
+  }
+  .horizontal-checkbox {
+    &.last-child {
+      margin-right: 0 !important;
+    }
+  }
+
+  .vertical-checkbox {
+    margin-right: 0 !important;
+    margin-bottom: 12rpx !important;
+
+    &.last-child {
+      margin-bottom: 0 !important;
+    }
+  }
+}

+ 67 - 0
miniprogram/common-components/w-checkbox/index.ts

@@ -0,0 +1,67 @@
+// 组件不支持简单的双向绑定,需要使用input和confirm事件来实现。
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared",
+  },
+  properties: {
+    value: {
+      type: Array,
+      value: [],
+      observer: "_value",
+    },
+    options: {
+      required: true,
+      type: Array,
+      value: [],
+      observer: "_options",
+    },
+    /** 排列方向,可选值为 vertical horizontal */
+    direction: {
+      type: String,
+      value: "horizontal",
+    },
+  },
+  data: {
+    innerOptions: [] as any[],
+    innerValue: [] as any[],
+  },
+  methods: {
+    onCheckChange(event: any) {
+      const value = event.detail;
+      this.setData({
+        value: value.map((v: any) => String(v)), // 只更新 innerValue
+      });
+      this.triggerEvent("change", value); // 将值传递给父组件
+    },
+    onChecked(val: any) {
+      return this.data.innerValue.includes(val) ? true : false;
+    },
+    _value(newVal: any[]) {
+      const tempVal: any[] = [];
+      this.data.innerOptions.forEach((item: any) => {
+        tempVal.push({
+          ...item,
+          isSelect: newVal.includes(item.value) ? true : false,
+        });
+      });
+      this.setData({
+        innerOptions: tempVal,
+        innerValue: newVal.map((v: any) => String(v)), // 确保数据类型一致
+      });
+    },
+    _options(newVal: any[]) {
+      const value: any = this.data.value;
+      const tempVal: any[] = [];
+      newVal.forEach((item: any) => {
+        tempVal.push({
+          ...item,
+          isSelect: value.includes(item.value) ? true : false,
+        });
+      });
+      this.setData({
+        innerOptions: tempVal,
+      });
+    },
+  },
+});

+ 18 - 0
miniprogram/common-components/w-checkbox/index.wxml

@@ -0,0 +1,18 @@
+<!--components/w-radio/index.wxml-->
+<van-checkbox-group class="gender-section" value="{{ innerValue }}" bind:change="onCheckChange" direction="{{ direction }}">
+  <van-checkbox
+    wx:for="{{ innerOptions }}"
+    wx:key="index"
+    name="{{ item.value }}"
+    data-value="{{ item.value }}"
+    use-icon-slot
+    custom-class="{{ direction === 'vertical'  ? 'vertical-checkbox' : 'horizontal-checkbox'}} {{index === innerOptions.length -1 ? 'last-child' : ''}}"
+  >
+    {{ item.label }}
+    <image
+      class="icon-img"
+      slot="icon"
+      src="{{ item.isSelect ? 'https://oss.dayaedu.com/ktyq/1741145121534.png' : 'https://oss.dayaedu.com/ktyq/1741144749279.png' }}"
+    />
+  </van-checkbox>
+</van-checkbox-group>

+ 7 - 0
miniprogram/common-components/w-datetime-picker/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-popup": "@vant/weapp/popup/index",
+    "van-datetime-picker": "@vant/weapp/datetime-picker/index"
+  }
+}

+ 66 - 0
miniprogram/common-components/w-datetime-picker/index.less

@@ -0,0 +1,66 @@
+/* components/w-picker/index.wxss */
+.van-picker {
+  --main-color: #1cacf1 !important;
+}
+.van-picker__toolbar,
+.toolbar-top {
+  margin: 0 14rpx !important;
+  padding: 0 14rpx !important;
+  border-bottom: 2rpx solid #f2f2f2;
+  height: auto !important;
+  line-height: normal !important;
+}
+
+.van-picker__cancel,
+.van-picker__confirm,
+.toolbar-cancel {
+  font-size: 32rpx !important;
+  padding: 28rpx 0 !important;
+  color: #777777 !important;
+}
+
+.van-picker__confirm {
+  color: var(--main-color) !important;
+}
+
+.toolbar-top {
+  --main-color: #1cacf1 !important;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin: 0 14rpx !important;
+  padding: 0 14rpx !important;
+  border-bottom: 2rpx solid #f2f2f2;
+  height: auto !important;
+  line-height: normal !important;
+
+  .toolbar-confirm {
+    color: var(--main-color) !important;
+  }
+}
+
+.van-picker-column__item--selected {
+  font-weight: 600;
+  font-size: 32rpx;
+  color: var(--main-color) !important;
+}
+
+.van-picker-column {
+  position: relative;
+  z-index: 1;
+}
+
+.van-picker__frame {
+  z-index: 0 !important;
+  left: 0 !important;
+  right: 0 !important;
+
+  &::after {
+    background: #f6f6f6;
+    border-radius: 8px;
+  }
+}
+.van-picker__columns {
+  width: calc(100% - 32px) !important;
+  margin: 0 auto;
+}

+ 187 - 0
miniprogram/common-components/w-datetime-picker/index.ts

@@ -0,0 +1,187 @@
+// components/w-datetime-picker/index.ts
+/**
+ * 格式化时长
+ * @param num 时长 可以传负
+ */
+function formatTime(num: number) {
+  const date = new Date();
+  date.setFullYear(date.getFullYear() + num);
+  return date.getTime();
+}
+
+/**
+ * 时长填充
+ * @param value 值
+ * @param maxLength 长度
+ * @param fillText 填充内容
+ * @returns 填充值
+ */
+function padStartString(value: any, maxLength = 2, fillText = "0") {
+  const text = value + "";
+  return text.padStart(maxLength, fillText);
+}
+
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared",
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    value: {
+      type: Number as any,
+      value: null,
+      observer: "_value",
+    },
+    /** 是否显示 */
+    show: {
+      type: Boolean,
+      value: false,
+      observer: "_show",
+    },
+    /** 类型,可选值为 date time year-month 不建议动态修改 */
+    type: {
+      type: String,
+      value: "datetime",
+    },
+    zIndex: {
+      type: Number,
+      value: 100,
+    },
+    minDate: {
+      type: Number,
+      value: formatTime(-20),
+    },
+    maxDate: {
+      type: Number,
+      value: formatTime(20),
+    },
+    /**
+     * 可见的选项个数
+     */
+    visibleItemCount: {
+      type: Number,
+      value: 6,
+    },
+    itemHeight: {
+      type: Number,
+      value: 46,
+    },
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    innerShow: false,
+    // showAfterLeave: false,
+    innerValue: null as any, // 单列时为string, 多列为array
+    // defaultIndex: 0, // 默认选中的index 单行显示的
+    // innerLoading: false,
+    formatter(type: string, value: any) {
+      if (type === "year") {
+        return `${value}年`;
+      }
+      if (type === "month") {
+        return `${value}月`;
+      }
+      if (type === "day") {
+        return `${value}日`;
+      }
+      if (type === "hour") {
+        return `${value}时`;
+      }
+      if (type === "minute") {
+        return `${value}分`;
+      }
+      return value;
+    },
+  },
+  lifetimes: {
+    attached() {
+      if (this.data.type !== "time" && !this.data.value) {
+        this.setData({
+          innerValue: Date.now(),
+        });
+      }
+    },
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onSubmit(event: any) {
+      const date = new Date(event.detail);
+      const year = date.getFullYear();
+      const month = date.getMonth() + 1;
+      const day = date.getDate();
+      const hour = date.getHours();
+      const minute = date.getMinutes();
+      const detail = {
+        chName: "",
+        value: event.detail,
+      };
+
+      const type = this.data.type;
+      if (type === "datetime") {
+        detail.chName =
+          year +
+          "年" +
+          padStartString(month) +
+          "月" +
+          padStartString(day) +
+          "日 " +
+          padStartString(hour) +
+          "时" +
+          padStartString(minute) +
+          "分";
+      } else if (type === "date") {
+        detail.chName =
+          year +
+          "年" +
+          padStartString(month) +
+          "月" +
+          padStartString(day) +
+          "日";
+      } else if (type === "year-month") {
+        detail.chName = year + "年" + padStartString(month) + "月";
+      } else if (type === "time") {
+        detail.chName =
+          padStartString(hour) + "分" + padStartString(minute) + "秒";
+      }
+
+      this.setData({
+        value: detail.value,
+      });
+      this.triggerEvent("confirm", detail);
+      this.triggerEvent("input", detail.value);
+    },
+    onClose() {
+      this.triggerEvent("cancel");
+    },
+
+    // onAfterLeave() {
+    //   this.setData({
+    //     showAfterLeave: true,
+    //   });
+    // },
+    // onBeforeEnter() {
+    //   this.setData({
+    //     showAfterLeave: false,
+    //   });
+    // },
+    _show() {
+      // 单行显示 需要设置默认选中的index
+      this.setData({
+        innerShow: this.data.show,
+      });
+    },
+    _value() {
+      if (!this.data.value) return;
+      this.setData({
+        innerValue: this.data.value,
+      });
+    },
+  },
+});

+ 4 - 0
miniprogram/common-components/w-datetime-picker/index.wxml

@@ -0,0 +1,4 @@
+<!-- components/w-picker/index.wxml -->
+<van-popup round="{{true}}" lock-scroll="{{true}}" z-index="{{zIndex}}" show="{{innerShow}}" position="bottom" safe-area-inset-bottom="{{false}}" bind:close="onClose">
+  <van-datetime-picker type="{{ type }}" value="{{ innerValue }}" formatter="{{ formatter }}" bind:cancel="onClose" bind:confirm="onSubmit" />
+</van-popup>

+ 6 - 0
miniprogram/common-components/w-dialog/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-popup": "@vant/weapp/popup/index"
+  }
+}

+ 105 - 0
miniprogram/common-components/w-dialog/index.less

@@ -0,0 +1,105 @@
+/* components/w-dialog/index.wxss */
+
+.dialog-section {
+  width: 532rpx;
+  // background: linear-gradient(180deg, #FFDEE7 0%, #FFFFFF 19%, #FFFFFF 100%);
+  // border-radius: 20rpx;
+  box-sizing: border-box;
+
+  .dialog-title {
+    text-align: center;
+    font-weight: 600;
+    font-size: 34rpx;
+    color: #333;
+    line-height: 1;
+    padding-top: 36rpx;
+    &.default {
+      text-align: left;
+      padding: 36rpx 36rpx 0;
+      display: flex;
+      align-items: center;
+
+      &::before {
+        content: '';
+        display: inline-block;
+        width: 8rpx;
+        height: 28rpx;
+        background: linear-gradient(to bottom, #259CFE, #44C9FF);
+        border-radius: 8rpx;
+        margin-right: 12rpx;
+      }
+    }
+  }
+  
+
+  .dialog-content {
+    padding: 36rpx 36rpx 40rpx;
+    font-size: 30rpx;
+    color: #777777;
+    line-height: 42rpx;
+  }
+
+  .center {
+    text-align: center;
+  }
+
+  .left {
+    text-align: left;
+  }
+
+  .right {
+    text-align: right;
+  }
+
+  .dialog-btn-group--theme {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 24rpx 40rpx;
+
+    .btn {
+      flex: 1;
+      text-align: center;
+      line-height: 80rpx;
+      background: #FFFFFF;
+      border-radius: 40px;
+      border: 2rpx solid #DBDBDB;
+      font-size: 32rpx;
+      color: #333333;
+      font-weight: 500;
+      box-sizing: content-box;
+      max-width: 316rpx;
+
+      & + .btn {
+        margin-left: 24rpx;
+      }
+    }
+
+    .del-btn {
+      border: none;
+      color: #FFFFFF;
+      background: linear-gradient( 90deg, #44C9FF 0%, #259CFE 100%), #EFEFEF;
+    }
+  }
+
+
+  .dialog-btn-group {
+    display: flex;
+    align-items: center;
+    border-top: 2rpx solid #F2F2F2;
+
+    .btn {
+      flex: 1;
+      text-align: center;
+      line-height: 92rpx;
+      font-size: 32rpx;
+      color: #777777;
+      font-weight: 500;
+    }
+
+    .del-btn {
+      border-left: 2rpx solid #F2F2F2;
+      color: #1CACF1;
+    }
+  }
+}

+ 112 - 0
miniprogram/common-components/w-dialog/index.ts

@@ -0,0 +1,112 @@
+// components/w-dialog/index.ts
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared"
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    show: {
+      type: Boolean,
+      value: false
+    },
+    /** 标题 */
+    title: {
+      type: String,
+      value: ''
+    },
+    /** 文本内容 */
+    message: {
+      type: String,
+      value: ''
+    },
+    /** 
+     * 内容对齐方式,可选值为left right
+     */
+    messageAlign: {
+      type: String,
+      value: 'center'
+    },
+    /**
+     * 样式风格,可选值为round-button
+     */
+    theme: {
+      type: String,
+      value: 'default'
+    },
+    /**
+     * z-index 层级
+     */
+    zIndex: {
+      type: Number,
+      default: 100
+    },
+    /** 是否展示确认按钮 */
+    showConfirmButton: {
+      type: Boolean,
+      value: true,
+    },
+    /** 是否展示取消按钮 */
+    showCancelButton: {
+      type: Boolean,
+      value: true,
+    },
+    /** 确认按钮的文案 */
+    confirmButtonText: {
+      type: String,
+      value: '确认'
+    },
+    /** 取消按钮的文案 */
+    cancelButtonText: {
+      type: String,
+      value: '取消'
+    },
+    /** 是否展示遮罩层 */
+    overlay: {
+      type: Boolean,
+      value: true,
+    },
+    /** 是否在点击遮罩层后关闭 */
+    closeOnClickOverlay: {
+      type: Boolean,
+      value: true,
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    /** 取消 */
+    onDialogClose() {
+      this.setData({
+        show: false
+      })
+      this.triggerEvent('close', false)
+    },
+    /** 确认 */
+    onDialogConfirm() {
+      this.setData({
+        show: false
+      })
+      this.triggerEvent('confirm')
+    },
+    onClickOverlay() {
+      if(this.data.closeOnClickOverlay) {
+        this.setData({
+          show: false
+        })
+        this.triggerEvent('click-overlay')
+      }
+    }
+  }
+})

+ 17 - 0
miniprogram/common-components/w-dialog/index.wxml

@@ -0,0 +1,17 @@
+<!--components/w-dialog/index.wxml-->
+<van-popup round lock-scroll="{{true}}" overlay="{{ overlay }}" z-index="{{102}}" show="{{show}}" close-on-click-overlay="{{ closeOnClickOverlay }}" bind:click-overlay="onClickOverlay">
+    <view class="dialog-section">
+      <view class="dialog-title {{ theme === 'default' ? 'default' : '' }}" wx:if="{{ title }}">{{ title }}</view>
+
+      <view class="dialog-content {{ messageAlign }}">{{ message }}</view>
+
+      <view class="dialog-btn-group" wx:if="{{ theme === 'default' }}">
+        <view class="btn" bind:tap="onDialogClose" wx:if="{{ showCancelButton }}">{{ cancelButtonText }}</view>
+        <view class="btn del-btn" bind:tap="onDialogConfirm" wx:if="{{ showConfirmButton }}">{{ confirmButtonText }}</view>
+      </view>
+      <view class="dialog-btn-group--theme" wx:if="{{ theme === 'round-button' }}">
+        <view class="btn" bind:tap="onDialogClose" wx:if="{{ showCancelButton }}">{{ cancelButtonText }}</view>
+        <view class="btn del-btn" bind:tap="onDialogConfirm" wx:if="{{ showConfirmButton }}">{{ confirmButtonText }}</view>
+      </view>
+    </view>
+  </van-popup>

+ 4 - 0
miniprogram/common-components/w-empty/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 22 - 0
miniprogram/common-components/w-empty/index.less

@@ -0,0 +1,22 @@
+/* components/w-empty/index.wxss */
+.w-empty {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  box-sizing: border-box;
+  text-align: center;
+
+  .w-empty__img {
+    width: 440rpx;
+    height: 440rpx;
+  }
+
+  .w-empty__text {
+    font-size: 32rpx;
+    color: #999999;
+    line-height: 44rpx;
+    text-align: center;
+    padding-top: 20rpx;
+  }
+}

+ 33 - 0
miniprogram/common-components/w-empty/index.ts

@@ -0,0 +1,33 @@
+// components/w-empty/index.ts
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared",
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    image: {
+      type: String,
+      value: "https://oss.dayaedu.com/ktyq/1741336580521.png"
+    },
+    text: {
+      type: String,
+      value: "暂无内容"
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+  }
+})

+ 6 - 0
miniprogram/common-components/w-empty/index.wxml

@@ -0,0 +1,6 @@
+<!--components/w-empty/index.wxml-->
+<view class="w-empty">
+  <image class="w-empty__img" mode="widthFix" src="{{image}}" />
+  <text class="w-empty__text">{{text}}</text>
+  <slot></slot>
+</view>

+ 6 - 0
miniprogram/common-components/w-field/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-field": "@vant/weapp/field/index"
+  }
+}

+ 72 - 0
miniprogram/common-components/w-field/index.less

@@ -0,0 +1,72 @@
+/* components/w-field/index.wxss */
+// ios 间距
+
+.w-field {
+  --cell-font-size: 32rpx !important;
+  --field-label-color: #666 !important;
+  --cell-vertical-padding: 24rpx !important;
+  --cell-horizontal-padding: 32rpx !important;
+  // --van-cell-border-color: #F2F2F2 !important;
+
+  .van-cell::after {
+    right: 0 !important;
+  }
+
+  .van-field__control--custom {
+    min-height: auto !important;
+    height: auto !important;
+  }
+
+  .van-cell__right-icon-wrap {
+    color: #D8D8D8 !important;
+    font-size: 24rpx !important;
+
+    .van-icon {
+      display: flex;
+      align-items: center;
+      vertical-align: middle;
+      font-weight: bold !important;
+      margin-right: -6px;
+    }
+  }
+
+  &.top {
+    .van-cell {
+      flex-direction: column;
+    }
+
+    .van-cell__title {
+      position: relative;
+      max-width: 100% !important;
+      margin-right: 0 !important;
+      width: 100% !important;
+      padding-bottom: 20rpx !important;
+      margin-bottom: 20rpx !important;
+
+      &::after {
+        box-sizing: border-box;
+        content: " ";
+        pointer-events: none;
+        right: -32rpx;
+        bottom: 0;
+        left: 0;
+        border-bottom: 2rpx solid #ebedf0;
+        position: absolute;
+        transform: scaleY(.5);
+      }
+    }
+
+    .van-cell__value {
+      width: 100%;
+    }
+  }
+
+
+  .van-field__control--right {
+    justify-content: flex-end !important;
+  }
+
+  .van-field__control--center {
+    justify-content: center !important;
+  }
+}

+ 145 - 0
miniprogram/common-components/w-field/index.ts

@@ -0,0 +1,145 @@
+// components/w-field/index.ts
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared"
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    value: {
+      type: String,
+      value: '',
+      observer: '_value'
+    },
+    label: {
+      type: String,
+      value: '标题'
+    },
+    /** 是否显示内边框 */
+    border: {
+      type: Boolean,
+      value: true,
+    },
+    /** 是否禁用输入框 */
+    disabled: {
+      type: Boolean,
+      value: false,
+    },
+    /** 是否只读 */
+    readonly: {
+      type: Boolean,
+      value: false,
+    },
+    /** 是否居中 */
+    center: {
+      type: Boolean,
+      value: false,
+    },
+    /** 是否启用清除控件 */
+    clearable: {
+      type: Boolean,
+      value: false,
+    },
+    /** 是否开启点击反馈 */
+    clickable: {
+      type: Boolean,
+      value: false,
+    },
+    /** 是否展示右侧箭头并开启点击反馈 */
+    isLink: {
+      type: Boolean,
+      value: false,
+    },
+    /**
+     * 输入框内容对齐方式,可选值为 center right, 默认 left
+     */
+    inputAlign: {
+      type: String,
+      value: 'left'
+    },
+    /** 标题宽度 默认5.2em */
+    titleWidth: {
+      type: String,
+      value: '5.2em'
+    },
+    /**
+     * 可设置为任意原生类型, 如 number idcard textarea digit nickname, 默认 text
+     */
+    type: {
+      type: String,
+      value: 'text'
+    },
+    /** 请输入占位符 */
+    placeholder: {
+      type: String,
+      value: '请输入内容'
+    },
+    /** 指定 placeholder 的样式 */
+    placeholderStyle: {
+      type: String,
+      value: ''
+    },
+    /** 是否自适应内容高度,只对 textarea 有效,可传入对象,如 { maxHeight: 100, minHeight: 50 },单位为px */
+    autosize: {
+      type: [Object, Boolean] as any,
+      value: false
+    },
+    /** 最大输入长度,设置为 -1 的时候不限制最大长度 */
+    maxlength: {
+      type: Number,
+      value: -1
+    },
+    /** 是否显示字数统计,需要设置maxlength属性 */
+    showWordLimit: {
+      type: Boolean,
+      value: false
+    },
+    /**
+     * 左侧文本对齐方式,可选值为 top, 默认 left
+     */
+    labelAlign: {
+      type: String,
+      value: 'left'
+    }
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    innerValue: ''
+  },
+  lifetimes: {
+    attached() {
+      //
+    }
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    // 值变化事件
+    onChange(event: { detail: any }) {
+      const value = event.detail || '';
+      this.triggerEvent('change', value)
+    },
+    // 失焦事件
+    onBlur(event: { detail: any }) {
+      const value = event.detail || '';
+      this.triggerEvent('blur', value)
+    },
+    onInput(event: { detail: any }) {
+      const value = event.detail || '';
+      this.setData({
+        value: value
+      })
+      this.triggerEvent('input', value)
+    },
+    _value() {
+      this.setData({
+        innerValue: this.data.value
+      })
+    }
+  }
+})

+ 33 - 0
miniprogram/common-components/w-field/index.wxml

@@ -0,0 +1,33 @@
+<!--components/w-field/index.wxml-->
+<van-field 
+  class="w-field {{ labelAlign }}"
+  type="{{ type }}"
+  model:value="{{ innerValue }}" 
+  input-align="{{ inputAlign }}"
+  placeholder="{{ placeholder }}"
+  placeholder-style="{{ placeholderStyle }}"
+  label="{{ label }}"
+  is-link="{{ isLink }}"
+  border="{{ border }}"
+  disabled="{{ disabled }}"
+  readonly="{{ readonly }}"
+  clearable="{{ clearable }}"
+  clickable="{{ clickable }}"
+  maxlength="{{ maxlength }}"
+  autosize="{{ autosize }}"
+  show-word-limit="{{ showWordLimit }}"
+  center="{{ center }}"
+  title-width="{{ titleWidth }}"
+  bind:input="onInput"
+  bind:change="onChange"
+  bind:blur="onBlur"
+  >
+    <!-- <slot name="icon" slot="left-icon" /> -->
+    <label for="{{ name }}" wx:if="{{ label }}" name="title" slot="title">
+      {{ label }}
+    </label>
+    <slot name="input" slot="input" />
+    <slot name="right-icon" slot="right-icon" />
+    <slot name="icon" slot="icon" />
+    <slot name="button" slot="button" />
+  </van-field>

+ 9 - 0
miniprogram/common-components/w-picker/index.json

@@ -0,0 +1,9 @@
+{
+  "component": true,
+  "usingComponents": {
+    "w-search": "../w-search/index",
+    "van-popup": "@vant/weapp/popup/index",
+    "van-picker": "@vant/weapp/picker/index",
+    "van-search": "@vant/weapp/search/index"
+  }
+}

+ 66 - 0
miniprogram/common-components/w-picker/index.less

@@ -0,0 +1,66 @@
+/* components/w-picker/index.wxss */
+.van-picker {
+  --main-color: #1cacf1 !important;
+}
+.van-picker__toolbar,
+.toolbar-top {
+  margin: 0 14rpx !important;
+  padding: 0 14rpx !important;
+  border-bottom: 2rpx solid #f2f2f2;
+  height: auto !important;
+  line-height: normal !important;
+}
+
+.van-picker__cancel,
+.van-picker__confirm,
+.toolbar-cancel {
+  font-size: 32rpx !important;
+  padding: 28rpx 0 !important;
+  color: #777777 !important;
+}
+
+.van-picker__confirm {
+  color: var(--main-color) !important;
+}
+
+.toolbar-top {
+  --main-color: #1cacf1 !important;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin: 0 14rpx !important;
+  padding: 0 14rpx !important;
+  border-bottom: 2rpx solid #f2f2f2;
+  height: auto !important;
+  line-height: normal !important;
+
+  .toolbar-confirm {
+    color: var(--main-color) !important;
+  }
+}
+
+.van-picker-column__item--selected {
+  font-weight: 600;
+  font-size: 32rpx;
+  color: var(--main-color) !important;
+}
+
+.van-picker-column {
+  position: relative;
+  z-index: 1;
+}
+
+.van-picker__frame {
+  z-index: 0 !important;
+
+  &::after {
+    background: #f6f6f6;
+    border-radius: 8px;
+  }
+}
+
+.searchList1 {
+  .searchSection {
+    padding: 20rpx;
+  }
+}

+ 161 - 0
miniprogram/common-components/w-picker/index.ts

@@ -0,0 +1,161 @@
+// components/w-picker/index.ts
+// 组件不支持简单的双向绑定,需要使用input和confirm事件来实现。
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared",
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    value: {
+      type: [String, Number, Array] as any,
+      value: "",
+      observer: "_value",
+    },
+    /** 是否显示 */
+    show: {
+      type: Boolean,
+      value: false,
+      observer: "_show",
+    },
+    /**
+     * 列数据
+     * @example 单列 [{ text: a, value: 1 }, { text: b, value: 2  }]
+     * @example 多列 [{ values: [{ text: a, value: 1 }, { text: b, value: 2  }], defaultIndex: 0 }, { values: [{ text: a, value: 1 }, { text: b, value: 2  }], defaultIndex: 0  }]
+     */
+    columns: {
+      type: Array as any,
+      value: [],
+      observer: "_columns",
+    },
+    zIndex: {
+      type: Number,
+      value: 100
+    },
+    /**
+     * 可见的选项个数
+     */
+    visibleItemCount: {
+      type: Number,
+      value: 6,
+    },
+    itemHeight: {
+      type: Number,
+      value: 46,
+    },
+    placeholder: {
+      type: String,
+      value: '请输入搜索关键词'
+    }
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    isMulti: false, // 是否多列
+    innerShow: false,
+    showAfterLeave: false,
+    innerValue: "" as any, // 单列时为string, 多列为array
+    defaultIndex: 0, // 默认选中的index 单行显示的
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onSubmit(event: any) {
+      const detail = event.detail || {};
+      if(this.data.isMulti) {
+        const values: any = [];
+        detail.value.forEach((item: any) => {
+          values.push(item.value)
+        })
+        this.setData({
+          show: false,
+          value: values,
+        });
+        this.triggerEvent("confirm", detail);
+        this.triggerEvent("input", values);
+      } else {
+        this.setData({
+          show: false,
+          value: detail.value.value,
+        });
+        this.triggerEvent("confirm", detail);
+        this.triggerEvent("input", detail.value.value);
+      }
+    },
+    onChange(event: any) {
+      const detail = event.detail.value;
+      this.triggerEvent("change", detail);
+    },
+    onClose() {
+      this.triggerEvent("cancel");
+    },
+    onAfterLeave() {
+      this.setData({
+        showAfterLeave: true,
+      });
+    },
+    onBeforeEnter() {
+      this.setData({
+        showAfterLeave: false,
+      });
+    },
+    _show() {
+      // 单行显示 需要设置默认选中的index
+      let defaultIndex = 0;
+      let columns = [...this.data.columns];
+      if (this.data.show) {
+        if (!this.data.isMulti) {
+          // 单行显示
+          this.data.columns.forEach((item: any, index: number) => {
+            if (item.value === this.data.value) {
+              defaultIndex = index;
+            }
+          });
+        } else {
+          // 多行显示
+          columns.forEach((item: any, index: number) => {
+            item.values.forEach((value: any, valueIndex: number) => {
+              if (value.value === this.data.value[index]) {
+                item.defaultIndex = valueIndex;
+              }
+            });
+          });
+        }
+      }
+
+      this.setData(
+        {
+          defaultIndex,
+          columns,
+        },
+        () => {
+          this.setData({
+            innerShow: this.data.show,
+          });
+        }
+      );
+    },
+    _value() {
+      // console.log(this.data, "value");
+      this.setData({
+        innerValue: this.data.value,
+      });
+    },
+    _columns() {
+      // 判断是多行还是单行
+      if (this.data.columns[0] && this.data.columns[0].values) {
+        this.setData({
+          isMulti: true,
+        });
+      } else {
+        this.setData({
+          isMulti: false,
+        });
+      }
+    }
+  },
+});

+ 7 - 0
miniprogram/common-components/w-picker/index.wxml

@@ -0,0 +1,7 @@
+<!-- components/w-picker/index.wxml -->
+<van-popup round="{{true}}" lock-scroll="{{true}}" z-index="{{zIndex}}" show="{{innerShow}}" position="bottom" safe-area-inset-bottom="{{false}}" bind:close="onClose" bind:after-leave="onAfterLeave" bind:before-enter="onBeforeEnter">
+  <!-- 单选择器 -->
+  <van-picker wx:if="{{ !showAfterLeave && !isMulti && !isSearch }}" columns="{{ columns }}" visible-item-count="{{ visibleItemCount }}" item-height="{{ itemHeight }}" show-toolbar bind:cancel="onClose" default-index="{{ defaultIndex }}" bind:confirm="onSubmit" bind:change="onChange"></van-picker>
+  <!-- 多选择器 -->
+  <van-picker wx:if="{{ !showAfterLeave && isMulti  && !isSearch }}" columns="{{ columns }}" visible-item-count="{{ visibleItemCount }}" item-height="{{ itemHeight }}" show-toolbar bind:cancel="onClose" bind:confirm="onSubmit" bind:change="onChange"></van-picker>
+</van-popup>

二进制
miniprogram/common-components/w-radio/images/icon-active.png


二进制
miniprogram/common-components/w-radio/images/icon-normal.png


+ 7 - 0
miniprogram/common-components/w-radio/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-radio": "@vant/weapp/radio/index",
+    "van-radio-group": "@vant/weapp/radio-group/index"
+  }
+}

+ 34 - 0
miniprogram/common-components/w-radio/index.less

@@ -0,0 +1,34 @@
+/* components/w-radio/index.wxss */
+.gender-section {
+  display: flex;
+  .van-radio {
+    margin-right: 40rpx !important;
+    display: flex;
+    align-items: center;
+
+    .van-radio__label {
+      padding-left: 8rpx;
+    }
+  }
+
+  .icon-img {
+    display: block;
+    width: 36rpx;
+    height: 36rpx;
+  }
+
+  .horizontal-radio {
+    &.last-child {
+      margin-right: 0 !important;
+    }
+  }
+
+  .vertical-radio {
+    margin-right: 0 !important;
+    margin-bottom: 12rpx !important;
+
+    &.last-child {
+      margin-bottom: 0 !important;
+    }
+  }
+}

+ 51 - 0
miniprogram/common-components/w-radio/index.ts

@@ -0,0 +1,51 @@
+// components/w-radio/index.ts
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared"
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    value: {
+      type: [String, Number] as any,
+      value: '',
+      observer: '_value'
+    },
+    /** 选项值 */
+    options: {
+      type: Array,
+      value: []
+    },
+    /** 排列方向,可选值为 vertical horizontal */
+    direction: {
+      type: String,
+      value: "horizontal",
+    },
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    innerValue: ''
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onCheckChange(event: { detail: any }) {
+      const value = event.detail
+      this.setData({
+        value: value
+      })
+      this.triggerEvent('input', value)
+    },
+    _value() {
+      this.setData({
+        innerValue: this.data.value
+      })
+    }
+  }
+})

+ 7 - 0
miniprogram/common-components/w-radio/index.wxml

@@ -0,0 +1,7 @@
+<!--components/w-radio/index.wxml-->
+<van-radio-group class="gender-section" value="{{ innerValue }}" bind:change="onCheckChange" direction="{{ direction }}">
+  <van-radio use-icon-slot name="{{ item.value }}" wx:for="{{ options }}" wx:key="index" custom-class="{{ direction === 'vertical'  ? 'vertical-radio' : 'horizontal-radio'}} {{index === options.length -1 ? 'last-child' : ''}}">
+    {{ item.label }}
+    <image class="icon-img" slot="icon" src="{{ innerValue === item.value ? 'https://oss.dayaedu.com/ktyq/1741144717860.png' : 'https://oss.dayaedu.com/ktyq/1741144749279.png' }}" />
+  </van-radio>
+</van-radio-group>

+ 10 - 0
miniprogram/common-components/w-search-picker/index.json

@@ -0,0 +1,10 @@
+{
+  "component": true,
+  "usingComponents": {
+    "w-search": "../w-search/index",
+    "w-empty": "../w-empty/index",
+    "van-popup": "@vant/weapp/popup/index",
+    "van-picker": "@vant/weapp/picker/index",
+    "van-search": "@vant/weapp/search/index"
+  }
+}

+ 86 - 0
miniprogram/common-components/w-search-picker/index.less

@@ -0,0 +1,86 @@
+/* components/w-picker/index.wxss */
+.van-picker {
+  --main-color: #1cacf1 !important;
+}
+.van-picker__toolbar,
+.toolbar-top {
+  margin: 0 14rpx !important;
+  padding: 0 14rpx !important;
+  border-bottom: 2rpx solid #f2f2f2;
+  height: auto !important;
+  line-height: normal !important;
+}
+
+.van-picker__cancel,
+.van-picker__confirm,
+.toolbar-cancel {
+  font-size: 32rpx !important;
+  padding: 28rpx 0 !important;
+  color: #777777 !important;
+}
+
+.van-picker__confirm {
+  color: var(--main-color) !important;
+}
+
+.toolbar-top {
+  --main-color: #1cacf1 !important;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin: 0 14rpx !important;
+  padding: 0 14rpx !important;
+  border-bottom: 2rpx solid #f2f2f2;
+  height: auto !important;
+  line-height: normal !important;
+
+  .toolbar-confirm {
+    color: var(--main-color) !important;
+  }
+}
+
+.van-picker-column__item--selected {
+  font-weight: 600;
+  font-size: 32rpx;
+  color: var(--main-color) !important;
+}
+
+.van-picker-column {
+  position: relative;
+  z-index: 1;
+}
+
+.van-picker__frame {
+  z-index: 0 !important;
+
+  &::after {
+    background: #f6f6f6;
+    border-radius: 8px;
+  }
+}
+
+.searchList1 {
+  .searchSection {
+    padding: 20rpx;
+  }
+}
+
+.pickerSection {
+  position: relative;
+}
+
+.empty-box {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 10;
+  background-color: #fff;
+  padding-top: 30rpx;
+
+  .w-empty__img {
+    width: 320rpx;
+    height: 320rpx;
+  }
+}

+ 165 - 0
miniprogram/common-components/w-search-picker/index.ts

@@ -0,0 +1,165 @@
+// components/w-search-picker/index.ts
+// 组件不支持简单的双向绑定,需要使用input和confirm事件来实现。
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared",
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    value: {
+      type: [String, Number] as any,
+      value: "",
+      observer: "_value",
+    },
+    searchValue: {
+      type: String,
+      value: "",
+      observer: "_searchValue",
+    },
+    /** 是否显示 */
+    show: {
+      type: Boolean,
+      value: false,
+      observer: "_show",
+    },
+    /**
+     * 列数据
+     * @example 单列 [{ text: a, value: 1 }, { text: b, value: 2  }]
+     */
+    columns: {
+      type: Array as any,
+      value: [],
+    },
+    /**
+     * 可见的选项个数
+     */
+    visibleItemCount: {
+      type: Number,
+      value: 6,
+    },
+    itemHeight: {
+      type: Number,
+      value: 46,
+    },
+    searchLoading: {
+      type: Boolean,
+      value: false,
+      observer: "_searchLoading",
+    },
+    placeholder: {
+      type: String,
+      value: "请输入搜索关键词",
+    },
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    innerShow: false,
+    showAfterLeave: false,
+    innerValue: "" as any, // 单列时为string, 多列为array
+    defaultIndex: 0, // 默认选中的index 单行显示的
+    innerLoading: false,
+    searchName: "",
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onSubmit() {
+      this.setData({
+        show: false,
+        value: this.data.innerValue,
+      });
+      const columns = this.data.columns || [];
+      let detail: any = {};
+      if (this.data.innerValue) {
+        columns.forEach((item: any) => {
+          if (item.value === this.data.innerValue) {
+            detail = item;
+          }
+        });
+      }
+      this.triggerEvent("confirm", detail);
+      this.triggerEvent("input", this.data.innerValue);
+    },
+    onChange(event: any) {
+      const detail = event.detail.value;
+      this.setData({
+        innerValue: detail.value,
+      });
+      this.triggerEvent("change", detail);
+    },
+    onClose() {
+      this.triggerEvent("cancel");
+    },
+    onSearchChange(event: any) {
+      this.triggerEvent("search-change", event.detail);
+    },
+    onSearch(event: any) {
+      // 如果在搜索中,不允许搜索
+      if(this.data.innerLoading) {
+        return;
+      }
+      this.setData({
+        searchValue: event.detail,
+      });
+      this.triggerEvent("search", event.detail);
+    },
+    onAfterLeave() {
+      this.setData({
+        showAfterLeave: true,
+      });
+    },
+    onBeforeEnter() {
+      this.setData({
+        showAfterLeave: false,
+      });
+    },
+    _show() {
+      // 单行显示 需要设置默认选中的index
+      let defaultIndex = 0;
+      let innerValue = this.data.innerValue;
+      if (this.data.show) {
+        // 单行显示
+        this.data.columns.forEach((item: any, index: number) => {
+          if (item.value === this.data.value) {
+            defaultIndex = index;
+          }
+          if (!innerValue) {
+            innerValue = item.value;
+          }
+        });
+      }
+      this.setData(
+        {
+          defaultIndex,
+          innerValue,
+        },
+        () => {
+          this.setData({
+            innerShow: this.data.show,
+          });
+        }
+      );
+    },
+    _value() {
+      this.setData({
+        innerValue: this.data.value,
+      });
+    },
+    _searchLoading() {
+      this.setData({
+        innerLoading: this.data.searchLoading,
+      });
+    },
+    _searchValue() {
+      this.setData({
+        searchName: this.data.searchValue,
+      });
+    },
+  },
+});

+ 19 - 0
miniprogram/common-components/w-search-picker/index.wxml

@@ -0,0 +1,19 @@
+<!-- components/w-picker/index.wxml -->
+<van-popup round="{{true}}" lock-scroll="{{true}}" z-index="{{102}}" show="{{innerShow}}" position="bottom" safe-area-inset-bottom="{{false}}" bind:close="onClose" bind:after-leave="onAfterLeave" bind:before-enter="onBeforeEnter">
+  <view class="toolbar-top">
+    <view class="toolbar-cancel" bind:tap="onClose">取消</view>
+    <view class="toolbar-confirm" bind:tap="onSubmit">确认</view>
+  </view>
+  <view class="searchList1">
+    <view class="searchSection">
+      <w-search value="{{ searchName }}" backgroundClass="gray" bind:search="onSearch" bind:change="onSearchChange"></w-search>
+    </view>
+    <view class="pickerSection">
+      <van-picker wx:if="{{ !showAfterLeave }}" loading="{{ innerLoading }}" columns="{{ columns }}" visible-item-count="{{ visibleItemCount }}" default-index="{{ defaultIndex }}" bind:change="onChange"></van-picker>
+
+      <view class="empty-box" wx:if="{{ columns.length <= 0 && !innerLoading }}">
+        <w-empty />
+      </view>
+    </view>
+  </view>
+</van-popup>

二进制
miniprogram/common-components/w-search/images/icon-search.png


+ 6 - 0
miniprogram/common-components/w-search/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-search": "@vant/weapp/search/index"
+  }
+}

+ 52 - 0
miniprogram/common-components/w-search/index.less

@@ -0,0 +1,52 @@
+/* components/w-search/index.wxss */
+
+.searchList {
+  margin: 0;
+  padding: 0 !important;
+  height: 70rpx !important;
+  border-radius: 35rpx !important;
+  border: 1rpx solid #ffffff !important;
+
+  &.white {
+    background: #fff !important;
+    .van-search__content {
+      background: #fff !important;
+    }
+  }
+
+  &.gray {
+    background: #f8f8f8 !important;
+    .van-search__content {
+      background: #f8f8f8 !important;
+    }
+  }
+
+  .icon-search {
+    width: 32rpx;
+    height: 32rpx;
+    margin: auto 10rpx auto auto;
+  }
+
+  .van-search__content {
+    border-top-left-radius: 35rpx !important;
+    border-bottom-left-radius: 35rpx !important;
+  }
+
+  .van-cell {
+    font-size: 26rpx !important;
+  }
+
+  .searchBtn {
+    width: 112rpx;
+    line-height: 54rpx !important;
+    padding: 0 !important;
+    text-align: center;
+    background-color: #1cacf1;
+    font-weight: 500;
+    font-size: 28rpx;
+    color: #ffffff;
+    line-height: 40rpx;
+    border-radius: 40rpx;
+    margin-right: 6rpx;
+  }
+}

+ 69 - 0
miniprogram/common-components/w-search/index.ts

@@ -0,0 +1,69 @@
+// components/w-search/index.ts
+Component({
+  options: {
+    multipleSlots: true,
+    styleIsolation: "shared",
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    /**
+     * 搜索框的值
+     */
+    value: {
+      type: String,
+      value: "",
+      observer: '_value'
+    },
+    /**
+     * 搜索框的占位符
+     */
+    placeholder: {
+      type: String,
+      value: "请输入搜索内容",
+    },
+    // 搜索框的占位符样式
+    placeholderStyle: {
+      type: String,
+      value: "color: #BDBDBD;",
+    },
+    searchButtonText: {
+      type: String,
+      value: "搜索",
+    },
+    /**
+     * 输入框的背景色 - 默认白色, [white gray]
+     */
+    backgroundClass: {
+      type: String,
+      value: 'white'
+    }
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    searchName: ""
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onSearchChange(event: any) {
+      this.setData({
+        value: event.detail,
+      });
+      this.triggerEvent("change", event.detail);
+    },
+    onSearch() {
+      // this.getSchools(this.data.searchName);
+      this.triggerEvent("search", this.data.searchName)
+    },
+    _value() {
+      this.setData({
+        searchName: this.data.value
+      })
+    }
+  },
+});

+ 5 - 0
miniprogram/common-components/w-search/index.wxml

@@ -0,0 +1,5 @@
+<!-- components/w-search/index.wxml -->
+<van-search value="{{ searchName }}" use-left-icon-slot use-action-slot show-action custom-class="searchList {{ backgroundClass }}" placeholder="{{ placeholder }}" bind:search="onSearch" bind:clear="onSearch" bind:change="onSearchChange" placeholder-style="{{ placeholderStyle }}">
+  <image slot="left-icon" src="./images/icon-search.png" class="icon-search" />
+  <view slot="action" class="searchBtn" bind:tap="onSearch">{{ searchButtonText }}</view>
+</van-search>

+ 1 - 1
miniprogram/config.ts

@@ -1,4 +1,4 @@
-const environmentVariable = "online";
+const environmentVariable = "test";
 const apiUrlInfo = {
   dev: "https://dev.kt.colexiu.com",
   test: "https://test.kt.colexiu.com",

+ 1 - 1
miniprogram/pages/buyerInformation/index.wxml

@@ -26,7 +26,7 @@
     <view class="section">
       <view class="section-content">
         <image src="./images/title2.png" class="section-title" />
-        <van-field value="{{ provinceName ? (provinceName || '') + ' ' + (cityName || '') + ' ' + (regionName || '') : '' }}" bind:tap="onShowAreaList" placeholder="请选择地区" input-align="right" is-link readonly >
+        <van-field value="{{ provinceName ? (provinceName || '') + ' ' + (cityName || '') + ' ' + (regionName || '') : '' }}" bind:tap="onShowAreaList" placeholder="请选择地区" input-align="right" is-link readonly>
           <view class="required" slot="label"><text>*</text>所在地区</view>
         </van-field>
         <van-field model:value="{{ schoolAreaName }}" placeholder="请选择学校" input-align="right" is-link readonly bind:tap="onSelectSchool" >

+ 15 - 0
miniprogram/pages/test-components/index.json

@@ -0,0 +1,15 @@
+{
+  "usingComponents": {
+    "navigation-bar": "/components/navigation-bar/navigation-bar",
+    "w-field": "../../common-components/w-field/index",
+    "w-dialog": "../../common-components/w-dialog/index",
+    "w-radio": "../../common-components/w-radio/index",
+    "w-checkbox": "../../common-components/w-checkbox/index",
+    "w-picker": "../../common-components/w-picker/index",
+    "w-search-picker": "../../common-components/w-search-picker/index",
+    "w-search": "../../common-components/w-search/index",
+    "w-area": "../../common-components/w-area/index",
+    "w-datetime-picker": "../../common-components/w-datetime-picker/index"
+  },
+  "disableScroll": true
+}

+ 32 - 0
miniprogram/pages/test-components/index.less

@@ -0,0 +1,32 @@
+/* pages/test-components/index.wxss */
+page {
+  background-color: #f2f2f2;
+  height: 100vh;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+.page-container {
+  flex: 1 auto;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.page-footer {
+  // position: absolute;
+  // bottom: 0;
+  // left: 0;
+  // right: 0;
+  padding: 40rpx 30rpx;
+  // padding-bottom: env(safe-area-inset-bottom);
+  background-color: #fff;
+}
+
+.container {
+  margin: 26rpx;
+  border-radius: 12rpx;
+  overflow: hidden;
+  height: auto;
+}

+ 223 - 0
miniprogram/pages/test-components/index.ts

@@ -0,0 +1,223 @@
+import { api_sysAreaQueryAllProvince } from "../../api/new";
+
+// pages/test-components/index.ts
+Page({
+  /**
+   * 页面的初始数据
+   */
+  data: {
+    fieldValue: "",
+    fieldValue1: "11",
+    radioList: [
+      {
+        label: "男",
+        value: "1",
+      },
+      {
+        label: "女",
+        value: "0",
+      },
+      {
+        label: "未知",
+        value: "2",
+      },
+    ],
+    radio: "0",
+    checkbox: ["1"],
+    dialogShow: false,
+
+    pickerValue: [],
+    pickerShow: false,
+    moreValue: "",
+    columns: [
+      {
+        values: [
+          { text: "一年级", value: 1 },
+          { text: "二年级", value: 2 },
+          { text: "三年级", value: 3 },
+          { text: "四年级", value: 4 },
+        ],
+        defaultIndex: 0,
+      },
+      {
+        values: [
+          { text: "一班", value: 1 },
+          { text: "二班", value: 2 },
+          { text: "三班", value: 3 },
+          { text: "四班", value: 4 },
+        ],
+      },
+
+      // { text: "一年级", value: 1 },
+      // { text: "二年级", value: 2 },
+      // { text: "三年级", value: 3 },
+      // { text: "四年级", value: 4 },
+    ] as any,
+
+    // 搜索
+    pickerSearchShow: false,
+    searchColumns: [
+      { text: "一年级", value: 1 },
+      { text: "二年级", value: 2 },
+      { text: "三年级", value: 3 },
+      { text: "四年级", value: 4 },
+    ] as any,
+    pickerSearchValue: "",
+    searchName: "",
+    searchLoading: false,
+    searchValue: "",
+
+    // 省市区
+    pickerAreaShow: false,
+    fieldArea: "",
+    areaList: [] as any,
+    areaValue: {
+      // cityCode: "110120",
+      // provinceCode: "110000",
+      // regionCode: null
+    },
+
+    // 日期
+    pickerTimeShow: false,
+    fieldTime: "",
+    timeValue: Date.now(),
+    currentDate: Date.now(),
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad() {
+    this.getAreas();
+  },
+  /** 获取省市区 */
+  async getAreas() {
+    try {
+      const { data } = await api_sysAreaQueryAllProvince({});
+      this.setData({
+        areaList: data.data,
+      });
+    } catch {
+      //
+    }
+  },
+  onInput(e: any) {
+    console.log(e, "input");
+  },
+  onSubmit() {
+    console.log(this.data, "data");
+    // this.setData({
+    //   dialogShow: true
+    // })
+  },
+  onCheckboxChange(event: any) {
+    this.setData({
+      checkbox: event.detail,
+    });
+  },
+  onSelectClass() {
+    this.setData({
+      pickerShow: true,
+    });
+  },
+  onConfirm(data: any) {
+    console.log(data, "data");
+    const values: any = [];
+    let texts: string = "";
+    const detail = data.detail;
+    if (Array.isArray(detail.value)) {
+      detail.value.forEach((item: any) => {
+        values.push(item.value);
+        texts += texts ? "/" + item.text : item.text;
+      });
+    }
+    this.setData({
+      pickerShow: false,
+      pickerValue: values,
+      moreValue: texts,
+    });
+  },
+  onCancel() {
+    this.setData({
+      pickerShow: false,
+    });
+  },
+
+  onDialogShow() {
+    this.setData({
+      dialogShow: true,
+    });
+  },
+  // 搜索
+  onSelectSearch() {
+    this.setData({
+      pickerSearchShow: true,
+    });
+  },
+  onSearchClose() {
+    this.setData({
+      pickerSearchShow: false,
+    });
+  },
+  onSearchConfirm(data: any) {
+    // console.log(data, "data");
+    this.setData({
+      pickerShow: false,
+      searchValue: data.detail.text,
+      pickerSearchValue: data.detail.value,
+    });
+  },
+  onSearch() {
+    // console.log(event, "event");
+    this.setData({
+      searchLoading: true,
+    });
+
+    setTimeout(() => {
+      this.setData({
+        searchColumns: [],
+        searchLoading: false,
+      });
+    }, 1000);
+  },
+
+  // 省市区
+  onSelectArea() {
+    this.setData({
+      pickerAreaShow: true,
+    });
+  },
+  onCloseArea() {
+    this.setData({
+      pickerAreaShow: false,
+    });
+  },
+  onAreaInput(values: any) {
+    console.log(values, "values");
+    const detail = values.detail;
+    this.setData({
+      areaValue: detail.detail,
+      fieldArea: detail.name || "",
+      pickerShow: false,
+    });
+  },
+
+  // 日期
+  onSelectTime() {
+    this.setData({
+      pickerTimeShow: true,
+    });
+  },
+  onCloseTime() {
+    this.setData({
+      pickerTimeShow: false,
+    });
+  },
+  onTimeInput(event: any) {
+    this.setData({
+      timeValue: event.detail.value,
+      fieldTime: event.detail.chName,
+      pickerTimeShow: false,
+    });
+  },
+});

+ 64 - 0
miniprogram/pages/test-components/index.wxml

@@ -0,0 +1,64 @@
+<wxs src="./select.wxs" module="select" />
+
+<navigation-bar title="测试组件页面"></navigation-bar>
+<view class="page-container">
+  <view class="container">
+    <w-field model:value="{{ fieldValue1 }}" label="多行文本" label-align="top" type="textarea" autosize="{{ { maxHeight: 100, minHeight: 50 } }}" maxlength="100" showWordLimit></w-field>
+
+    <!-- <w-search value="{{ searchName }}" bind:search="onSearch"></w-search> -->
+  </view>
+
+  <view class="container">
+    <w-field model:value="{{ fieldValue }}" label="标题文字标题文字字" />
+    <w-field model:value="{{ fieldValue }}" readonly is-link />
+
+    <w-field model:value="{{ fieldValue }}" center inputAlign="right" label="单选框">
+      <view slot="input">
+        <w-radio model:value="{{ radio }}" options="{{ radioList }}"></w-radio>
+      </view>
+    </w-field>
+
+    <w-field model:value="{{ fieldValue }}" center label="多选框">
+      <view slot="input">
+        <w-checkbox value="{{ checkbox }}" options="{{ radioList }}" bind:change="onCheckboxChange"></w-checkbox>
+      </view>
+    </w-field>
+  </view>
+
+  <view class="container">
+    <w-field label="多Picker" placeholder="请选择" value="{{ moreValue }}" is-link readonly bind:tap="onSelectClass" />
+  </view>
+
+  <view class="container">
+    <w-field label="带搜索" placeholder="请选择" value="{{ searchValue }}" is-link readonly bind:tap="onSelectSearch" />
+  </view>
+
+  <view class="container">
+    <w-field label="省市区" placeholder="请选择省市区" value="{{ fieldArea }}" is-link readonly bind:tap="onSelectArea" />
+  </view>
+  <view class="container">
+    <w-field label="日期" placeholder="请选择日期" value="{{ fieldTime }}" is-link readonly bind:tap="onSelectTime" />
+  </view>
+
+  <button bind:tap="onDialogShow" type="primary">Dialog 弹窗提示</button>
+
+  {{ select.formatSelect([1,2,3], '1') }}
+</view>
+
+
+<view class="page-footer">
+  <button type="primary" style="width: 100% !important; line-height: 52rpx;" bind:tap="onSubmit">提交</button>
+</view>
+
+
+<w-dialog show="{{ dialogShow }}" title="标题" message="确认删除该地址吗?确认删除该地址吗?确认删除该地址吗?确认删除该地址吗?确认删除该地址吗?确认删除该地址吗?"></w-dialog>
+
+<w-picker show="{{ pickerShow }}" value="{{ pickerValue }}" columns="{{ columns }}" bind:cancel="onCancel" bind:confirm="onConfirm"></w-picker>
+
+<w-search-picker value="{{ pickerSearchValue }}" show="{{ pickerSearchShow }}" columns="{{ searchColumns }}" bind:cancel="onSearchClose" bind:confirm="onSearchConfirm" bind:search="onSearch" searchLoading="{{ searchLoading }}"></w-search-picker>
+
+<!-- 省市区 -->
+<w-area value="{{ areaValue }}" columns="{{ areaList }}" show="{{ pickerAreaShow }}" bind:cancel="onCloseArea" bind:confirm="onAreaInput"></w-area>
+
+<!-- 日期 -->
+ <w-datetime-picker value="{{ timeValue }}" show="{{ pickerTimeShow }}" type="year-month" bind:cancel="onCloseTime" bind:confirm="onTimeInput"></w-datetime-picker>

+ 8 - 0
miniprogram/pages/test-components/select.wxs

@@ -0,0 +1,8 @@
+var formatSelect = function (selects, value) {
+  console.log(selects, 'selects', value)
+  console.log(selects.includes(value) ? 'true' : 'false')
+  // return selects.includes(value)
+}
+module.exports = {
+  formatSelect: formatSelect
+}

+ 7 - 7
project.private.config.json

@@ -4,21 +4,21 @@
   "setting": {
     "compileHotReLoad": true
   },
-  "libVersion": "3.6.6",
+  "libVersion": "3.7.10",
   "condition": {
     "miniprogram": {
       "list": [
         {
-          "name": "pages/buyerInformation/index",
-          "pathName": "pages/buyerInformation/index",
-          "query": "userBeneficiaryId=",
+          "name": "pages/test-components/index",
+          "pathName": "pages/test-components/index",
+          "query": "",
           "launchMode": "default",
           "scene": null
         },
         {
-          "name": "pages/orders/order-result",
-          "pathName": "pages/orders/order-result",
-          "query": "orderNo=1888139694953324546",
+          "name": "pages/buyerInformation/index",
+          "pathName": "pages/buyerInformation/index",
+          "query": "userBeneficiaryId=",
           "launchMode": "default",
           "scene": null
         },