yfx
2026-03-17 41d2fe31c18c0a82e0239035c2e50f10fa46c715
UI 绑卡,权收
9 files modified
79 files added
4889 ■■■■■ changed files
.gitignore 22 ●●●●● patch | view | raw | blame | history
.hbuilderx/launch.json 2 ●●● patch | view | raw | blame | history
androidPrivacy.json 2 ●●● patch | view | raw | blame | history
manifest.json 20 ●●●● patch | view | raw | blame | history
pages.json 16 ●●●●● patch | view | raw | blame | history
pages/my/my.vue 88 ●●●● patch | view | raw | blame | history
pages/quanshou/index.vue 12 ●●●●● patch | view | raw | blame | history
pages/quanyi/add.vue 29 ●●●● patch | view | raw | blame | history
pages/quanyi/components/agreement/index.vue 16 ●●●● patch | view | raw | blame | history
pages/quanyi/index.vue 20 ●●●● patch | view | raw | blame | history
pages/web/xieyi.vue 41 ●●●●● patch | view | raw | blame | history
static/imgs/quanshou/FZV1.png patch | view | raw | blame | history
static/imgs/quanshou/FZV2.png patch | view | raw | blame | history
static/imgs/quanshou/FZV3.png patch | view | raw | blame | history
static/imgs/quanshou/FZV4.png patch | view | raw | blame | history
static/imgs/quanshou/FZV5.png patch | view | raw | blame | history
static/imgs/quanshou/FZV6.png patch | view | raw | blame | history
static/imgs/quanshou/KB1.png patch | view | raw | blame | history
static/imgs/quanshou/KB2.png patch | view | raw | blame | history
static/imgs/quanshou/KB3.png patch | view | raw | blame | history
static/imgs/quanshou/KB4.png patch | view | raw | blame | history
static/imgs/quanshou/KB5.png patch | view | raw | blame | history
static/imgs/quanshou/KB6.png patch | view | raw | blame | history
static/imgs/quanshou/NEWBOX1.png patch | view | raw | blame | history
static/imgs/quanshou/NEWBOX2.png patch | view | raw | blame | history
static/imgs/quanshou/S81.png patch | view | raw | blame | history
static/imgs/quanshou/S82.png patch | view | raw | blame | history
static/imgs/quanshou/S83.png patch | view | raw | blame | history
static/imgs/quanshou/S84.png patch | view | raw | blame | history
static/imgs/quanshou/S85.png patch | view | raw | blame | history
static/imgs/quanshou/S86.png patch | view | raw | blame | history
static/imgs/quanshou/S87.png patch | view | raw | blame | history
static/imgs/quanshou/S89.png patch | view | raw | blame | history
static/imgs/quanshou/SH1.png patch | view | raw | blame | history
static/imgs/quanshou/SH2.png patch | view | raw | blame | history
static/imgs/quanshou/SH3.png patch | view | raw | blame | history
static/imgs/quanshou/SH4.png patch | view | raw | blame | history
static/imgs/quanshou/SH5.png patch | view | raw | blame | history
static/imgs/quanshou/SH6.png patch | view | raw | blame | history
static/imgs/quanshou/SH7.png patch | view | raw | blame | history
static/imgs/quanshou/SH8.png patch | view | raw | blame | history
static/imgs/quanshou/SH9.png patch | view | raw | blame | history
static/imgs/quanshou/SW1.png patch | view | raw | blame | history
static/imgs/quanshou/SW2.png patch | view | raw | blame | history
static/imgs/quanshou/SW3.png patch | view | raw | blame | history
static/imgs/quanshou/SW4.png patch | view | raw | blame | history
static/imgs/quanshou/SW5.png patch | view | raw | blame | history
static/imgs/quanshou/SW6.png patch | view | raw | blame | history
static/imgs/quanshou/SW7.png patch | view | raw | blame | history
static/imgs/quanshou/SW8.png patch | view | raw | blame | history
static/imgs/quanshou/SWLL.png patch | view | raw | blame | history
static/imgs/quanshou/VIP1.png patch | view | raw | blame | history
static/imgs/quanshou/VIP2.png patch | view | raw | blame | history
static/imgs/quanshou/VIPFZ0.png patch | view | raw | blame | history
static/imgs/quanshou/W41.png patch | view | raw | blame | history
static/imgs/quanshou/W42.png patch | view | raw | blame | history
static/imgs/quanshou/W43.png patch | view | raw | blame | history
static/imgs/quanshou/W44.png patch | view | raw | blame | history
static/imgs/quanshou/Y51.png patch | view | raw | blame | history
static/imgs/quanshou/Y52.png patch | view | raw | blame | history
static/imgs/quanshou/Y53.png patch | view | raw | blame | history
static/imgs/quanshou/Y54.png patch | view | raw | blame | history
static/imgs/quanshou/Y55.png patch | view | raw | blame | history
static/imgs/quanshou/ZC1.png patch | view | raw | blame | history
static/imgs/quanshou/ZC2.png patch | view | raw | blame | history
static/imgs/quanshou/ZC3.png patch | view | raw | blame | history
uni_modules/oui-icon/changelog.md 19 ●●●●● patch | view | raw | blame | history
uni_modules/oui-icon/components/oui-icon/iconfont.ttf patch | view | raw | blame | history
uni_modules/oui-icon/components/oui-icon/icons.scss 2423 ●●●●● patch | view | raw | blame | history
uni_modules/oui-icon/components/oui-icon/oui-icon.vue 56 ●●●●● patch | view | raw | blame | history
uni_modules/oui-icon/components/oui-icon/types.ts 28 ●●●●● patch | view | raw | blame | history
uni_modules/oui-icon/package.json 101 ●●●●● patch | view | raw | blame | history
uni_modules/oui-icon/readme.md 177 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/changelog.md 20 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/index.ts 1 ●●●● patch | view | raw | blame | history
uni_modules/oui-utils/jest.config.js 12 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/libs/index.ts 4 ●●●● patch | view | raw | blame | history
uni_modules/oui-utils/libs/object.ts 158 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/libs/style.ts 93 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/libs/test.ts 336 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/libs/uuid.ts 39 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/package.json 111 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/readme.md 241 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/tests/object.test.ts 159 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/tests/style.test.ts 142 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/tests/test.test.ts 385 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/tests/uuid.test.ts 91 ●●●●● patch | view | raw | blame | history
uni_modules/oui-utils/tsconfig.json 25 ●●●●● patch | view | raw | blame | history
.gitignore
New file
@@ -0,0 +1,22 @@
.DS_Store
unpackage/
dist/
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.project
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
.hbuilderx/launch.json
@@ -3,7 +3,7 @@
    "configurations" : [
        {
            "customPlaygroundType" : "local",
            "playground" : "custom",
            "playground" : "standard",
            "type" : "uni-app:app-android"
        },
        {
androidPrivacy.json
@@ -1,7 +1,7 @@
{
    "version" : "1",
    "prompt" : "template",
    "title" : "荔枝商城注册协议和隐私政策",
    "title" : "齐享购商城注册协议和隐私政策",
    "message" : "  请你务必审慎阅读、充分理解“注册协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"https://www.ncmiruo.com/index/?gp=1 \">《注册协议》</a>和<a href=\"https://www.ncmiruo.com/?gp=2 \">《隐私政策》</a>了解详细信息。并且我们会申请读取您的设备信息,以向您提供安全风控服务。同时您应特别注意前述协议中免除或者限制我们责任的条款、对您权利进行限制的条款。如您已详细阅读并同意金喜易购服务协议和隐私政策,请点击【同意并继续】开始使用我们的服务。",
    "buttonAccept" : "同意并继续",
    "buttonRefuse" : "暂不同意",
manifest.json
@@ -1,6 +1,6 @@
{
    "name" : "齐享购",
    "appid" : "__UNI__AEFE893",
    "appid" : "__UNI__3333B1D",
    "description" : "",
    "versionName" : "1.3.6",
    "versionCode" : 136,
@@ -148,25 +148,11 @@
                    "description" : "核身服务",
                    "platforms" : "Android,iOS",
                    "url" : "https://ext.dcloud.net.cn/plugin?id=10378",
                    "android_package_name" : "com.zhibaohui.qxg",
                    "ios_bundle_id" : "",
                    "android_package_name" : "",
                    "ios_bundle_id" : "com.qxg.app",
                    "isCloud" : true,
                    "bought" : 1,
                    "pid" : "10378",
                    "parameters" : {}
                }
            },
            "DC-WBNormal" : {
                "__plugin_info__" : {
                    "name" : "DC-WBNormal",
                    "description" : "WB Normal SDK uni-app 插件",
                    "platforms" : "Android",
                    "url" : "https://ext.dcloud.net.cn/plugin?id=1491",
                    "android_package_name" : "com.zhibaohui.qxg",
                    "ios_bundle_id" : "",
                    "isCloud" : true,
                    "bought" : 1,
                    "pid" : "1491",
                    "parameters" : {}
                }
            }
pages.json
@@ -213,30 +213,34 @@
        {
            "path": "pages/quanyi/index",
            "style": {
                "navigationBarTitleText": "权益",
                "navigationStyle":"custom"
                "navigationBarTitleText": "会员权益"
            }
        },
        {
            "path": "pages/quanyi/add",
            "style": {
                "navigationBarTitleText": "绑卡",
                "navigationStyle":"custom"
                "navigationBarTitleText": "绑卡"
            }
        },
        {
            "path": "pages/quanshou/index",
            "style": {
                "navigationBarTitleText": "绑卡",
                "navigationBarTitleText": "会员权收",
                "navigationStyle":"custom"
            }
        },
        {
            "path": "pages/web/index",
            "style": {
                "navigationBarTitleText": "绑卡",
                "navigationBarTitleText": "",
                "navigationStyle":"custom"
            }
        },
        {
            "path": "pages/web/xieyi",
            "style": {
                "navigationBarTitleText": "协议"
            }
        }
    ],
    "tabBar": {
pages/my/my.vue
@@ -5,7 +5,7 @@
        <view class="startBar" style="background-color: #e3b400;"></view>
        <view class="myTop">
            <view class="headimg">
                <image :src="userInfo != null ? userInfo.avatar : '/static/imgs/headimg.png'" class="" mode="" @tap="setImg"></image>
                <image :src="userInfo != null ? userInfo.avatar : '/static/imgs/headimg.png'" class="" mode="widthFix" @tap="setImg"></image>
                <view class="">
                    <template>
                        <view v-if="userInfo == null" @tap="$util.goPages('/pages/login/login')">未登录</view>
@@ -38,23 +38,23 @@
            </view>
            <view class="myNavs-view">
                <view class="myNavs-item" @tap="$util.checkLogin('/pages/order/order?status=1')">
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-1.png" mode=""></image>
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-1.png" mode="widthFix"></image>
                    <text class="myNavs-item-text">待支付</text>
                </view>
                <view class="myNavs-item" @tap="$util.checkLogin('/pages/order/order?status=3')">
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-5.png" mode=""></image>
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-5.png" mode="widthFix"></image>
                    <text class="myNavs-item-text">待签约</text>
                </view>
                <view class="myNavs-item" @tap="$util.checkLogin('/pages/order/order?status=4')">
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-2.png" mode=""></image>
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-2.png" mode="widthFix"></image>
                    <text class="myNavs-item-text">待发货</text>
                </view>
                <view class="myNavs-item" @tap="$util.checkLogin('/pages/order/order?status=5')">
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-3.png" mode=""></image>
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-3.png" mode="widthFix"></image>
                    <text class="myNavs-item-text">待收货</text>
                </view>
                <view class="myNavs-item" @tap="$util.checkLogin('/pages/order/order?status=8')">
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-4.png" mode=""></image>
                    <image class="myNavs-item-icon" src="/static/imgs/my-n-4.png" mode="widthFix"></image>
                    <text class="myNavs-item-text">已完成</text>
                </view>
            </view>
@@ -66,32 +66,36 @@
            </view>
            <view class="myServe-view">
                <view class="myServe-item" @tap="$util.checkLogin('/pages/profile/profile')">
                    <image src="/static/imgs/my-s-1.png" mode=""></image>
                    <image src="/static/imgs/my-s-1.png" mode="widthFix"></image>
                    <text>个人信息</text>
                </view>
                <view class="myServe-item" @tap="$util.checkLogin('/pages/logs/logs')" v-if="Config && Config.put_open == 2">
                    <image src="/static/imgs/my-s-2.png" mode=""></image>
                    <image src="/static/imgs/my-s-2.png" mode="widthFix"></image>
                    <text>账单信息</text>
                </view>
                <view class="myServe-item" @tap="$util.checkLogin('/pages/bank/bank')">
                    <image src="/static/imgs/my-s-3.png" mode=""></image>
                    <image src="/static/imgs/my-s-3.png" mode="widthFix"></image>
                    <text>银行卡</text>
                </view>
                <view class="myServe-item" @tap="$util.checkLogin('/pages/address/address')">
                    <image src="/static/imgs/my-s-4.png" mode=""></image>
                    <image src="/static/imgs/my-s-4.png" mode="widthFix"></image>
                    <text>我的地址</text>
                </view>
                <view class="myServe-item" @tap="$util.checkLogin('/pages/collect/collect')">
                    <image src="/static/imgs/my-s-5.png" mode=""></image>
                    <image src="/static/imgs/my-s-5.png" mode="widthFix"></image>
                    <text>我的收藏</text>
                </view>
                <view class="myServe-item" @tap="goKefu">
                    <image src="/static/imgs/my-s-6.png" mode=""></image>
                    <image src="/static/imgs/my-s-6.png" mode="widthFix"></image>
                    <text>联系客服</text>
                </view>
                <view class="myServe-item" v-if="Config.put_open == 1">
        <view class="myServe-item" @tap="toHuishou" >
          <template v-if="isShop">
            <image src="/static/imgs/quanshou.png" mode="widthFix"></image>
            <text>会员权收</text></template>
                    
                </view>
            </view>
        </view>
        <view class="myBeiAn">
@@ -106,6 +110,19 @@
        </view>
        <agreePup ref="agreePup1" :gp="1" title="注册协议"></agreePup>
        <agreePup ref="agreePup2" :gp="2" title="隐私政策"></agreePup>
    <uni-popup type="center" ref="comment" >
      <view style="background-image: url('/static/imgs/BOXSDF.png');background-repeat: no-repeat;background-size: 100% 100%">
        <view style="border-radius: 48rpx;padding: 28rpx 20rpx">
          <view style="text-align: center;padding-top: 30rpx;" >
            <image src="/static/imgs/wuImg.png" style="width: 372rpx;height: 240rpx;"></image>
          </view>
          <view style="font-size: 32rpx;font-weight: 600;color: #333333;text-align: center;margin-top: 34rpx;margin-bottom: 80rpx;padding: 0 28rpx;">{{errState}}</view>
          <view @click="$refs.comment.close()" style="font-weight: 500;font-size: 32rpx;color: #fff;text-align: center;padding: 24rpx 28rpx;border-top: 2rpx solid #EEEEEE;background: #FF1472;;border-radius: 40rpx ;">好的</view>
        </view>
      </view>
    </uni-popup>
    </view>
</template>
@@ -120,15 +137,58 @@
            return {
                userInfo: this.$util.getUserInfo(),
                Config: {put_open: 1},
        isShop:false,
        errState:''
            }
        },
        onLoad() {
        },
        onShow() {
    async onShow() {
            this.getConfig()
            this.getUserInfo()
      const _this = this;
      if(_this.$util.getUserInfo()?.phone){
        await uni.request({
          url: _this.$webHost+'/api/v2/auth/qxgIosUnionLogin?phone='+_this.$util.getUserInfo().phone,
          data:{
            customerId: uni.getStorageSync('qxgLogin').customerId,
            appId: uni.getStorageSync('qxgLogin').appId
          },
          success: function (res) {
            _this.isShop = res?.data?.data?.isShop;
            uni.setStorageSync('qxgLogin', res.data.data)
          }
        })
      }
        },
        methods: {
      toHuishou(){
        const _this = this;
        if(!_this.isShop){
          return
        }
        uni.request({
          url: _this.$webHost+'/api/v2/customer/ryxIsBuyEquity?phone='+_this.$util.getUserInfo().phone,
          data:{
            customerId: uni.getStorageSync('qxgLogin').customerId,
            appId: uni.getStorageSync('qxgLogin').appId
          },
          success: function (res) {
            if(res.data.code==200){
              uni.navigateTo( {
                url: '/pages/quanshou/index'
              })
            } else {
              _this.errState = res.data.data;
              _this.$refs.comment.open()
            }
          }
        })
      },
            goKefu() {
                if(this.Config && this.Config.kefu_open == 1) {
                    this.wechatWork()
pages/quanshou/index.vue
@@ -60,6 +60,10 @@
    const _this = this;
    uni.request({
      url: _this.$webHost+'/api/v2/customer/getRyxBenefitsRecordList?phone='+_this.$util.getUserInfo().phone,
      data:{
        customerId: uni.getStorageSync('qxgLogin').customerId,
        appId: uni.getStorageSync('qxgLogin').appId
      },
      success: function (res) {
        if(res.data.code==200){
          _this.lst = res.data.data;
@@ -96,6 +100,10 @@
      const _this = this;
      uni.request({
        url: _this.$webHost+'/api/v2/memberEquity/getUrlByShop/'+_this.$util.getUserInfo().phone,
        data:{
          customerId: uni.getStorageSync('qxgLogin').customerId,
          appId: uni.getStorageSync('qxgLogin').appId
        },
        success: function (res) {
          if(res.data.code==200){
            uni.request( {
@@ -119,6 +127,10 @@
      const _this = this;
      uni.request({
        url: _this.$webHost+'/api/v2/shop/findExpressNo?phone='+_this.$util.getUserInfo().phone,
        data:{
          customerId: uni.getStorageSync('qxgLogin').customerId,
          appId: uni.getStorageSync('qxgLogin').appId
        },
        success: function (res) {
          if(res.data.code==200){
            _this.tips = res.data.data;
pages/quanyi/add.vue
@@ -11,6 +11,7 @@
  },
  data() {
    return {
      loginAct: false,
      $webHost:'https://rbb98765.zrgy-bbg.com',
      protocolChecked: [1],
      offIcon,leftIcon,kkIcon,
@@ -137,16 +138,6 @@
  },
  mounted(){
    this.windowHeight = uni.getSystemInfoSync().windowHeight;
    this.interval = setInterval(() => {
      let newHeight = window.innerHeight;
      if (this.windowHeight !== newHeight) {
        this.windowHeight = newHeight;
        this.handleResize();
      }
    }, 200);
  },
  methods: {
    kk(){
@@ -221,7 +212,7 @@
    },
    submit() {
      const _this = this;
      if(!this.protocolChecked[0]){uni.showToast({title: '请阅读并同意相关协议', icon: 'none'});return;}
      if(!this.loginAct){uni.showToast({title: '请阅读并同意相关协议', icon: 'none'});return;}
      if(!_this.cardName){
        uni.showToast({
          title: "请填写您的姓名",
@@ -352,6 +343,9 @@
      })
    },
    agreeClick () {
      this.loginAct = !this.loginAct
    },
    getSmsCode(){
      const that = this;
@@ -850,23 +844,16 @@
    <view style=" padding: 0 30rpx;margin-left: 20rpx;">
      <view style="display: flex;">
        <uv-checkbox-group
            activeColor="#FF1472"
            shape="circle"
            v-model="protocolChecked">
          <uv-checkbox size="14" :name="1" >
          </uv-checkbox>
          <text class="protocol-text" >
        <image @click="agreeClick" style="width: 50rpx;height: 50rpx;" :src="!loginAct ? '/static/imgs/check.png' : '/static/imgs/checked.png'" ></image>
        <text class="protocol-text" style="line-height: 50rpx" >
            我已阅读并同意<text style="font-size: 24rpx;color: #4766FE" class="link-text"  @click="$refs.agreement.open()">《相关协议》</text>
          </text>
        </uv-checkbox-group>
      </view>
    </view>
    <view class="sub" v-if="show">
      <button  :class="protocolChecked[0]?'':'nots'" class="sub_btn" @click="submit">提交</button>
      <button  :class="loginAct?'':'nots'" class="sub_btn" @click="submit">提交</button>
    </view>
    <agreement ref="agreement"></agreement>
pages/quanyi/components/agreement/index.vue
@@ -12,9 +12,13 @@
    const _this = this;
    uni.request( {
      url: _this.$webHost + "/api/v2/renpin/H5/getPlatformProtocolNew",
      data: {
        customerId: uni.getStorageSync('qxgLogin').customerId,
        appId: uni.getStorageSync('qxgLogin').appId
      },
      method: 'GET',
      success(res) {
        console.log(res.data.data);
        console.log(111,res.data.data);
        _this.list = res.data.data;
      }
    })
@@ -25,6 +29,7 @@
      this.$refs.agreement.open();
    },
    toProtocol(e){
      console.log(e);
      let title = e.name;
      let code = e.code;
      uni.navigateTo({
@@ -42,16 +47,16 @@
            zIndex="3">
    <uv-cell-group>
      <view class="popup-title">
        <view>请阅读以下协议</view>
        <view style="margin: auto;text-align: center;padding: 20rpx;">请阅读以下协议</view>
      </view>
      <view class="setting-list-item">
        <view
            class="setting-text"
            style="font-size: 32rpx; font-weight: bold; justify-content: left"
            style="font-size: 32rpx; font-weight: bold; justify-content: left;padding-left: 30rpx;"
        >平台服务协议</view
        >
      </view>
      <view class="list" style="height: 50vh;">
      <view class="list" style="height: 50vh;overflow: scroll;">
        <view class="li" @click="toProtocol(item)" v-for="(item,index) in list" :key="index" >
          <view style="font-size: 30rpx">{{item.name}}</view>
@@ -73,7 +78,10 @@
  display: flex;
  flex-direction: row;
  justify-content: center;
  margin: auto;
  margin-top: 38rpx;
  text-align: center;
}
.setting-list-item {
  display: flex;
pages/quanyi/index.vue
@@ -7,6 +7,10 @@
    const _this = this;
    await uni.request({
      url: _this.$webHost+'/api/v2/auth/qxgIosUnionLogin?phone='+_this.$util.getUserInfo().phone,
      data:{
        customerId: uni.getStorageSync('qxgLogin').customerId,
        appId: uni.getStorageSync('qxgLogin').appId
      },
      success: function (res) {
        _this.qxgLogin = res.data.data;
        uni.setStorageSync('qxgLogin',_this.qxgLogin)
@@ -117,12 +121,10 @@
</script>
<template>
  <view style="background-image: url('/static/imgs/QYBACKA.png');background-repeat: no-repeat;background-size: 100% 100%;background-color: #FFF4FA;">
  <view class="box" style="">
    <view class="head" style="position: relative">
      <view style="display: flex;justify-content: flex-start;z-index: 9">
        <view  @tap="goHome" id="home" class="home acea-row row-center-wrapper iconfont icon-fanhui2 h5_back icon-shouye4" :style="{ height:  '20rpx',lineHeight:  '80rpx'}" ></view>
      </view>
      <view style="margin-left: 20rpx;display: flex;justify-content: space-between;">
@@ -203,7 +205,7 @@
  background: #999 !important;
}
.head{
  padding: 30rpx 10rpx 0 30rpx;
  padding: 0rpx 10rpx 0 30rpx;
  font-size: 32rpx;
  color: #252938;
@@ -297,5 +299,11 @@
  display: flex;
  flex-direction: row;
}
.box{
  background-image: url('/static/imgs/QYBACKA.png');
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-color: #FFF4FA;
  min-height: 100vh;
}
</style>
pages/web/xieyi.vue
New file
@@ -0,0 +1,41 @@
<script>
export default {
  name: "xieyi",
  data(){
    return {
      text: ''
    }
  },
  onLoad(pro) {
    console.log(pro);
    let code =pro.code;
    let title = pro.title;
    const _this = this;
    uni.request( {
      url: _this.$webHost+`/api/v2/protocolInfo/getProtocol?appId=b2e24d04-6a97-40a0-9894-a44237c674ac&code=${code}`,
      success: function (res) {
        _this.text = res.data.data.content;
        uni.setNavigationBarTitle({
          title: title
        })
      }.bind(this)
    })
  },
  onShow() {
  },
}
</script>
<template>
  <view class="content">
    <view class="text-container"><rich-text :nodes="text"></rich-text></view>
  </view>
</template>
<style scoped lang="scss">
.content{
  height: calc( 100vh - 50px);
  overflow: scroll;
}
</style>
static/imgs/quanshou/FZV1.png
static/imgs/quanshou/FZV2.png
static/imgs/quanshou/FZV3.png
static/imgs/quanshou/FZV4.png
static/imgs/quanshou/FZV5.png
static/imgs/quanshou/FZV6.png
static/imgs/quanshou/KB1.png
static/imgs/quanshou/KB2.png
static/imgs/quanshou/KB3.png
static/imgs/quanshou/KB4.png
static/imgs/quanshou/KB5.png
static/imgs/quanshou/KB6.png
static/imgs/quanshou/NEWBOX1.png
static/imgs/quanshou/NEWBOX2.png
static/imgs/quanshou/S81.png
static/imgs/quanshou/S82.png
static/imgs/quanshou/S83.png
static/imgs/quanshou/S84.png
static/imgs/quanshou/S85.png
static/imgs/quanshou/S86.png
static/imgs/quanshou/S87.png
static/imgs/quanshou/S89.png
static/imgs/quanshou/SH1.png
static/imgs/quanshou/SH2.png
static/imgs/quanshou/SH3.png
static/imgs/quanshou/SH4.png
static/imgs/quanshou/SH5.png
static/imgs/quanshou/SH6.png
static/imgs/quanshou/SH7.png
static/imgs/quanshou/SH8.png
static/imgs/quanshou/SH9.png
static/imgs/quanshou/SW1.png
static/imgs/quanshou/SW2.png
static/imgs/quanshou/SW3.png
static/imgs/quanshou/SW4.png
static/imgs/quanshou/SW5.png
static/imgs/quanshou/SW6.png
static/imgs/quanshou/SW7.png
static/imgs/quanshou/SW8.png
static/imgs/quanshou/SWLL.png
static/imgs/quanshou/VIP1.png
static/imgs/quanshou/VIP2.png
static/imgs/quanshou/VIPFZ0.png
static/imgs/quanshou/W41.png
static/imgs/quanshou/W42.png
static/imgs/quanshou/W43.png
static/imgs/quanshou/W44.png
static/imgs/quanshou/Y51.png
static/imgs/quanshou/Y52.png
static/imgs/quanshou/Y53.png
static/imgs/quanshou/Y54.png
static/imgs/quanshou/Y55.png
static/imgs/quanshou/ZC1.png
static/imgs/quanshou/ZC2.png
static/imgs/quanshou/ZC3.png
uni_modules/oui-icon/changelog.md
New file
@@ -0,0 +1,19 @@
## 2.0.2(2025-11-13)
- [修复] click 事件类型定义为 unknown
## 2.0.1(2025-11-09)
- [修复] 修复小程序端图标显示问题
## 2.0.0(2025-09-26)
- [重构] 使用 TypeScript 重写
- [新增] 支持自定义字体图标
- [新增] 支持图片图标
## 1.1.0(2024-09-21)
- 新增一些图标
## 1.0.0(2024-08-28)
- 发布一些基础图标
uni_modules/oui-icon/components/oui-icon/iconfont.ttf
Binary files differ
uni_modules/oui-icon/components/oui-icon/icons.scss
New file
@@ -0,0 +1,2423 @@
@font-face {
  font-family: 'oui-icons';
  font-weight: normal;
  font-style: normal;
  /* #ifdef MP */
  src: url('https://at.alicdn.com/t/c/font_4666081_qd5aqo8pzil.ttf?t=1762660600975')
    format('truetype');
  /* #endif */
  /* #ifndef MP */
  src: url('./iconfont.ttf?t=1762660600975') format('truetype');
  /* #endif */
}
.oui-icon {
  display: inline-block;
  font-family: 'oui-icons' !important;
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  &::before {
    display: inline-block;
  }
  &--image {
    width: 1em;
    height: 1em;
  }
  &__image {
    width: 100%;
    height: 100%;
  }
}
.oui-icon-sort-ascending:before {
  content: '\e86a';
}
.oui-icon-zhihu-circle-fill:before {
  content: '\e96b';
}
.oui-icon-fall:before {
  content: '\e86b';
}
.oui-icon-reddit-circle-fill:before {
  content: '\e96c';
}
.oui-icon-swap:before {
  content: '\e86c';
}
.oui-icon-alipay-square-fill:before {
  content: '\e96d';
}
.oui-icon-stock:before {
  content: '\e86d';
}
.oui-icon-dingtalk-square-fill:before {
  content: '\e96e';
}
.oui-icon-rise:before {
  content: '\e86e';
}
.oui-icon-codeSandbox-square-f:before {
  content: '\e96f';
}
.oui-icon-indent:before {
  content: '\e86f';
}
.oui-icon-behance-square-fill:before {
  content: '\e970';
}
.oui-icon-outdent:before {
  content: '\e870';
}
.oui-icon-amazon-square-fill:before {
  content: '\e971';
}
.oui-icon-menu:before {
  content: '\e871';
}
.oui-icon-codepen-square-fill:before {
  content: '\e972';
}
.oui-icon-unorderedlist:before {
  content: '\e872';
}
.oui-icon-dribbble-square-fill:before {
  content: '\e973';
}
.oui-icon-orderedlist:before {
  content: '\e873';
}
.oui-icon-dropbox-square-fill:before {
  content: '\e974';
}
.oui-icon-align-right:before {
  content: '\e874';
}
.oui-icon-facebook-fill:before {
  content: '\e975';
}
.oui-icon-align-center:before {
  content: '\e875';
}
.oui-icon-googleplus-square-f:before {
  content: '\e976';
}
.oui-icon-align-left:before {
  content: '\e876';
}
.oui-icon-google-square-fill:before {
  content: '\e977';
}
.oui-icon-pic-center:before {
  content: '\e877';
}
.oui-icon-instagram-fill:before {
  content: '\e978';
}
.oui-icon-pic-right:before {
  content: '\e878';
}
.oui-icon-ie-square-fill:before {
  content: '\e979';
}
.oui-icon-pic-left:before {
  content: '\e879';
}
.oui-icon-medium-square-fill:before {
  content: '\e97a';
}
.oui-icon-bold:before {
  content: '\e87a';
}
.oui-icon-linkedin-fill:before {
  content: '\e97b';
}
.oui-icon-font-colors:before {
  content: '\e87b';
}
.oui-icon-qq-square-fill:before {
  content: '\e97c';
}
.oui-icon-exclaimination:before {
  content: '\e87c';
}
.oui-icon-reddit-square-fill:before {
  content: '\e97d';
}
.oui-icon-font-size:before {
  content: '\e87d';
}
.oui-icon-twitter-square-fill:before {
  content: '\e97e';
}
.oui-icon-infomation:before {
  content: '\e87e';
}
.oui-icon-sketch-square-fill:before {
  content: '\e97f';
}
.oui-icon-line-height:before {
  content: '\e87f';
}
.oui-icon-slack-square-fill:before {
  content: '\e980';
}
.oui-icon-strikethrough:before {
  content: '\e880';
}
.oui-icon-taobao-square-fill:before {
  content: '\e981';
}
.oui-icon-underline:before {
  content: '\e881';
}
.oui-icon-weibo-square-fill:before {
  content: '\e982';
}
.oui-icon-number:before {
  content: '\e882';
}
.oui-icon-zhihu-square-fill:before {
  content: '\e983';
}
.oui-icon-italic:before {
  content: '\e883';
}
.oui-icon-zoomout:before {
  content: '\e984';
}
.oui-icon-code:before {
  content: '\e884';
}
.oui-icon-apartment:before {
  content: '\e985';
}
.oui-icon-column-width:before {
  content: '\e885';
}
.oui-icon-audio:before {
  content: '\e986';
}
.oui-icon-check:before {
  content: '\e886';
}
.oui-icon-audio-fill:before {
  content: '\e987';
}
.oui-icon-ellipsis:before {
  content: '\e887';
}
.oui-icon-robot:before {
  content: '\e988';
}
.oui-icon-check-circle:before {
  content: '\e77d';
}
.oui-icon-dash:before {
  content: '\e888';
}
.oui-icon-zoomin:before {
  content: '\e989';
}
.oui-icon-ci:before {
  content: '\e77e';
}
.oui-icon-close:before {
  content: '\e889';
}
.oui-icon-robot-fill:before {
  content: '\e98a';
}
.oui-icon-dollar:before {
  content: '\e77f';
}
.oui-icon-enter:before {
  content: '\e88a';
}
.oui-icon-bug-fill:before {
  content: '\e98b';
}
.oui-icon-compass:before {
  content: '\e780';
}
.oui-icon-line:before {
  content: '\e88b';
}
.oui-icon-bug:before {
  content: '\e98c';
}
.oui-icon-close-circle:before {
  content: '\e781';
}
.oui-icon-question:before {
  content: '\e88d';
}
.oui-icon-audiostatic:before {
  content: '\e98d';
}
.oui-icon-frown:before {
  content: '\e782';
}
.oui-icon-rollback:before {
  content: '\e88e';
}
.oui-icon-comment:before {
  content: '\e98e';
}
.oui-icon-info-circle:before {
  content: '\e783';
}
.oui-icon-small-dash:before {
  content: '\e88f';
}
.oui-icon-signal-fill:before {
  content: '\e98f';
}
.oui-icon-left-circle:before {
  content: '\e784';
}
.oui-icon-pause:before {
  content: '\e890';
}
.oui-icon-verified:before {
  content: '\e990';
}
.oui-icon-down-circle:before {
  content: '\e785';
}
.oui-icon-bg-colors:before {
  content: '\e891';
}
.oui-icon-shortcut-fill:before {
  content: '\e991';
}
.oui-icon-euro:before {
  content: '\e786';
}
.oui-icon-crown:before {
  content: '\e892';
}
.oui-icon-videocameraadd:before {
  content: '\e992';
}
.oui-icon-copyright:before {
  content: '\e787';
}
.oui-icon-drag:before {
  content: '\e893';
}
.oui-icon-switchuser:before {
  content: '\e993';
}
.oui-icon-minus-circle:before {
  content: '\e788';
}
.oui-icon-desktop:before {
  content: '\e894';
}
.oui-icon-whatsapp:before {
  content: '\e994';
}
.oui-icon-meh:before {
  content: '\e789';
}
.oui-icon-gift:before {
  content: '\e895';
}
.oui-icon-appstoreadd:before {
  content: '\e995';
}
.oui-icon-plus-circle:before {
  content: '\e78a';
}
.oui-icon-stop:before {
  content: '\e896';
}
.oui-icon-caret-down:before {
  content: '\e996';
}
.oui-icon-play-circle:before {
  content: '\e78b';
}
.oui-icon-fire:before {
  content: '\e897';
}
.oui-icon-backward:before {
  content: '\e997';
}
.oui-icon-question-circle:before {
  content: '\e78c';
}
.oui-icon-thunderbolt:before {
  content: '\e898';
}
.oui-icon-caret-up:before {
  content: '\e998';
}
.oui-icon-pound:before {
  content: '\e78d';
}
.oui-icon-check-circle-fill:before {
  content: '\e899';
}
.oui-icon-caret-right:before {
  content: '\e999';
}
.oui-icon-right-circle:before {
  content: '\e78e';
}
.oui-icon-left-circle-fill:before {
  content: '\e89a';
}
.oui-icon-caret-left:before {
  content: '\e99a';
}
.oui-icon-smile:before {
  content: '\e78f';
}
.oui-icon-down-circle-fill:before {
  content: '\e89b';
}
.oui-icon-fast-backward:before {
  content: '\e99b';
}
.oui-icon-trademark:before {
  content: '\e790';
}
.oui-icon-minus-circle-fill:before {
  content: '\e89c';
}
.oui-icon-forward:before {
  content: '\e99c';
}
.oui-icon-time-circle:before {
  content: '\e791';
}
.oui-icon-close-circle-fill:before {
  content: '\e89d';
}
.oui-icon-fast-forward:before {
  content: '\e99d';
}
.oui-icon-timeout:before {
  content: '\e792';
}
.oui-icon-info-circle-fill:before {
  content: '\e89e';
}
.oui-icon-search:before {
  content: '\e99e';
}
.oui-icon-earth:before {
  content: '\e793';
}
.oui-icon-up-circle-fill:before {
  content: '\e89f';
}
.oui-icon-retweet:before {
  content: '\e99f';
}
.oui-icon-yuan:before {
  content: '\e794';
}
.oui-icon-right-circle-fill:before {
  content: '\e8a0';
}
.oui-icon-login:before {
  content: '\e9a0';
}
.oui-icon-up-circle:before {
  content: '\e795';
}
.oui-icon-plus-circle-fill:before {
  content: '\e8a1';
}
.oui-icon-step-backward:before {
  content: '\e9a1';
}
.oui-icon-warning-circle:before {
  content: '\e796';
}
.oui-icon-question-circle-fill:before {
  content: '\e8a2';
}
.oui-icon-step-forward:before {
  content: '\e9a2';
}
.oui-icon-sync:before {
  content: '\e797';
}
.oui-icon-euro-circle-fill:before {
  content: '\e8a3';
}
.oui-icon-swap-right:before {
  content: '\e9a3';
}
.oui-icon-transaction:before {
  content: '\e798';
}
.oui-icon-frown-fill:before {
  content: '\e8a4';
}
.oui-icon-swap-left:before {
  content: '\e9a4';
}
.oui-icon-undo:before {
  content: '\e799';
}
.oui-icon-copyright-circle-fil:before {
  content: '\e8a5';
}
.oui-icon-woman:before {
  content: '\e9a5';
}
.oui-icon-redo:before {
  content: '\e79a';
}
.oui-icon-ci-circle-fill:before {
  content: '\e8a6';
}
.oui-icon-eyeclose-fill:before {
  content: '\e9a6';
}
.oui-icon-reload:before {
  content: '\e79b';
}
.oui-icon-compass-fill:before {
  content: '\e8a7';
}
.oui-icon-eye-close:before {
  content: '\e9a7';
}
.oui-icon-reloadtime:before {
  content: '\e79c';
}
.oui-icon-dollar-circle-fill:before {
  content: '\e8a8';
}
.oui-icon-clear:before {
  content: '\e9a9';
}
.oui-icon-message:before {
  content: '\e79d';
}
.oui-icon-poweroff-circle-fill:before {
  content: '\e8a9';
}
.oui-icon-collapse:before {
  content: '\e9aa';
}
.oui-icon-dashboard:before {
  content: '\e79e';
}
.oui-icon-meh-fill:before {
  content: '\e8aa';
}
.oui-icon-expand:before {
  content: '\e9ab';
}
.oui-icon-issuesclose:before {
  content: '\e79f';
}
.oui-icon-play-circle-fill:before {
  content: '\e8ab';
}
.oui-icon-deletecolumn:before {
  content: '\e9ac';
}
.oui-icon-poweroff:before {
  content: '\e7a0';
}
.oui-icon-pound-circle-fill:before {
  content: '\e8ac';
}
.oui-icon-merge-cells:before {
  content: '\e9ad';
}
.oui-icon-logout:before {
  content: '\e7a1';
}
.oui-icon-smile-fill:before {
  content: '\e8ad';
}
.oui-icon-subnode:before {
  content: '\e9ae';
}
.oui-icon-piechart:before {
  content: '\e7a2';
}
.oui-icon-stop-fill:before {
  content: '\e8ae';
}
.oui-icon-rotate-left:before {
  content: '\e9af';
}
.oui-icon-setting:before {
  content: '\e7a3';
}
.oui-icon-warning-circle-fill:before {
  content: '\e8af';
}
.oui-icon-rotate-right:before {
  content: '\e9b0';
}
.oui-icon-eye:before {
  content: '\e7a4';
}
.oui-icon-time-circle-fill:before {
  content: '\e8b0';
}
.oui-icon-insertrowbelow:before {
  content: '\e9b1';
}
.oui-icon-location:before {
  content: '\e7a5';
}
.oui-icon-trademark-circle-fil:before {
  content: '\e8b1';
}
.oui-icon-insertrowabove:before {
  content: '\e9b2';
}
.oui-icon-edit-square:before {
  content: '\e7a6';
}
.oui-icon-yuan-circle-fill:before {
  content: '\e8b2';
}
.oui-icon-table1:before {
  content: '\e9b3';
}
.oui-icon-export:before {
  content: '\e7a7';
}
.oui-icon-heart-fill:before {
  content: '\e8b3';
}
.oui-icon-solit-cells:before {
  content: '\e9b4';
}
.oui-icon-save:before {
  content: '\e7a8';
}
.oui-icon-piechart-circle-fil:before {
  content: '\e8b4';
}
.oui-icon-formatpainter:before {
  content: '\e9b5';
}
.oui-icon-import:before {
  content: '\e7a9';
}
.oui-icon-dashboard-fill:before {
  content: '\e8b5';
}
.oui-icon-insertrowright:before {
  content: '\e9b6';
}
.oui-icon-appstore:before {
  content: '\e7aa';
}
.oui-icon-message-fill:before {
  content: '\e8b6';
}
.oui-icon-formatpainter-fill:before {
  content: '\e9b7';
}
.oui-icon-close-square:before {
  content: '\e7ab';
}
.oui-icon-check-square-fill:before {
  content: '\e8b7';
}
.oui-icon-insertrowleft:before {
  content: '\e9b8';
}
.oui-icon-down-square:before {
  content: '\e7ac';
}
.oui-icon-down-square-fill:before {
  content: '\e8b8';
}
.oui-icon-translate:before {
  content: '\e9b9';
}
.oui-icon-layout:before {
  content: '\e7ad';
}
.oui-icon-minus-square-fill:before {
  content: '\e8b9';
}
.oui-icon-deleterow:before {
  content: '\e9ba';
}
.oui-icon-left-square:before {
  content: '\e7ae';
}
.oui-icon-close-square-fill:before {
  content: '\e8ba';
}
.oui-icon-sisternode:before {
  content: '\e9bb';
}
.oui-icon-play-square:before {
  content: '\e7af';
}
.oui-icon-codelibrary-fill:before {
  content: '\e8bb';
}
.oui-icon-field-number:before {
  content: '\e9bc';
}
.oui-icon-control:before {
  content: '\e7b0';
}
.oui-icon-left-square-fill:before {
  content: '\e8bc';
}
.oui-icon-field-string:before {
  content: '\e9bd';
}
.oui-icon-codelibrary:before {
  content: '\e7b1';
}
.oui-icon-play-square-fill:before {
  content: '\e8bd';
}
.oui-icon-function:before {
  content: '\e9be';
}
.oui-icon-detail:before {
  content: '\e7b2';
}
.oui-icon-up-square-fill:before {
  content: '\e8be';
}
.oui-icon-field-time:before {
  content: '\e9bf';
}
.oui-icon-minus-square:before {
  content: '\e7b3';
}
.oui-icon-right-square-fill:before {
  content: '\e8bf';
}
.oui-icon-gif:before {
  content: '\e9c0';
}
.oui-icon-plus-square:before {
  content: '\e7b4';
}
.oui-icon-plus-square-fill:before {
  content: '\e8c0';
}
.oui-icon-partition:before {
  content: '\e9c1';
}
.oui-icon-right-square:before {
  content: '\e7b5';
}
.oui-icon-accountbook-fill:before {
  content: '\e8c1';
}
.oui-icon-index:before {
  content: '\e9c2';
}
.oui-icon-project:before {
  content: '\e7b6';
}
.oui-icon-carryout-fill:before {
  content: '\e8c2';
}
.oui-icon-storedprocedure:before {
  content: '\e9c3';
}
.oui-icon-wallet:before {
  content: '\e7b7';
}
.oui-icon-calendar-fill:before {
  content: '\e8c3';
}
.oui-icon-console-sql:before {
  content: '\e9c4';
}
.oui-icon-up-square:before {
  content: '\e7b8';
}
.oui-icon-calculator-fill:before {
  content: '\e8c4';
}
.oui-icon-icon-test:before {
  content: '\e9c5';
}
.oui-icon-calculator:before {
  content: '\e7b9';
}
.oui-icon-interation-fill:before {
  content: '\e8c5';
}
.oui-icon-aim:before {
  content: '\e9c6';
}
.oui-icon-interation:before {
  content: '\e7ba';
}
.oui-icon-project-fill:before {
  content: '\e8c6';
}
.oui-icon-compress:before {
  content: '\e9c7';
}
.oui-icon-check-square:before {
  content: '\e7bb';
}
.oui-icon-detail-fill:before {
  content: '\e8c7';
}
.oui-icon-expend:before {
  content: '\e9c8';
}
.oui-icon-border:before {
  content: '\e7bc';
}
.oui-icon-save-fill:before {
  content: '\e8c8';
}
.oui-icon-folder-view:before {
  content: '\e9c9';
}
.oui-icon-border-outer:before {
  content: '\e7bd';
}
.oui-icon-wallet-fill:before {
  content: '\e8c9';
}
.oui-icon-file-gif:before {
  content: '\e9ca';
}
.oui-icon-border-top:before {
  content: '\e7be';
}
.oui-icon-control-fill:before {
  content: '\e8ca';
}
.oui-icon-group:before {
  content: '\e9cb';
}
.oui-icon-border-bottom:before {
  content: '\e7bf';
}
.oui-icon-layout-fill:before {
  content: '\e8cb';
}
.oui-icon-send:before {
  content: '\e9cc';
}
.oui-icon-border-left:before {
  content: '\e7c0';
}
.oui-icon-appstore-fill:before {
  content: '\e8cc';
}
.oui-icon-report:before {
  content: '\e9cd';
}
.oui-icon-border-right:before {
  content: '\e7c1';
}
.oui-icon-mobile-fill:before {
  content: '\e8cd';
}
.oui-icon-view:before {
  content: '\e9ce';
}
.oui-icon-border-inner:before {
  content: '\e7c2';
}
.oui-icon-tablet-fill:before {
  content: '\e8ce';
}
.oui-icon-shortcut:before {
  content: '\e9cf';
}
.oui-icon-border-verticle:before {
  content: '\e7c3';
}
.oui-icon-book-fill:before {
  content: '\e8cf';
}
.oui-icon-ungroup:before {
  content: '\e9d0';
}
.oui-icon-border-horizontal:before {
  content: '\e7c4';
}
.oui-icon-redenvelope-fill:before {
  content: '\e8d0';
}
.oui-icon-radius-bottomleft:before {
  content: '\e7c5';
}
.oui-icon-safetycertificate-f:before {
  content: '\e8d1';
}
.oui-icon-radius-bottomright:before {
  content: '\e7c6';
}
.oui-icon-propertysafety-fill:before {
  content: '\e8d2';
}
.oui-icon-radius-upleft:before {
  content: '\e7c7';
}
.oui-icon-insurance-fill:before {
  content: '\e8d3';
}
.oui-icon-radius-upright:before {
  content: '\e7c8';
}
.oui-icon-securityscan-fill:before {
  content: '\e8d4';
}
.oui-icon-radius-setting:before {
  content: '\e7c9';
}
.oui-icon-file-exclamation-fil:before {
  content: '\e8d5';
}
.oui-icon-adduser:before {
  content: '\e7ca';
}
.oui-icon-file-add-fill:before {
  content: '\e8d6';
}
.oui-icon-deleteteam:before {
  content: '\e7cb';
}
.oui-icon-file-fill:before {
  content: '\e8d7';
}
.oui-icon-deleteuser:before {
  content: '\e7cc';
}
.oui-icon-file-excel-fill:before {
  content: '\e8d8';
}
.oui-icon-addteam:before {
  content: '\e7cd';
}
.oui-icon-file-markdown-fill:before {
  content: '\e8d9';
}
.oui-icon-team:before {
  content: '\e7cf';
}
.oui-icon-file-text-fill:before {
  content: '\e8da';
}
.oui-icon-areachart:before {
  content: '\e7d0';
}
.oui-icon-file-ppt-fill:before {
  content: '\e8db';
}
.oui-icon-linechart:before {
  content: '\e7d1';
}
.oui-icon-file-unknown-fill:before {
  content: '\e8dc';
}
.oui-icon-barchart:before {
  content: '\e7d2';
}
.oui-icon-file-word-fill:before {
  content: '\e8dd';
}
.oui-icon-pointmap:before {
  content: '\e7d3';
}
.oui-icon-file-zip-fill:before {
  content: '\e8de';
}
.oui-icon-container:before {
  content: '\e7d4';
}
.oui-icon-file-pdf-fill:before {
  content: '\e8df';
}
.oui-icon-database:before {
  content: '\e7d5';
}
.oui-icon-file-image-fill:before {
  content: '\e8e0';
}
.oui-icon-sever:before {
  content: '\e7d6';
}
.oui-icon-diff-fill:before {
  content: '\e8e1';
}
.oui-icon-mobile:before {
  content: '\e7d7';
}
.oui-icon-file-copy-fill:before {
  content: '\e8e2';
}
.oui-icon-tablet:before {
  content: '\e7d8';
}
.oui-icon-snippets-fill:before {
  content: '\e8e3';
}
.oui-icon-redenvelope:before {
  content: '\e7d9';
}
.oui-icon-batchfolding-fill:before {
  content: '\e8e4';
}
.oui-icon-book:before {
  content: '\e7da';
}
.oui-icon-reconciliation-fill:before {
  content: '\e8e5';
}
.oui-icon-filedone:before {
  content: '\e7db';
}
.oui-icon-folder-add-fill:before {
  content: '\e8e6';
}
.oui-icon-reconciliation:before {
  content: '\e7dc';
}
.oui-icon-folder-fill:before {
  content: '\e8e7';
}
.oui-icon-file-exception:before {
  content: '\e7dd';
}
.oui-icon-folder-open-fill:before {
  content: '\e8e8';
}
.oui-icon-filesync:before {
  content: '\e7de';
}
.oui-icon-database-fill:before {
  content: '\e8e9';
}
.oui-icon-filesearch:before {
  content: '\e7df';
}
.oui-icon-container-fill:before {
  content: '\e8ea';
}
.oui-icon-solution:before {
  content: '\e7e0';
}
.oui-icon-sever-fill:before {
  content: '\e8eb';
}
.oui-icon-fileprotect:before {
  content: '\e7e1';
}
.oui-icon-calendar-check-fill:before {
  content: '\e8ec';
}
.oui-icon-file-add:before {
  content: '\e7e2';
}
.oui-icon-image-fill:before {
  content: '\e8ed';
}
.oui-icon-file-excel:before {
  content: '\e7e3';
}
.oui-icon-idcard-fill:before {
  content: '\e8ee';
}
.oui-icon-file-exclamation:before {
  content: '\e7e4';
}
.oui-icon-creditcard-fill:before {
  content: '\e8ef';
}
.oui-icon-file-pdf:before {
  content: '\e7e5';
}
.oui-icon-fund-fill:before {
  content: '\e8f0';
}
.oui-icon-file-image:before {
  content: '\e7e6';
}
.oui-icon-read-fill:before {
  content: '\e8f1';
}
.oui-icon-file-markdown:before {
  content: '\e7e7';
}
.oui-icon-contacts-fill:before {
  content: '\e8f2';
}
.oui-icon-file-unknown:before {
  content: '\e7e8';
}
.oui-icon-delete-fill:before {
  content: '\e8f3';
}
.oui-icon-file-ppt:before {
  content: '\e7e9';
}
.oui-icon-notification-fill:before {
  content: '\e8f4';
}
.oui-icon-file-word:before {
  content: '\e7ea';
}
.oui-icon-flag-fill:before {
  content: '\e8f5';
}
.oui-icon-file:before {
  content: '\e7eb';
}
.oui-icon-moneycollect-fill:before {
  content: '\e8f6';
}
.oui-icon-file-zip:before {
  content: '\e7ec';
}
.oui-icon-medicinebox-fill:before {
  content: '\e8f7';
}
.oui-icon-file-text:before {
  content: '\e7ed';
}
.oui-icon-rest-fill:before {
  content: '\e8f8';
}
.oui-icon-file-copy:before {
  content: '\e7ee';
}
.oui-icon-shopping-fill:before {
  content: '\e8f9';
}
.oui-icon-snippets:before {
  content: '\e7ef';
}
.oui-icon-skin-fill:before {
  content: '\e8fa';
}
.oui-icon-audit:before {
  content: '\e7f0';
}
.oui-icon-video-fill:before {
  content: '\e8fb';
}
.oui-icon-diff:before {
  content: '\e7f1';
}
.oui-icon-sound-fill:before {
  content: '\e8fc';
}
.oui-icon-batchfolding:before {
  content: '\e7f2';
}
.oui-icon-bulb-fill:before {
  content: '\e8fd';
}
.oui-icon-securityscan:before {
  content: '\e7f3';
}
.oui-icon-bell-fill:before {
  content: '\e8fe';
}
.oui-icon-propertysafety:before {
  content: '\e7f4';
}
.oui-icon-filter-fill:before {
  content: '\e8ff';
}
.oui-icon-safetycertificate:before {
  content: '\e7f5';
}
.oui-icon-fire-fill:before {
  content: '\e900';
}
.oui-icon-insurance:before {
  content: '\e7f6';
}
.oui-icon-funnelplot-fill:before {
  content: '\e901';
}
.oui-icon-alert:before {
  content: '\e7f7';
}
.oui-icon-gift-fill:before {
  content: '\e902';
}
.oui-icon-delete:before {
  content: '\e7f8';
}
.oui-icon-hourglass-fill:before {
  content: '\e903';
}
.oui-icon-hourglass:before {
  content: '\e7f9';
}
.oui-icon-home-fill:before {
  content: '\e904';
}
.oui-icon-bulb:before {
  content: '\e7fa';
}
.oui-icon-trophy-fill:before {
  content: '\e905';
}
.oui-icon-experiment:before {
  content: '\e7fb';
}
.oui-icon-location-fill:before {
  content: '\e906';
}
.oui-icon-bell:before {
  content: '\e7fc';
}
.oui-icon-cloud-fill:before {
  content: '\e907';
}
.oui-icon-trophy:before {
  content: '\e7fd';
}
.oui-icon-customerservice-fill:before {
  content: '\e908';
}
.oui-icon-rest:before {
  content: '\e7fe';
}
.oui-icon-experiment-fill:before {
  content: '\e909';
}
.oui-icon-usb:before {
  content: '\e7ff';
}
.oui-icon-eye-fill:before {
  content: '\e90a';
}
.oui-icon-skin:before {
  content: '\e800';
}
.oui-icon-like-fill:before {
  content: '\e90b';
}
.oui-icon-home:before {
  content: '\e801';
}
.oui-icon-lock-fill:before {
  content: '\e90c';
}
.oui-icon-bank:before {
  content: '\e802';
}
.oui-icon-unlike-fill:before {
  content: '\e90d';
}
.oui-icon-filter:before {
  content: '\e803';
}
.oui-icon-star-fill:before {
  content: '\e90e';
}
.oui-icon-funnelplot:before {
  content: '\e804';
}
.oui-icon-alert-fill:before {
  content: '\e90f';
}
.oui-icon-like:before {
  content: '\e805';
}
.oui-icon-highlight-fill:before {
  content: '\e910';
}
.oui-icon-unlike:before {
  content: '\e806';
}
.oui-icon-phone-fill:before {
  content: '\e911';
}
.oui-icon-unlock:before {
  content: '\e807';
}
.oui-icon-edit-fill:before {
  content: '\e912';
}
.oui-icon-lock:before {
  content: '\e808';
}
.oui-icon-pushpin-fill:before {
  content: '\e913';
}
.oui-icon-customerservice:before {
  content: '\e809';
}
.oui-icon-rocket-fill:before {
  content: '\e914';
}
.oui-icon-flag:before {
  content: '\e80a';
}
.oui-icon-thunderbolt-fill:before {
  content: '\e915';
}
.oui-icon-moneycollect:before {
  content: '\e80b';
}
.oui-icon-tag-fill:before {
  content: '\e916';
}
.oui-icon-medicinebox:before {
  content: '\e80c';
}
.oui-icon-wrench-fill:before {
  content: '\e917';
}
.oui-icon-shop:before {
  content: '\e80d';
}
.oui-icon-tags-fill:before {
  content: '\e918';
}
.oui-icon-rocket:before {
  content: '\e80e';
}
.oui-icon-bank-fill:before {
  content: '\e919';
}
.oui-icon-shopping:before {
  content: '\e80f';
}
.oui-icon-camera-fill:before {
  content: '\e91a';
}
.oui-icon-folder:before {
  content: '\e810';
}
.oui-icon-error-fill:before {
  content: '\e91b';
}
.oui-icon-folder-open:before {
  content: '\e811';
}
.oui-icon-crown-fill:before {
  content: '\e91c';
}
.oui-icon-folder-add:before {
  content: '\e812';
}
.oui-icon-mail-fill:before {
  content: '\e91d';
}
.oui-icon-deploymentunit:before {
  content: '\e813';
}
.oui-icon-car-fill:before {
  content: '\e91e';
}
.oui-icon-accountbook:before {
  content: '\e814';
}
.oui-icon-printer-fill:before {
  content: '\e91f';
}
.oui-icon-contacts:before {
  content: '\e815';
}
.oui-icon-shop-fill:before {
  content: '\e920';
}
.oui-icon-carryout:before {
  content: '\e816';
}
.oui-icon-setting-fill:before {
  content: '\e921';
}
.oui-icon-calendar-check:before {
  content: '\e817';
}
.oui-icon-usb-fill:before {
  content: '\e922';
}
.oui-icon-calendar:before {
  content: '\e818';
}
.oui-icon-golden-fill:before {
  content: '\e923';
}
.oui-icon-scan:before {
  content: '\e819';
}
.oui-icon-build-fill:before {
  content: '\e924';
}
.oui-icon-select:before {
  content: '\e81a';
}
.oui-icon-boxplot-fill:before {
  content: '\e925';
}
.oui-icon-boxplot:before {
  content: '\e81b';
}
.oui-icon-sliders-fill:before {
  content: '\e926';
}
.oui-icon-build:before {
  content: '\e81c';
}
.oui-icon-alibaba:before {
  content: '\e927';
}
.oui-icon-sliders:before {
  content: '\e81d';
}
.oui-icon-antdesign:before {
  content: '\e928';
}
.oui-icon-laptop:before {
  content: '\e81e';
}
.oui-icon-ant-cloud:before {
  content: '\e929';
}
.oui-icon-barcode:before {
  content: '\e81f';
}
.oui-icon-behance:before {
  content: '\e92a';
}
.oui-icon-camera:before {
  content: '\e820';
}
.oui-icon-googleplus:before {
  content: '\e92b';
}
.oui-icon-cluster:before {
  content: '\e821';
}
.oui-icon-medium:before {
  content: '\e92c';
}
.oui-icon-gateway:before {
  content: '\e822';
}
.oui-icon-google:before {
  content: '\e92d';
}
.oui-icon-car:before {
  content: '\e823';
}
.oui-icon-ie:before {
  content: '\e92e';
}
.oui-icon-printer:before {
  content: '\e824';
}
.oui-icon-amazon:before {
  content: '\e92f';
}
.oui-icon-read:before {
  content: '\e825';
}
.oui-icon-slack:before {
  content: '\e930';
}
.oui-icon-cloud-server:before {
  content: '\e826';
}
.oui-icon-alipay:before {
  content: '\e931';
}
.oui-icon-cloud-upload:before {
  content: '\e827';
}
.oui-icon-taobao:before {
  content: '\e932';
}
.oui-icon-cloud:before {
  content: '\e828';
}
.oui-icon-zhihu:before {
  content: '\e933';
}
.oui-icon-cloud-download:before {
  content: '\e829';
}
.oui-icon-html:before {
  content: '\e934';
}
.oui-icon-cloud-sync:before {
  content: '\e82a';
}
.oui-icon-linkedin:before {
  content: '\e935';
}
.oui-icon-video:before {
  content: '\e82b';
}
.oui-icon-yahoo:before {
  content: '\e936';
}
.oui-icon-notification:before {
  content: '\e82c';
}
.oui-icon-facebook:before {
  content: '\e937';
}
.oui-icon-sound:before {
  content: '\e82d';
}
.oui-icon-skype:before {
  content: '\e938';
}
.oui-icon-radarchart:before {
  content: '\e82e';
}
.oui-icon-codeSandbox:before {
  content: '\e939';
}
.oui-icon-qrcode:before {
  content: '\e82f';
}
.oui-icon-chrome:before {
  content: '\e93a';
}
.oui-icon-fund:before {
  content: '\e830';
}
.oui-icon-codepen:before {
  content: '\e93b';
}
.oui-icon-image:before {
  content: '\e831';
}
.oui-icon-aliwangwang:before {
  content: '\e93c';
}
.oui-icon-mail:before {
  content: '\e832';
}
.oui-icon-apple:before {
  content: '\e93d';
}
.oui-icon-table:before {
  content: '\e833';
}
.oui-icon-android:before {
  content: '\e93e';
}
.oui-icon-idcard:before {
  content: '\e834';
}
.oui-icon-sketch:before {
  content: '\e93f';
}
.oui-icon-creditcard:before {
  content: '\e835';
}
.oui-icon-gitlab:before {
  content: '\e940';
}
.oui-icon-heart:before {
  content: '\e836';
}
.oui-icon-dribbble:before {
  content: '\e941';
}
.oui-icon-block:before {
  content: '\e837';
}
.oui-icon-instagram:before {
  content: '\e942';
}
.oui-icon-error:before {
  content: '\e838';
}
.oui-icon-reddit:before {
  content: '\e943';
}
.oui-icon-star:before {
  content: '\e839';
}
.oui-icon-windows:before {
  content: '\e944';
}
.oui-icon-gold:before {
  content: '\e83a';
}
.oui-icon-yuque:before {
  content: '\e945';
}
.oui-icon-heatmap:before {
  content: '\e83b';
}
.oui-icon-youtube:before {
  content: '\e946';
}
.oui-icon-wifi:before {
  content: '\e83c';
}
.oui-icon-gitlab-fill:before {
  content: '\e947';
}
.oui-icon-attachment:before {
  content: '\e83d';
}
.oui-icon-dropbox:before {
  content: '\e948';
}
.oui-icon-edit:before {
  content: '\e83e';
}
.oui-icon-dingtalk:before {
  content: '\e949';
}
.oui-icon-key:before {
  content: '\e83f';
}
.oui-icon-android-fill:before {
  content: '\e94a';
}
.oui-icon-api:before {
  content: '\e840';
}
.oui-icon-apple-fill:before {
  content: '\e94b';
}
.oui-icon-disconnect:before {
  content: '\e841';
}
.oui-icon-html-fill:before {
  content: '\e94c';
}
.oui-icon-highlight:before {
  content: '\e842';
}
.oui-icon-windows-fill:before {
  content: '\e94d';
}
.oui-icon-monitor:before {
  content: '\e843';
}
.oui-icon-qq:before {
  content: '\e94e';
}
.oui-icon-link:before {
  content: '\e844';
}
.oui-icon-twitter:before {
  content: '\e94f';
}
.oui-icon-man:before {
  content: '\e845';
}
.oui-icon-skype-fill:before {
  content: '\e950';
}
.oui-icon-percentage:before {
  content: '\e846';
}
.oui-icon-weibo:before {
  content: '\e951';
}
.oui-icon-pushpin:before {
  content: '\e847';
}
.oui-icon-yuque-fill:before {
  content: '\e952';
}
.oui-icon-phone:before {
  content: '\e848';
}
.oui-icon-youtube-fill:before {
  content: '\e953';
}
.oui-icon-shake:before {
  content: '\e849';
}
.oui-icon-yahoo-fill:before {
  content: '\e954';
}
.oui-icon-tag:before {
  content: '\e84a';
}
.oui-icon-wechat-fill:before {
  content: '\e955';
}
.oui-icon-wrench:before {
  content: '\e84b';
}
.oui-icon-chrome-fill:before {
  content: '\e956';
}
.oui-icon-tags:before {
  content: '\e84c';
}
.oui-icon-alipay-circle-fill:before {
  content: '\e957';
}
.oui-icon-scissor:before {
  content: '\e84d';
}
.oui-icon-aliwangwang-fill:before {
  content: '\e958';
}
.oui-icon-mr:before {
  content: '\e84e';
}
.oui-icon-behance-circle-fill:before {
  content: '\e959';
}
.oui-icon-share:before {
  content: '\e84f';
}
.oui-icon-amazon-circle-fill:before {
  content: '\e95a';
}
.oui-icon-branches:before {
  content: '\e850';
}
.oui-icon-codepen-circle-fill:before {
  content: '\e95b';
}
.oui-icon-fork:before {
  content: '\e851';
}
.oui-icon-codeSandbox-circle-f:before {
  content: '\e95c';
}
.oui-icon-shrink:before {
  content: '\e852';
}
.oui-icon-dropbox-circle-fill:before {
  content: '\e95d';
}
.oui-icon-arrawsalt:before {
  content: '\e853';
}
.oui-icon-github-fill:before {
  content: '\e95e';
}
.oui-icon-verticalright:before {
  content: '\e854';
}
.oui-icon-dribbble-circle-fill:before {
  content: '\e95f';
}
.oui-icon-verticalleft:before {
  content: '\e855';
}
.oui-icon-googleplus-circle-f:before {
  content: '\e960';
}
.oui-icon-fullscreen:before {
  content: '\e85a';
}
.oui-icon-medium-circle-fill:before {
  content: '\e961';
}
.oui-icon-fullscreen-exit:before {
  content: '\e85b';
}
.oui-icon-qq-circle-fill:before {
  content: '\e962';
}
.oui-icon-doubleleft:before {
  content: '\e85c';
}
.oui-icon-ie-circle-fill:before {
  content: '\e963';
}
.oui-icon-doubleright:before {
  content: '\e85d';
}
.oui-icon-google-circle-fill:before {
  content: '\e964';
}
.oui-icon-colum-height:before {
  content: '\e863';
}
.oui-icon-dingtalk-circle-fill:before {
  content: '\e965';
}
.oui-icon-vertical-align-botto:before {
  content: '\e864';
}
.oui-icon-sketch-circle-fill:before {
  content: '\e966';
}
.oui-icon-vertical-align-middl:before {
  content: '\e865';
}
.oui-icon-slack-circle-fill:before {
  content: '\e967';
}
.oui-icon-totop:before {
  content: '\e866';
}
.oui-icon-twitter-circle-fill:before {
  content: '\e968';
}
.oui-icon-vertical-align-top:before {
  content: '\e867';
}
.oui-icon-taobao-circle-fill:before {
  content: '\e969';
}
.oui-icon-sort-descending:before {
  content: '\e869';
}
.oui-icon-weibo-circle-fill:before {
  content: '\e96a';
}
.oui-icon-user:before {
  content: '\e7ce';
}
.oui-icon-right:before {
  content: '\e856';
}
.oui-icon-left:before {
  content: '\e857';
}
.oui-icon-up:before {
  content: '\e858';
}
.oui-icon-down:before {
  content: '\e859';
}
.oui-icon-arrowright:before {
  content: '\e85e';
}
.oui-icon-arrowup:before {
  content: '\e85f';
}
.oui-icon-arrowleft:before {
  content: '\e860';
}
.oui-icon-arrowdown:before {
  content: '\e861';
}
.oui-icon-upload:before {
  content: '\e862';
}
.oui-icon-download:before {
  content: '\e868';
}
.oui-icon-minus:before {
  content: '\e88c';
}
.oui-icon-plus:before {
  content: '\e9a8';
}
uni_modules/oui-icon/components/oui-icon/oui-icon.vue
New file
@@ -0,0 +1,56 @@
<template>
  <view :class="rootClass" :style="rootStyle" @click="handleClick">
    <image v-if="isImage" class="oui-icon__image" :src="name" />
  </view>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { CSSProperties } from 'vue';
import { addUnit, convertStyle, test } from '@/uni_modules/oui-utils';
import type { IconEmits } from './types';
import { iconProps } from './types';
defineOptions({
  name: 'oui-icon',
  options: {
    virtualHost: true,
    addGlobalClass: true,
    styleIsolation: 'apply-shared',
  },
});
const emit = defineEmits<IconEmits>();
const props = defineProps(iconProps);
const isImage = computed(() => {
  return test.def(props.name) && props.name?.includes('/');
});
const rootClass = computed(() => {
  const prefix = props.classPrefix;
  return [prefix, props.customClass, isImage.value ? 'oui-icon--image' : `${prefix}-${props.name}`];
});
const rootStyle = computed(() => {
  let style: CSSProperties = {
    color: props.color,
    fontSize: addUnit(props.size),
  };
  if (props.customStyle) {
    style = { ...style, ...convertStyle(props.customStyle) };
  }
  return style;
});
const handleClick = (event: any) => {
  emit('click', event);
};
</script>
<style scoped lang="scss">
@use './icons.scss';
</style>
uni_modules/oui-icon/components/oui-icon/types.ts
New file
@@ -0,0 +1,28 @@
import type { ExtractPropTypes } from 'vue';
export const iconProps = {
  customClass: String,
  customStyle: String,
  classPrefix: {
    type: String,
    default: 'oui-icon',
  },
  name: {
    type: String,
    required: true,
  },
  size: {
    type: [String, Number],
    default: 16,
  },
  color: {
    type: String,
    default: '#000000',
  },
};
export interface IconEmits {
  (e: 'click', event: unknown): void;
}
export type IconProps = ExtractPropTypes<typeof iconProps>;
uni_modules/oui-icon/package.json
New file
@@ -0,0 +1,101 @@
{
  "id": "oui-icon",
  "displayName": "oui-icon",
  "version": "2.0.2",
  "description": "一个功能丰富的字体图标库",
  "keywords": [
    "图标",
    "oui",
    "icon"
],
  "repository": "",
  "engines": {
    "HBuilderX": "^3.1.0",
    "uni-app": "^4.41",
    "uni-app-x": ""
  },
  "dcloudext": {
    "type": "component-vue",
    "sale": {
      "regular": {
        "price": "0.00"
      },
      "sourcecode": {
        "price": "0.00"
      }
    },
    "contact": {
      "qq": ""
    },
    "declaration": {
      "ads": "无",
      "data": "无",
      "permissions": "无"
    },
    "npmurl": "",
    "darkmode": "x",
    "i18n": "x",
    "widescreen": "x"
  },
  "uni_modules": {
    "dependencies": [
      "oui-utils"
    ],
    "encrypt": [],
    "platforms": {
      "cloud": {
        "tcb": "√",
        "aliyun": "√",
        "alipay": "√"
      },
      "client": {
        "uni-app": {
          "vue": {
            "vue2": "√",
            "vue3": "√"
          },
          "web": {
            "safari": "√",
            "chrome": "√"
          },
          "app": {
            "vue": "√",
            "nvue": "√",
            "android": "√",
            "ios": "√",
            "harmony": "√"
          },
          "mp": {
            "weixin": "√",
            "alipay": "√",
            "toutiao": "√",
            "baidu": "√",
            "kuaishou": "√",
            "jd": "√",
            "harmony": "√",
            "qq": "√",
            "lark": "√"
          },
          "quickapp": {
            "huawei": "√",
            "union": "√"
          }
        },
        "uni-app-x": {
          "web": {
            "safari": "-",
            "chrome": "-"
          },
          "app": {
            "android": "-",
            "ios": "-",
            "harmony": "-"
          },
          "mp": {
            "weixin": "-"
          }
        }
      }
    }
  }
}
uni_modules/oui-icon/readme.md
New file
@@ -0,0 +1,177 @@
# oui-icon
一个功能丰富的字体图标库,基于 iconfont 字体文件,提供了丰富的图标资源。
## 特性
- **丰富图标库**:包含 600+ 个精美图标,涵盖常用场景
- **多端兼容**:支持 uni-app 全平台(H5、小程序、App)
- **灵活配置**:支持自定义大小、颜色、样式
- **图片支持**:除字体图标外,还支持图片图标
- **轻量高效**:基于字体文件,体积小,渲染快
- **易于使用**:简单的 API 设计,开箱即用
## 安装
```bash
# 通过 uni_modules 安装(推荐)
# 直接将 oui-icon 文件夹放入 src/uni_modules/ 目录下即可
```
## 基础用法
### 字体图标
```vue
<template>
  <view>
    <!-- 基础用法 -->
    <oui-icon name="home" />
    <!-- 自定义大小和颜色 -->
    <oui-icon name="heart" size="24" color="#ff4757" />
  </view>
</template>
```
### 图片图标
```vue
<template>
  <view>
    <!-- 使用图片路径 -->
    <oui-icon name="/static/icons/custom-icon.png" size="24" />
  </view>
</template>
```
### 事件处理
```vue
<template>
  <view>
    <oui-icon name="setting" @click="handleClick" />
  </view>
</template>
<script setup>
const handleClick = (event) => {
  console.log('图标被点击了', event);
};
</script>
```
## API
### Props
| 参数        | 类型             | 默认值   | 说明                               |
| ----------- | ---------------- | -------- | ---------------------------------- |
| name        | String           | -        | 图标名称或图片路径(必填)         |
| size        | String \| Number | 16       | 图标大小,支持数字或带单位的字符串 |
| color       | String           | #000000  | 图标颜色                           |
| classPrefix | String           | oui-icon | 图标类名前缀,用于自定义图标       |
| customClass | String           | -        | 自定义类名                         |
| customStyle | String           | -        | 自定义样式                         |
### Events
| 事件名 | 说明           | 回调参数     |
| ------ | -------------- | ------------ |
| click  | 点击图标时触发 | event: unknown |
## 图标列表
### 常用图标
```vue
<!-- 基础图标 -->
<oui-icon name="home" />
<!-- 首页 -->
<oui-icon name="user" />
<!-- 用户 -->
<oui-icon name="setting" />
<!-- 设置 -->
<oui-icon name="search" />
<!-- 搜索 -->
<oui-icon name="heart" />
<!-- 喜欢 -->
<oui-icon name="star" />
<!-- 星标 -->
<oui-icon name="location" />
<!-- 位置 -->
<oui-icon name="phone" />
<!-- 电话 -->
<oui-icon name="mail" />
<!-- 邮件 -->
<oui-icon name="camera" />
<!-- 相机 -->
<!-- 方向图标 -->
<oui-icon name="left" />
<!-- 左箭头 -->
<oui-icon name="right" />
<!-- 右箭头 -->
<oui-icon name="up" />
<!-- 上箭头 -->
<oui-icon name="down" />
<!-- 下箭头 -->
<!-- 操作图标 -->
<oui-icon name="plus" />
<!-- 加号 -->
<oui-icon name="minus" />
<!-- 减号 -->
<oui-icon name="close" />
<!-- 关闭 -->
<oui-icon name="check" />
<!-- 勾选 -->
<oui-icon name="edit" />
<!-- 编辑 -->
<oui-icon name="delete" />
<!-- 删除 -->
```
### 社交图标
```vue
<oui-icon name="wechat" />
<!-- 微信 -->
<oui-icon name="qq" />
<!-- QQ -->
<oui-icon name="weibo" />
<!-- 微博 -->
<oui-icon name="alipay" />
<!-- 支付宝 -->
<oui-icon name="facebook" />
<!-- Facebook -->
<oui-icon name="twitter" />
<!-- Twitter -->
<oui-icon name="linkedin" />
<!-- LinkedIn -->
<oui-icon name="github" />
<!-- GitHub -->
```
### 文件图标
```vue
<oui-icon name="file" />
<!-- 文件 -->
<oui-icon name="folder" />
<!-- 文件夹 -->
<oui-icon name="file-text" />
<!-- 文本文件 -->
<oui-icon name="file-pdf" />
<!-- PDF文件 -->
<oui-icon name="file-excel" />
<!-- Excel文件 -->
<oui-icon name="file-word" />
<!-- Word文件 -->
<oui-icon name="file-ppt" />
<!-- PPT文件 -->
<oui-icon name="file-image" />
<!-- 图片文件 -->
```
> 💡 **提示**:组件包含 600+ 个图标,完整图标列表请查看 `components/oui-icon/icons.scss` 文件,或在项目中使用开发者工具查看所有可用图标类名。
uni_modules/oui-utils/changelog.md
New file
@@ -0,0 +1,20 @@
## 1.2.1(2025-10-16)
- [优化] 一些文件导出方式优化
## 1.2.0(2025-10-14)
- [新增] 新增 uuid 工具类
- [新增] 新增 nullish 方法
- [新增] 新增 emptyString 方法
- [新增] 新增 whitespaceString 方法
- [新增] 新增 emptyArray 方法
- [新增] 新增 emptyObject 方法
- [新增] 新增 nan 方法
- [新增] 补充单元测试
## 1.0.1(2025-09-26)
- [修复] readme 中的一些错误
## 1.0.0(2025-09-26)
- 发布一些基础工具类
uni_modules/oui-utils/index.ts
New file
@@ -0,0 +1 @@
export * from './libs';
uni_modules/oui-utils/jest.config.js
New file
@@ -0,0 +1,12 @@
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/tests'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  collectCoverageFrom: [
    'libs/**/*.ts',
    '!libs/**/*.d.ts',
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
};
uni_modules/oui-utils/libs/index.ts
New file
@@ -0,0 +1,4 @@
export * from './object';
export * from './style';
export * from './uuid';
export { default as test } from './test';
uni_modules/oui-utils/libs/object.ts
New file
@@ -0,0 +1,158 @@
import $test from './test';
/**
 * @description 去除空格
 */
export function trim(
  str: string | number,
  pos: 'both' | 'left' | 'right' | 'all' = 'both',
): string {
  str = String(str);
  if (pos == 'both') {
    return str.replace(/^\s+|\s+$/g, '');
  }
  if (pos == 'left') {
    return str.replace(/^\s*/, '');
  }
  if (pos == 'right') {
    return str.replace(/(\s*$)/g, '');
  }
  if (pos == 'all') {
    return str.replace(/\s+/g, '');
  }
  return str;
}
/**
 * 内部递归克隆函数
 * @param obj 要克隆的对象
 * @param visited 已访问对象的映射,用于处理循环引用
 */
function cloneRecursive<T>(obj: T, visited: WeakMap<any, any>): T {
  if (obj === null || obj === undefined) {
    return obj;
  }
  if (typeof obj !== 'object') {
    return obj;
  }
  // 检查循环引用
  if (visited.has(obj as any)) {
    return visited.get(obj as any);
  }
  let cloned: any;
  if (obj instanceof Date) {
    cloned = new Date(obj.getTime());
    visited.set(obj as any, cloned);
  } else if (obj instanceof RegExp) {
    cloned = new RegExp(obj.source, obj.flags);
    visited.set(obj as any, cloned);
  } else if ($test.array(obj)) {
    cloned = [];
    visited.set(obj as any, cloned);
    const arr = obj as any;
    for (let i = 0; i < arr.length; i++) {
      cloned[i] = cloneRecursive(arr[i], visited);
    }
  } else if ($test.object(obj)) {
    cloned = {};
    visited.set(obj as any, cloned);
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        cloned[key] = cloneRecursive((obj as any)[key], visited);
      }
    }
  } else {
    return obj;
  }
  return cloned as T;
}
/**
 * @description 深度克隆对象
 * @param {T} obj 需要深度克隆的值,可以是任何类型
 * @returns {T} 克隆后的对象或者原值(原始类型)
 *
 * @example
 * const original = { a: 1, b: { c: 2 }, d: [1, 2, 3] };
 * const cloned = deepClone(original);
 * cloned.b.c = 999;
 * console.log(original.b.c); // 2 (原对象未被修改)
 *
 * @example
 * // 处理循环引用
 * const obj: any = { a: 1 };
 * obj.self = obj;
 * const cloned = deepClone(obj); // 不会无限递归
 */
export function deepClone<T>(obj: T): T {
  return cloneRecursive(obj, new WeakMap());
}
/**
 * @description JS对象深度合并
 * @param {Record<string, any>} target 目标对象,默认为空对象
 * @param {Record<string, any>} source 源对象,默认为空对象
 * @returns {T | false} 合并后的对象,如果输入参数无效则返回 false
 *
 * @example
 * const target = { a: 1, b: { c: 2 } };
 * const source = { b: { d: 3 }, e: 4 };
 * const result = deepMerge(target, source);
 * // result: { a: 1, b: { c: 2, d: 3 }, e: 4 }
 *
 * @example
 * // 数组合并
 * const target = { arr: [1, 2] };
 * const source = { arr: [3, 4] };
 * const result = deepMerge(target, source);
 * // result: { arr: [1, 2, 3, 4] }
 */
export function deepMerge<T extends Record<string, any>>(
  target: Record<string, any> = {},
  source: Record<string, any> = {},
): T | false {
  // 检查输入参数的有效性,确保都是普通对象
  if (!$test.object(target) || !$test.object(source)) {
    return false;
  }
  // 深克隆目标对象,避免修改原对象
  const result = deepClone(target);
  // 遍历源对象的所有可枚举属性
  for (const key in source) {
    // 使用安全的属性检查方法
    if (!Object.prototype.hasOwnProperty.call(source, key)) {
      continue;
    }
    const sourceValue = source[key];
    const targetValue = result[key];
    // 如果源值和目标值都是数组,则合并数组
    if ($test.array(sourceValue) && $test.array(targetValue)) {
      result[key] = [...targetValue, ...sourceValue];
    }
    // 如果源值和目标值都是对象,则递归合并
    else if ($test.object(sourceValue) && $test.object(targetValue)) {
      const mergedValue = deepMerge(targetValue, sourceValue);
      // 处理递归调用可能返回 false 的情况
      if (mergedValue === false) {
        result[key] = deepClone(sourceValue);
      } else {
        result[key] = mergedValue;
      }
    }
    // 其他情况直接覆盖,并深克隆以避免引用问题
    else {
      result[key] = deepClone(sourceValue);
    }
  }
  return result as T;
}
uni_modules/oui-utils/libs/style.ts
New file
@@ -0,0 +1,93 @@
import $test from './test';
import { trim } from './object';
/**
 * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
 *
 * @param {string|number} value 需要添加单位的值
 * @param {string} unit 添加的单位名,默认:px
 */
export function addUnit(value: string | number = 'auto', unit: string = 'px'): string {
  const newValue = String(value);
  return $test.number(newValue) ? `${newValue}${unit}` : newValue;
}
/**
 * @description 移除单位,如果有 px | rpx | upx | % | em | rem | vw | vh 等单位
 */
export function removeUnit(value: string | number): string {
  const newValue = String(value);
  return $test.number(newValue) ? newValue : newValue.replace(/(px|rpx|upx|%|em|rem|vw|vh)$/, '');
}
/**
 * @description 样式转换,对象转字符串,或者字符串转对象
 *
 * @param {object | string} customStyle 需要转换的目标
 * @param {String} target 转换的目的,object-转为对象,string-转为字符串
 */
// 函数重载:当 target 为 'object' 时,返回对象类型
export function convertStyle(
  customStyle: Record<string, any> | string,
  target: 'object',
): Record<string, any>;
// 函数重载:当 target 为 'string' 时,返回字符串类型
export function convertStyle(customStyle: Record<string, any> | string, target: 'string'): string;
// 函数重载:当 target 为默认值时,返回对象类型
export function convertStyle(customStyle: Record<string, any> | string): Record<string, any>;
// 实际实现
export function convertStyle(
  customStyle: Record<string, any> | string,
  target: 'object' | 'string' = 'object',
): Record<string, any> | string {
  // 处理空值情况
  if (!customStyle) {
    return target === 'object' ? {} : '';
  }
  // 字符串转字符串,对象转对象情形,直接返回
  if (
    (target === 'object' && typeof customStyle === 'object') ||
    (target === 'string' && typeof customStyle === 'string')
  ) {
    return customStyle;
  }
  // 字符串转对象
  if (target === 'object') {
    const trimmedStyle = trim(customStyle as string);
    if (!trimmedStyle) return {};
    const style: Record<string, string> = {};
    trimmedStyle.split(';').forEach((declaration) => {
      const trimmedDeclaration = trim(declaration);
      if (!trimmedDeclaration) return;
      // 找到第一个冒号的位置,支持属性值中包含冒号的情况
      const colonIndex = trimmedDeclaration.indexOf(':');
      if (colonIndex === -1) return;
      const property = trim(trimmedDeclaration.slice(0, colonIndex));
      const value = trim(trimmedDeclaration.slice(colonIndex + 1));
      if (property && value) {
        style[property] = value;
      }
    });
    return style;
  }
  // 对象转字符串形式
  return Object.entries(customStyle as Record<string, any>)
    .map(([key, value]) => {
      // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
      const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
      return `${cssKey}:${value}`;
    })
    .join(';');
}
uni_modules/oui-utils/libs/test.ts
New file
@@ -0,0 +1,336 @@
/**
 * @description 验证电子邮箱格式
 */
function email(value: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
/**
 * @description 验证手机格式
 */
function mobile(value: string): boolean {
  return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value);
}
/**
 * @description 验证URL格式
 */
function url(value: string): boolean {
  return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/.test(
    value,
  );
}
/**
 * @description 验证日期格式
 */
function date(value: string | number | Date): boolean {
  if (!value) {
    return false;
  }
  // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
  if (number(value as string)) value = +value;
  return !/Invalid|NaN/.test(new Date(value).toString());
}
/**
 * @description 验证ISO类型的日期格式
 */
function dateISO(value: string): boolean {
  return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value);
}
/**
 * @description 验证十进制数字
 */
function number(value: string | number): boolean {
  return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(String(value));
}
/**
 * @description 验证字符串
 */
function string(value: any): boolean {
  return typeof value === 'string';
}
/**
 * @description 验证整数
 */
function digits(value: string | number): boolean {
  return /^\d+$/.test(String(value));
}
/**
 * @description 验证身份证号码
 */
function idCard(value: string): boolean {
  return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value);
}
/**
 * @description 是否车牌号
 */
function carNo(value: string): boolean {
  // 新能源车牌
  const xreg =
    /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
  // 旧车牌
  const creg =
    /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
  if (value.length === 7) {
    return creg.test(value);
  }
  if (value.length === 8) {
    return xreg.test(value);
  }
  return false;
}
/**
 * @description 金额,只允许2位小数
 */
function amount(value: string | number): boolean {
  // 金额,只允许保留两位小数
  return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(String(value));
}
/**
 * @description 中文
 */
function chinese(value: string): boolean {
  const reg = /^[\u4e00-\u9fa5]+$/gi;
  return reg.test(value);
}
/**
 * @description 只能输入字母
 */
function letter(value: string): boolean {
  return /^[a-zA-Z]*$/.test(value);
}
/**
 * @description 只能是字母或者数字
 */
function enOrNum(value: string): boolean {
  // 英文或者数字
  const reg = /^[0-9a-zA-Z]*$/g;
  return reg.test(value);
}
/**
 * @description 验证是否包含某个值
 */
function contains(value: string, param: string): boolean {
  return value.indexOf(param) >= 0;
}
/**
 * @description 验证一个值范围[min, max]
 */
function range(value: number, param: [number, number]): boolean {
  return value >= param[0] && value <= param[1];
}
/**
 * @description 验证一个长度范围[min, max]
 */
function rangeLength(value: string | any[], param: [number, number]): boolean {
  return value.length >= param[0] && value.length <= param[1];
}
/**
 * @description 是否固定电话
 */
function landline(value: string): boolean {
  const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/;
  return reg.test(value);
}
/**
 * @description 是否json字符串
 */
function jsonString(value: string): boolean {
  if (typeof value === 'string') {
    try {
      const obj = JSON.parse(value);
      if (typeof obj === 'object' && obj) {
        return true;
      }
      return false;
    } catch (e) {
      return false;
    }
  }
  return false;
}
/**
 * @description 是否数组
 */
function array(value: any): boolean {
  if (typeof Array.isArray === 'function') {
    return Array.isArray(value);
  }
  return Object.prototype.toString.call(value) === '[object Array]';
}
/**
 * @description 是否对象
 */
function object(value: any): boolean {
  return Object.prototype.toString.call(value) === '[object Object]';
}
/**
 * @description 是否短信验证码
 */
function code(value: string | number, len: number = 6): boolean {
  return new RegExp(`^\\d{${len}}$`).test(String(value));
}
/**
 * @description 是否函数方法
 */
function func(value: any): boolean {
  return typeof value === 'function';
}
/**
 * @description 是否promise对象
 */
function promise(value: any): boolean {
  return Object.prototype.toString.call(value) === '[object Promise]';
}
/**
 * @description 是否图片格式
 */
function image(value: string): boolean {
  const newValue = value.split('?')[0];
  const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i;
  return IMAGE_REGEXP.test(newValue);
}
/**
 * @description 是否视频格式
 */
function video(value: string): boolean {
  const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i;
  return VIDEO_REGEXP.test(value);
}
/**
 * @description 是否为正则对象
 */
function regExp(value: any): boolean {
  return Object.prototype.toString.call(value) === '[object RegExp]';
}
/**
 * @description 是否定义
 */
function def(value: any): boolean {
  return value !== undefined && value !== null;
}
/**
 * @description 是否为null或undefined
 */
function nullish(value: any): boolean {
  return value === null || value === undefined;
}
/**
 * @description 是否为空字符串
 */
function emptyString(value: any): boolean {
  return typeof value === 'string' && value === '';
}
/**
 * @description 是否为只包含空白字符的字符串
 */
function whitespaceString(value: any): boolean {
  return typeof value === 'string' && value.trim() === '' && value !== '';
}
/**
 * @description 是否为空数组
 */
function emptyArray(value: any): boolean {
  return Array.isArray(value) && value.length === 0;
}
/**
 * @description 是否为空对象
 */
function emptyObject(value: any): boolean {
  return (
    typeof value === 'object' &&
    value !== null &&
    value.constructor === Object &&
    Object.keys(value).length === 0
  );
}
/**
 * @description 是否为NaN
 */
function nan(value: any): boolean {
  return typeof value === 'number' && isNaN(value);
}
/**
 * @description 是否为空
 */
function empty(value: any): boolean {
  return (
    nullish(value) ||
    nan(value) ||
    emptyString(value) ||
    whitespaceString(value) ||
    emptyArray(value) ||
    emptyObject(value)
  );
}
export default {
  email,
  mobile,
  url,
  date,
  dateISO,
  number,
  digits,
  idCard,
  carNo,
  amount,
  chinese,
  letter,
  enOrNum,
  contains,
  range,
  rangeLength,
  jsonString,
  landline,
  object,
  array,
  code,
  func,
  promise,
  video,
  image,
  regExp,
  string,
  def,
  empty,
  nullish,
  emptyString,
  whitespaceString,
  emptyArray,
  emptyObject,
  nan,
};
uni_modules/oui-utils/libs/uuid.ts
New file
@@ -0,0 +1,39 @@
/**
 * 生成随机十六进制字符
 * @param length 长度
 * @returns 随机十六进制字符串
 */
function getRandomHex(length: number): string {
  let result = '';
  const chars = '0123456789abcdef';
  for (let i = 0; i < length; i++) {
    result += chars[Math.floor(Math.random() * 16)];
  }
  return result;
}
/**
 * 生成UUID v4(符合RFC 4122标准)
 *
 * UUID v4格式: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
 * 其中 x 是随机十六进制数字,y 是 8、9、a 或 b 中的一个
 *
 * @returns 返回一个标准的UUID v4字符串
 */
export function generateUUID(): string {
  const part1 = getRandomHex(8);
  const part2 = getRandomHex(4);
  const part3 = '4' + getRandomHex(3); // 版本号4
  const part4 = ['8', '9', 'a', 'b'][Math.floor(Math.random() * 4)] + getRandomHex(3);
  const part5 = getRandomHex(12);
  return `${part1}-${part2}-${part3}-${part4}-${part5}`;
}
/**
 * 生成短UUID(去掉连字符)
 * @returns 返回一个不包含连字符的UUID字符串
 */
export function generateShortUUID(): string {
  return generateUUID().replace(/-/g, '');
}
uni_modules/oui-utils/package.json
New file
@@ -0,0 +1,111 @@
{
    "name": "oui-utils",
    "id": "oui-utils",
    "displayName": "oui-utils",
    "version": "1.2.1",
    "description": "一个功能丰富的 TypeScript 工具库",
    "keywords": [
        "工具包",
        "oui",
        "utils"
    ],
    "scripts": {
        "test": "jest",
        "test:watch": "jest --watch",
        "test:coverage": "jest --coverage",
        "test:ci": "jest --ci --coverage --watchAll=false"
    },
    "repository": "",
    "engines": {
        "HBuilderX": "^3.1.0",
        "uni-app": "^4.18",
        "uni-app-x": ""
    },
    "dcloudext": {
        "type": "sdk-js",
        "sale": {
            "regular": {
                "price": "0.00"
            },
            "sourcecode": {
                "price": "0.00"
            }
        },
        "contact": {
            "qq": ""
        },
        "declaration": {
            "ads": "无",
            "data": "无",
            "permissions": "无"
        },
        "npmurl": "",
        "darkmode": "x",
        "i18n": "x",
        "widescreen": "x"
    },
    "uni_modules": {
        "dependencies": [],
        "encrypt": [],
        "platforms": {
            "cloud": {
                "tcb": "√",
                "aliyun": "√",
                "alipay": "√"
            },
            "client": {
                "uni-app": {
                    "vue": {
                        "vue2": "√",
                        "vue3": "√"
                    },
                    "web": {
                        "safari": "√",
                        "chrome": "√"
                    },
                    "app": {
                        "vue": "√",
                        "nvue": "√",
                        "android": "√",
                        "ios": "√",
                        "harmony": "√"
                    },
                    "mp": {
                        "weixin": "√",
                        "alipay": "√",
                        "toutiao": "√",
                        "baidu": "√",
                        "kuaishou": "√",
                        "jd": "√",
                        "harmony": "√",
                        "qq": "√",
                        "lark": "√"
                    },
                    "quickapp": {
                        "huawei": "√",
                        "union": "√"
                    }
                },
                "uni-app-x": {
                    "web": {
                        "safari": "-",
                        "chrome": "-"
                    },
                    "app": {
                        "android": "-",
                        "ios": "-",
                        "harmony": "-"
                    },
                    "mp": {
                        "weixin": "-"
                    }
                }
            }
        }
    },
    "devDependencies": {
        "@types/jest": "^30.0.0",
        "jest": "^30.2.0",
        "ts-jest": "^29.4.5"
    }
}
uni_modules/oui-utils/readme.md
New file
@@ -0,0 +1,241 @@
# oui-utils
OUI 工具包 - 一个功能丰富的 TypeScript 工具库,提供了一些常用的工具函数,如对象操作、样式处理、数据验证等。
## 特性
- **TypeScript 支持** - 完整的类型定义,提供优秀的开发体验
- **轻量级** - 按需导入,不会增加不必要的包体积
- **功能丰富** - 涵盖对象操作、样式处理、数据验证等常用场景
- **uni-app 优化** - 专为 uni-app 项目设计,支持多端运行
- **类型安全** - 使用 TypeScript 类型保护,减少运行时错误
## 兼容性
### 平台支持
- H5
- 小程序(微信/支付宝/百度/字节跳动/QQ/钉钉/快手/飞书/京东)
- App (Vue/nvue/uvue)
- 快应用
### 框架支持
- Vue 2
- Vue 3
## 安装
```bash
# 通过 uni_modules 安装
# 将 oui-utils 文件夹放入项目的 uni_modules 目录中
```
## 快速开始
```typescript
import { trim } from '@/uni_modules/oui-utils';
```
## API 文档
### 对象工具
#### `trim(str, pos?)`
去除字符串空格
**参数:**
- `str: string | number` - 需要去除空格的字符串
- `pos?: 'both' | 'left' | 'right' | 'all'` - 去除位置,默认 'both'
**返回值:** `string`
```typescript
trim('  hello world  '); // 'hello world'
trim('  hello world  ', 'left'); // 'hello world  '
trim('  hello world  ', 'right'); // '  hello world'
trim('  hello world  ', 'all'); // 'helloworld'
```
#### `deepClone<T>(obj)`
深度克隆对象
**参数:**
- `obj: T` - 需要深度克隆的对象
**返回值:** `T`
```typescript
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 3;
console.log(original.b.c); // 2 (原对象未被修改)
```
#### `deepMerge<T>(target?, source?)`
深度合并对象
**参数:**
- `target?: T` - 目标对象,默认 `{}`
- `source?: Partial<T>` - 源对象,默认 `{}`
**返回值:** `T | false`
```typescript
const target = { a: 1, b: { c: 2 } };
const source = { b: { d: 3 }, e: 4 };
const result = deepMerge(target, source);
// result: { a: 1, b: { c: 2, d: 3 }, e: 4 }
```
### 样式工具
#### `addUnit(value?, unit?)`
为数值添加单位
**参数:**
- `value?: string | number` - 需要添加单位的值,默认 'auto'
- `unit?: string` - 单位名称,默认 'px'
**返回值:** `string`
```typescript
addUnit(20); // '20px'
addUnit(20, 'rem'); // '20rem'
addUnit('auto'); // 'auto'
addUnit('20px'); // '20px' (已有单位,直接返回)
```
#### `removeUnit(value)`
移除数值单位
**参数:**
- `value: string | number` - 需要移除单位的值
**返回值:** `string`
```typescript
removeUnit('20px'); // '20'
removeUnit('1.5rem'); // '1.5'
removeUnit(20); // '20'
```
#### `convertStyle(customStyle, target?)`
样式格式转换(对象 ↔ 字符串)
**参数:**
- `customStyle: Record<string, any> | string` - 需要转换的样式
- `target?: 'object' | 'string'` - 转换目标,默认 'object'
**返回值:** `Record<string, any> | string`
```typescript
// 字符串转对象
convertStyle('color: red; font-size: 14px');
// { color: 'red', 'font-size': '14px' }
// 对象转字符串
convertStyle({ color: 'red', fontSize: '14px' }, 'string');
// 'color:red;font-size:14px;'
```
### 验证工具
#### 基础验证
```typescript
test.email('test@example.com'); // true - 验证邮箱格式
test.mobile('13800138000'); // true - 验证手机号格式
test.url('https://example.com'); // true - 验证URL格式
test.number('123.45'); // true - 验证数字格式
test.string('hello'); // true - 验证字符串类型
test.digits('123'); // true - 验证整数
test.empty(''); // true - 空验证:空字符串、空格字符串、空数组、空对象、NaN、null、undefined
```
#### 身份验证
```typescript
test.idCard('110101199001011234'); // true - 验证身份证号
test.carNo('京A12345'); // true - 验证车牌号
test.landline('010-12345678'); // true - 验证固定电话
test.code('123456'); // true - 验证验证码(默认6位)
test.code('1234', 4); // true - 验证4位验证码
```
#### 内容验证
```typescript
test.chinese('中文'); // true - 验证中文字符
test.letter('abc'); // true - 验证字母
test.enOrNum('abc123'); // true - 验证字母或数字
test.amount('123.45'); // true - 验证金额格式
```
#### 日期验证
```typescript
test.date('2023-12-01'); // true - 验证日期格式
test.dateISO('2023/12/01'); // true - 验证ISO日期格式
```
#### 范围验证
```typescript
test.range(5, [1, 10]); // true - 验证数值范围
test.rangeLength('hello', [3, 10]); // true - 验证长度范围
test.contains('hello world', 'world'); // true - 验证包含关系
```
#### 类型验证
```typescript
test.array([1, 2, 3]); // true - 验证数组类型
test.object({ a: 1 }); // true - 验证对象类型
test.func(() => {}); // true - 验证函数类型
test.promise(Promise.resolve()); // true - 验证Promise类型
test.regExp(/test/); // true - 验证正则表达式
test.def(null); // false - 验证非 null、undefined 值
```
#### 文件类型验证
```typescript
test.image('photo.jpg'); // true - 验证图片格式
test.video('movie.mp4'); // true - 验证视频格式
test.jsonString('{"a": 1}'); // true - 验证JSON字符串
```
### UUID 工具
#### `generateUUID()`
生成 UUID
**返回值:** `string`
```typescript
generateUUID(); // '123e4567-e89b-12d3-a456-426614174000'
```
#### `generateShortUUID()`
生成短 UUID
**返回值:** `string`
```typescript
generateShortUUID(); // '123e4567e89b12d3a456426614174000'
```
uni_modules/oui-utils/tests/object.test.ts
New file
@@ -0,0 +1,159 @@
import { trim, deepClone, deepMerge } from '../libs/object';
describe('object utils', () => {
  describe('trim', () => {
    test('should trim both sides by default', () => {
      expect(trim('  hello world  ')).toBe('hello world');
      expect(trim('  test  ')).toBe('test');
      expect(trim('')).toBe('');
    });
    test('should trim left side only', () => {
      expect(trim('  hello world  ', 'left')).toBe('hello world  ');
      expect(trim('   test', 'left')).toBe('test');
    });
    test('should trim right side only', () => {
      expect(trim('  hello world  ', 'right')).toBe('  hello world');
      expect(trim('test   ', 'right')).toBe('test');
    });
    test('should trim all spaces', () => {
      expect(trim('  hello world  ', 'all')).toBe('helloworld');
      expect(trim(' h e l l o ', 'all')).toBe('hello');
    });
    test('should handle numbers', () => {
      expect(trim(123)).toBe('123');
      expect(trim(0)).toBe('0');
    });
    test('should handle edge cases', () => {
      expect(trim('')).toBe('');
      expect(trim('   ')).toBe('');
      expect(trim('no-spaces')).toBe('no-spaces');
    });
  });
  describe('deepClone', () => {
    test('should clone primitive values', () => {
      expect(deepClone(null)).toBe(null);
      expect(deepClone(undefined)).toBe(undefined);
      expect(deepClone(NaN)).toBeNaN();
      expect(deepClone(false)).toBe(false);
      expect(deepClone(123)).toBe(123);
      expect(deepClone('hello')).toBe('hello');
    });
    test('should clone arrays', () => {
      const arr = [1, 2, 3, [4, 5]];
      const cloned = deepClone(arr);
      expect(cloned).toEqual(arr);
      expect(cloned).not.toBe(arr);
      expect(cloned[3]).not.toBe(arr[3]);
    });
    test('should clone objects', () => {
      const obj = {
        a: 1,
        b: 'hello',
        c: {
          d: 2,
          e: [1, 2, 3],
        },
      };
      const cloned = deepClone(obj);
      expect(cloned).toEqual(obj);
      expect(cloned).not.toBe(obj);
      expect(cloned.c).not.toBe(obj.c);
      expect(cloned.c.e).not.toBe(obj.c.e);
    });
    test('should handle complex nested structures', () => {
      const complex = {
        arr: [1, { nested: true }, [2, 3]],
        obj: {
          deep: {
            deeper: {
              value: 'test',
            },
          },
        },
      };
      const cloned = deepClone(complex);
      expect(cloned).toEqual(complex);
      expect(cloned.arr[1]).not.toBe(complex.arr[1]);
      expect(cloned.obj.deep.deeper).not.toBe(complex.obj.deep.deeper);
    });
  });
  describe('deepMerge', () => {
    test('should merge simple objects', () => {
      const target = { a: 1, b: 2 };
      const source = { b: 3, c: 4 };
      const result = deepMerge(target, source);
      expect(result).toEqual({ a: 1, b: 3, c: 4 });
      expect(result).not.toBe(target);
    });
    test('should merge nested objects', () => {
      const target = {
        a: 1,
        nested: {
          x: 1,
          y: 2,
        },
      };
      const source = {
        nested: {
          y: 3,
          z: 4,
        },
        b: 2,
      };
      const result = deepMerge(target, source);
      expect(result).toEqual({
        a: 1,
        b: 2,
        nested: {
          x: 1,
          y: 3,
          z: 4,
        },
      });
    });
    test('should concatenate arrays', () => {
      const target = { arr: [1, 2] };
      const source = { arr: [3, 4] };
      const result = deepMerge(target, source);
      expect(result).toEqual({ arr: [1, 2, 3, 4] });
    });
    test('should handle non-object inputs', () => {
      expect(deepMerge('string' as any, {})).toBe(false);
      expect(deepMerge({}, 'string' as any)).toBe(false);
      expect(deepMerge(null as any, {})).toBe(false);
    });
    test('should handle empty inputs', () => {
      const target = { a: 1 };
      expect(deepMerge(target, {})).toEqual({ a: 1 });
      expect(deepMerge({}, target)).toEqual({ a: 1 });
    });
    test('should override primitive values with objects', () => {
      const target = { a: 'string' };
      const source = { a: { nested: true } };
      const result = deepMerge(target, source);
      expect(result).toEqual({ a: { nested: true } });
    });
  });
});
uni_modules/oui-utils/tests/style.test.ts
New file
@@ -0,0 +1,142 @@
import { addUnit, removeUnit, convertStyle } from '../libs/style';
describe('style utils', () => {
  describe('addUnit', () => {
    test('should add px unit to numbers', () => {
      expect(addUnit(100)).toBe('100px');
      expect(addUnit('100')).toBe('100px');
      expect(addUnit(0)).toBe('0px');
    });
    test('should add custom unit', () => {
      expect(addUnit(100, 'rem')).toBe('100rem');
      expect(addUnit('50', 'vh')).toBe('50vh');
    });
    test('should not add unit to values with existing units', () => {
      expect(addUnit('100px')).toBe('100px');
      expect(addUnit('50rpx')).toBe('50rpx');
      expect(addUnit('100%')).toBe('100%');
      expect(addUnit('auto')).toBe('auto');
    });
    test('should handle edge cases', () => {
      expect(addUnit()).toBe('auto');
      expect(addUnit('')).toBe('');
      expect(addUnit('inherit')).toBe('inherit');
    });
  });
  describe('removeUnit', () => {
    test('should remove common units', () => {
      expect(removeUnit('100px')).toBe('100');
      expect(removeUnit('50rpx')).toBe('50');
      expect(removeUnit('75%')).toBe('75');
      expect(removeUnit('2em')).toBe('2');
      expect(removeUnit('1.5rem')).toBe('1.5');
      expect(removeUnit('100vw')).toBe('100');
      expect(removeUnit('50vh')).toBe('50');
    });
    test('should handle numbers without units', () => {
      expect(removeUnit(100)).toBe('100');
      expect(removeUnit('100')).toBe('100');
    });
    test('should handle values without units', () => {
      expect(removeUnit('auto')).toBe('auto');
      expect(removeUnit('inherit')).toBe('inherit');
    });
  });
  describe('convertStyle', () => {
    describe('string to object conversion', () => {
      test('should convert simple style string to object', () => {
        const styleString = 'color: red; font-size: 14px';
        const result = convertStyle(styleString, 'object');
        expect(result).toEqual({
          color: 'red',
          'font-size': '14px',
        });
      });
      test('should handle style string with extra spaces', () => {
        const styleString = '  color : red ;  font-size : 14px  ; ';
        const result = convertStyle(styleString, 'object');
        expect(result).toEqual({
          color: 'red',
          'font-size': '14px',
        });
      });
      test('should handle style string with colons in values', () => {
        const styleString = 'background-image: url(http://example.com/image.jpg)';
        const result = convertStyle(styleString, 'object');
        expect(result).toEqual({
          'background-image': 'url(http://example.com/image.jpg)',
        });
      });
      test('should handle empty or invalid style strings', () => {
        expect(convertStyle('', 'object')).toEqual({});
        expect(convertStyle('   ', 'object')).toEqual({});
        expect(convertStyle('invalid-style', 'object')).toEqual({});
      });
    });
    describe('object to string conversion', () => {
      test('should convert simple object to style string', () => {
        const styleObj = {
          color: 'red',
          fontSize: '14px',
          marginTop: '10px',
        };
        const result = convertStyle(styleObj, 'string');
        expect(result).toBe('color:red;font-size:14px;margin-top:10px');
      });
      test('should handle camelCase properties', () => {
        const styleObj = {
          backgroundColor: 'blue',
          borderRadius: '5px',
          textAlign: 'center',
        };
        const result = convertStyle(styleObj, 'string');
        expect(result).toBe('background-color:blue;border-radius:5px;text-align:center');
      });
      test('should handle empty object', () => {
        expect(convertStyle({}, 'string')).toBe('');
      });
    });
    describe('default behavior and edge cases', () => {
      test('should default to object conversion', () => {
        const styleString = 'color: red';
        const result = convertStyle(styleString);
        expect(result).toEqual({ color: 'red' });
      });
      test('should return same type when no conversion needed', () => {
        const styleObj = { color: 'red' };
        const styleString = 'color: red';
        expect(convertStyle(styleObj, 'object')).toBe(styleObj);
        expect(convertStyle(styleString, 'string')).toBe(styleString);
      });
      test('should handle null and undefined inputs', () => {
        expect(convertStyle(null as any, 'object')).toEqual({});
        expect(convertStyle(undefined as any, 'object')).toEqual({});
        expect(convertStyle(null as any, 'string')).toBe('');
        expect(convertStyle(undefined as any, 'string')).toBe('');
      });
    });
  });
});
uni_modules/oui-utils/tests/test.test.ts
New file
@@ -0,0 +1,385 @@
import $test from '../libs/test';
describe('test utils', () => {
  describe('email validation', () => {
    test('should validate correct email formats', () => {
      expect($test.email('test@example.com')).toBe(true);
      expect($test.email('user.name@domain.co.uk')).toBe(true);
      expect($test.email('user+tag@example.org')).toBe(true);
      expect($test.email('123@test.com')).toBe(true);
    });
    test('should reject invalid email formats', () => {
      expect($test.email('invalid-email')).toBe(false);
      expect($test.email('@example.com')).toBe(false);
      expect($test.email('test@')).toBe(false);
      expect($test.email('test.example.com')).toBe(false);
      expect($test.email('')).toBe(false);
    });
  });
  describe('mobile validation', () => {
    test('should validate correct mobile numbers', () => {
      expect($test.mobile('13812345678')).toBe(true);
      expect($test.mobile('15987654321')).toBe(true);
      expect($test.mobile('18612345678')).toBe(true);
    });
    test('should reject invalid mobile numbers', () => {
      expect($test.mobile('12812345678')).toBe(false);
      expect($test.mobile('1381234567')).toBe(false);
      expect($test.mobile('138123456789')).toBe(false);
      expect($test.mobile('abcdefghijk')).toBe(false);
      expect($test.mobile('')).toBe(false);
    });
  });
  describe('url validation', () => {
    test('should validate correct URLs', () => {
      expect($test.url('https://www.example.com')).toBe(true);
      expect($test.url('http://example.com')).toBe(true);
      expect($test.url('ftp://files.example.com')).toBe(true);
      expect($test.url('https://sub.domain.com/path?query=1')).toBe(true);
    });
    test('should reject invalid URLs', () => {
      expect($test.url('not-a-url')).toBe(false);
      expect($test.url('www.example.com')).toBe(false);
      expect($test.url('example.com')).toBe(false);
      expect($test.url('')).toBe(false);
    });
  });
  describe('number validation', () => {
    test('should validate numbers', () => {
      expect($test.number('123')).toBe(true);
      expect($test.number('123.45')).toBe(true);
      expect($test.number('-123')).toBe(true);
      expect($test.number('+123.45')).toBe(true);
      expect($test.number('.5')).toBe(true);
      expect($test.number(123)).toBe(true);
    });
    test('should reject non-numbers', () => {
      expect($test.number('abc')).toBe(false);
      expect($test.number('12a')).toBe(false);
      expect($test.number('')).toBe(false);
      expect($test.number('.')).toBe(false);
    });
  });
  describe('string validation', () => {
    test('should validate strings', () => {
      expect($test.string('hello')).toBe(true);
      expect($test.string('')).toBe(true);
      expect($test.string('123')).toBe(true);
    });
    test('should reject non-strings', () => {
      expect($test.string(123)).toBe(false);
      expect($test.string(null)).toBe(false);
      expect($test.string(undefined)).toBe(false);
      expect($test.string({})).toBe(false);
      expect($test.string([])).toBe(false);
    });
  });
  describe('array validation', () => {
    test('should validate arrays', () => {
      expect($test.array([])).toBe(true);
      expect($test.array([1, 2, 3])).toBe(true);
      expect($test.array(['a', 'b'])).toBe(true);
      expect($test.array([{}, []])).toBe(true);
    });
    test('should reject non-arrays', () => {
      expect($test.array({})).toBe(false);
      expect($test.array('string')).toBe(false);
      expect($test.array(123)).toBe(false);
      expect($test.array(null)).toBe(false);
      expect($test.array(undefined)).toBe(false);
    });
  });
  describe('object validation', () => {
    test('should validate objects', () => {
      expect($test.object({})).toBe(true);
      expect($test.object({ a: 1 })).toBe(true);
      expect($test.object({ nested: { obj: true } })).toBe(true);
    });
    test('should reject non-objects', () => {
      expect($test.object([])).toBe(false);
      expect($test.object('string')).toBe(false);
      expect($test.object(123)).toBe(false);
      expect($test.object(null)).toBe(false);
      expect($test.object(undefined)).toBe(false);
    });
  });
  describe('function validation', () => {
    test('should validate functions', () => {
      expect($test.func(() => {})).toBe(true);
      expect($test.func(function () {})).toBe(true);
      expect($test.func(Math.max)).toBe(true);
    });
    test('should reject non-functions', () => {
      expect($test.func({})).toBe(false);
      expect($test.func('string')).toBe(false);
      expect($test.func(123)).toBe(false);
      expect($test.func(null)).toBe(false);
    });
  });
  describe('promise validation', () => {
    test('should validate promises', () => {
      expect($test.promise(Promise.resolve())).toBe(true);
      expect($test.promise((async () => {})())).toBe(true);
    });
    test('should reject non-promises', () => {
      expect($test.promise('string')).toBe(false);
      expect($test.promise({})).toBe(false);
      expect($test.promise({ then: () => {} })).toBe(false);
    });
  });
  describe('chinese validation', () => {
    test('should validate Chinese characters', () => {
      expect($test.chinese('中文')).toBe(true);
      expect($test.chinese('测试')).toBe(true);
      expect($test.chinese('你好世界')).toBe(true);
    });
    test('should reject non-Chinese text', () => {
      expect($test.chinese('hello')).toBe(false);
      expect($test.chinese('中文hello')).toBe(false);
      expect($test.chinese('123')).toBe(false);
      expect($test.chinese('')).toBe(false);
    });
  });
  describe('range validation', () => {
    test('should validate values in range', () => {
      expect($test.range(5, [1, 10])).toBe(true);
      expect($test.range(1, [1, 10])).toBe(true);
      expect($test.range(10, [1, 10])).toBe(true);
    });
    test('should reject values out of range', () => {
      expect($test.range(0, [1, 10])).toBe(false);
      expect($test.range(11, [1, 10])).toBe(false);
      expect($test.range(-5, [1, 10])).toBe(false);
    });
  });
  describe('rangeLength validation', () => {
    test('should validate string length in range', () => {
      expect($test.rangeLength('hello', [3, 10])).toBe(true);
      expect($test.rangeLength('hi', [2, 5])).toBe(true);
      expect($test.rangeLength('test', [4, 4])).toBe(true);
    });
    test('should validate array length in range', () => {
      expect($test.rangeLength([1, 2, 3], [2, 5])).toBe(true);
      expect($test.rangeLength([], [0, 2])).toBe(true);
    });
    test('should reject length out of range', () => {
      expect($test.rangeLength('hi', [3, 10])).toBe(false);
      expect($test.rangeLength('very long string', [1, 5])).toBe(false);
      expect($test.rangeLength([1, 2, 3, 4, 5, 6], [1, 3])).toBe(false);
    });
  });
  describe('jsonString validation', () => {
    test('should validate JSON strings', () => {
      expect($test.jsonString('{"key": "value"}')).toBe(true);
      expect($test.jsonString('[1, 2, 3]')).toBe(true);
      expect($test.jsonString('{"nested": {"obj": true}}')).toBe(true);
    });
    test('should reject invalid JSON strings', () => {
      expect($test.jsonString('not json')).toBe(false);
      expect($test.jsonString('{key: value}')).toBe(false);
      expect($test.jsonString('{"incomplete":')).toBe(false);
      expect($test.jsonString('')).toBe(false);
    });
  });
  describe('nullish validation', () => {
    test('should validate null and undefined', () => {
      expect($test.nullish(null)).toBe(true);
      expect($test.nullish(undefined)).toBe(true);
    });
    test('should reject non-nullish values', () => {
      expect($test.nullish('')).toBe(false);
      expect($test.nullish(0)).toBe(false);
      expect($test.nullish(false)).toBe(false);
      expect($test.nullish([])).toBe(false);
      expect($test.nullish({})).toBe(false);
      expect($test.nullish('null')).toBe(false);
      expect($test.nullish(NaN)).toBe(false);
    });
  });
  describe('emptyString validation', () => {
    test('should validate empty strings', () => {
      expect($test.emptyString('')).toBe(true);
    });
    test('should reject non-empty strings and non-strings', () => {
      expect($test.emptyString(' ')).toBe(false);
      expect($test.emptyString('hello')).toBe(false);
      expect($test.emptyString('0')).toBe(false);
      expect($test.emptyString(null)).toBe(false);
      expect($test.emptyString(undefined)).toBe(false);
      expect($test.emptyString(0)).toBe(false);
      expect($test.emptyString([])).toBe(false);
      expect($test.emptyString({})).toBe(false);
    });
  });
  describe('whitespaceString validation', () => {
    test('should validate whitespace-only strings', () => {
      expect($test.whitespaceString(' ')).toBe(true);
      expect($test.whitespaceString('  ')).toBe(true);
      expect($test.whitespaceString('\t')).toBe(true);
      expect($test.whitespaceString('\n')).toBe(true);
      expect($test.whitespaceString(' \t\n ')).toBe(true);
    });
    test('should reject empty strings, non-whitespace strings and non-strings', () => {
      expect($test.whitespaceString('')).toBe(false);
      expect($test.whitespaceString('hello')).toBe(false);
      expect($test.whitespaceString(' hello ')).toBe(false);
      expect($test.whitespaceString('0')).toBe(false);
      expect($test.whitespaceString(null)).toBe(false);
      expect($test.whitespaceString(undefined)).toBe(false);
      expect($test.whitespaceString(0)).toBe(false);
      expect($test.whitespaceString([])).toBe(false);
      expect($test.whitespaceString({})).toBe(false);
    });
  });
  describe('emptyArray validation', () => {
    test('should validate empty arrays', () => {
      expect($test.emptyArray([])).toBe(true);
    });
    test('should reject non-empty arrays and non-arrays', () => {
      expect($test.emptyArray([1])).toBe(false);
      expect($test.emptyArray([1, 2, 3])).toBe(false);
      expect($test.emptyArray([''])).toBe(false);
      expect($test.emptyArray([null])).toBe(false);
      expect($test.emptyArray([undefined])).toBe(false);
      expect($test.emptyArray({})).toBe(false);
      expect($test.emptyArray('')).toBe(false);
      expect($test.emptyArray(null)).toBe(false);
      expect($test.emptyArray(undefined)).toBe(false);
      expect($test.emptyArray(0)).toBe(false);
    });
  });
  describe('emptyObject validation', () => {
    test('should validate empty plain objects', () => {
      expect($test.emptyObject({})).toBe(true);
    });
    test('should reject non-empty objects and non-plain-objects', () => {
      expect($test.emptyObject({ a: 1 })).toBe(false);
      expect($test.emptyObject({ a: undefined })).toBe(false);
      expect($test.emptyObject([])).toBe(false);
      expect($test.emptyObject(new Date())).toBe(false);
      expect($test.emptyObject(new RegExp(''))).toBe(false);
      expect($test.emptyObject(null)).toBe(false);
      expect($test.emptyObject(undefined)).toBe(false);
      expect($test.emptyObject('')).toBe(false);
      expect($test.emptyObject(0)).toBe(false);
      expect($test.emptyObject(function() {})).toBe(false);
    });
  });
  describe('nan validation', () => {
    test('should validate NaN', () => {
      expect($test.nan(NaN)).toBe(true);
      expect($test.nan(Number.NaN)).toBe(true);
      expect($test.nan(0 / 0)).toBe(true);
      expect($test.nan(parseInt('abc'))).toBe(true);
    });
    test('should reject non-NaN values', () => {
      expect($test.nan(0)).toBe(false);
      expect($test.nan(1)).toBe(false);
      expect($test.nan(-1)).toBe(false);
      expect($test.nan(Infinity)).toBe(false);
      expect($test.nan(-Infinity)).toBe(false);
      expect($test.nan('NaN')).toBe(false);
      expect($test.nan(null)).toBe(false);
      expect($test.nan(undefined)).toBe(false);
      expect($test.nan('')).toBe(false);
      expect($test.nan([])).toBe(false);
      expect($test.nan({})).toBe(false);
    });
  });
  describe('empty validation (comprehensive)', () => {
    test('should validate all empty values', () => {
      // nullish values
      expect($test.empty(null)).toBe(true);
      expect($test.empty(undefined)).toBe(true);
      // NaN
      expect($test.empty(NaN)).toBe(true);
      // empty strings
      expect($test.empty('')).toBe(true);
      // whitespace strings
      expect($test.empty(' ')).toBe(true);
      expect($test.empty('  ')).toBe(true);
      expect($test.empty('\t')).toBe(true);
      expect($test.empty('\n')).toBe(true);
      expect($test.empty(' \t\n ')).toBe(true);
      // empty arrays
      expect($test.empty([])).toBe(true);
      // empty objects
      expect($test.empty({})).toBe(true);
    });
    test('should reject non-empty values', () => {
      // numbers (including 0)
      expect($test.empty(0)).toBe(false);
      expect($test.empty(1)).toBe(false);
      expect($test.empty(-1)).toBe(false);
      expect($test.empty(Infinity)).toBe(false);
      // booleans
      expect($test.empty(false)).toBe(false);
      expect($test.empty(true)).toBe(false);
      // non-empty strings
      expect($test.empty('hello')).toBe(false);
      expect($test.empty('0')).toBe(false);
      expect($test.empty(' hello ')).toBe(false);
      // non-empty arrays
      expect($test.empty([1])).toBe(false);
      expect($test.empty([''])).toBe(false);
      expect($test.empty([null])).toBe(false);
      // non-empty objects
      expect($test.empty({ a: 1 })).toBe(false);
      expect($test.empty({ a: undefined })).toBe(false);
      // special objects
      expect($test.empty(new Date())).toBe(false);
      expect($test.empty(new RegExp(''))).toBe(false);
      expect($test.empty(function() {})).toBe(false);
    });
  });
});
uni_modules/oui-utils/tests/uuid.test.ts
New file
@@ -0,0 +1,91 @@
import { generateUUID, generateShortUUID } from '../libs/uuid';
describe('uuid utils', () => {
  describe('generateUUID', () => {
    test('should generate valid UUID v4 format', () => {
      const uuid = generateUUID();
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
      expect(uuid).toMatch(uuidRegex);
      expect(uuid.length).toBe(36);
    });
    test('should generate unique UUIDs', () => {
      const uuid1 = generateUUID();
      const uuid2 = generateUUID();
      const uuid3 = generateUUID();
      expect(uuid1).not.toBe(uuid2);
      expect(uuid2).not.toBe(uuid3);
      expect(uuid1).not.toBe(uuid3);
    });
    test('should have correct version number (4)', () => {
      const uuid = generateUUID();
      const versionChar = uuid.charAt(14);
      expect(versionChar).toBe('4');
    });
    test('should have correct variant bits', () => {
      const uuid = generateUUID();
      const variantChar = uuid.charAt(19);
      expect(['8', '9', 'a', 'b']).toContain(variantChar.toLowerCase());
    });
    test('should generate multiple valid UUIDs', () => {
      const uuids = Array.from({ length: 100 }, () => generateUUID());
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
      uuids.forEach((uuid) => {
        expect(uuid).toMatch(uuidRegex);
      });
      // Check uniqueness
      const uniqueUuids = new Set(uuids);
      expect(uniqueUuids.size).toBe(100);
    });
  });
  describe('generateShortUUID', () => {
    test('should generate UUID without hyphens', () => {
      const shortUuid = generateShortUUID();
      const shortUuidRegex = /^[0-9a-f]{32}$/i;
      expect(shortUuid).toMatch(shortUuidRegex);
      expect(shortUuid.length).toBe(32);
      expect(shortUuid).not.toContain('-');
    });
    test('should generate unique short UUIDs', () => {
      const shortUuid1 = generateShortUUID();
      const shortUuid2 = generateShortUUID();
      const shortUuid3 = generateShortUUID();
      expect(shortUuid1).not.toBe(shortUuid2);
      expect(shortUuid2).not.toBe(shortUuid3);
      expect(shortUuid1).not.toBe(shortUuid3);
    });
    test('should be equivalent to generateUUID without hyphens', () => {
      const uuid = generateUUID();
      const shortUuid = generateShortUUID();
      const uuidWithoutHyphens = uuid.replace(/-/g, '');
      expect(shortUuid.length).toBe(uuidWithoutHyphens.length);
      expect(shortUuid).toMatch(/^[0-9a-f]{32}$/i);
    });
    test('should maintain version and variant information', () => {
      const shortUuid = generateShortUUID();
      // Version should be 4 (at position 12 in short format)
      expect(shortUuid.charAt(12)).toBe('4');
      // Variant should be 8, 9, a, or b (at position 16 in short format)
      const variantChar = shortUuid.charAt(16);
      expect(['8', '9', 'a', 'b']).toContain(variantChar.toLowerCase());
    });
  });
});
uni_modules/oui-utils/tsconfig.json
New file
@@ -0,0 +1,25 @@
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "types": [
      "jest",
      "@types/jest"
    ]
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "coverage"
  ]
}