LikeShop 单商户标准版 UniApp 端二开:页面组件复用与自定义主题适配

在电商系统二开项目里,前端 UniApp 端的改造成本往往被低估。

很多团队拿到 LikeShop 单商户标准版源码后,第一反应是直接改页面:改首页样式、改商品卡片、改配色。结果改着改着就发现:同样的商品卡片在首页、分类页、搜索页、活动页各写了一遍,改一个样式要改四五处;主题色散落在几十个页面文件里,客户换个品牌色要全局搜索替换;多端兼容出问题,小程序正常 H5 错乱。

本质原因是没有用好组件化体系和主题变量机制。LikeShop 单商户标准版的 UniApp 端本身已经做了分层的组件设计和 SCSS 变量体系,二开时顺着这套架构扩展,效率会比直接改页面高很多。

这篇文章从目录结构、组件复用体系、主题适配三个层面,讲讲基于这套源码做二开的正确姿势,附核心代码示例。

一、先理清目录结构:不要上来就改页面

二开的第一步不是写代码,是先搞清楚哪些是基础层、哪些是业务层。乱改基础组件,后续升级版本会非常痛苦。

LikeShop 单商户标准版 UniApp 端的典型目录结构大致如下:

复制代码
├── components/          # 公共组件库(二开尽量复用,少改源码)
│   ├── base/           # 基础组件:按钮、卡片、弹窗、空状态
│   ├── business/       # 业务组件:商品卡片、价格组件、规格选择器
│   └── layout/         # 布局组件:导航栏、底部Tab、分割线
├── pages/              # 业务页面(二开主要修改区)
│   ├── index/          # 首页
│   ├── goods/          # 商品列表、详情
│   ├── cart/           # 购物车
│   ├── user/           # 个人中心
│   └── ...
├── static/             # 静态资源
├── style/              # 全局样式与主题变量(主题适配核心)
│   ├── variables.scss  # 主题变量:主色、字号、间距、圆角
│   ├── mixin.scss      # 通用混入:文本省略、flex布局
│   └── common.scss     # 全局公共样式
├── utils/              # 工具函数
├── api/                # 接口请求
└── uni.scss            # 全局入口样式文件

二开原则:

  • style/ 里的变量是主题总开关,改配色优先改变量,不要去页面里硬写颜色;
  • components/business/ 里的业务组件优先复用,新需求尽量扩展组件属性,不要复制一份重写;
  • pages/ 是业务层,可以自由改,但尽量抽组件,不要把大段逻辑堆在页面里。

二、组件复用体系:三层组件化设计

这套源码的组件是分层设计的,越底层越稳定,越上层越贴近业务。二开时在对应层级扩展,不要跨层乱改。

2.1 第一层:基础组件(Base Components)

基础组件是和业务无关的通用 UI 单元,比如按钮、空状态、加载更多、弹窗、分割线。它们不包含任何商品、订单相关的业务逻辑,只负责展示和交互。

二开建议:

  • 不要直接修改原有基础组件的默认样式,容易影响所有引用页面;
  • 需要新的基础组件时,在 components/base/ 下新增,不要堆在页面里;
  • 基础组件尽量用 Props 驱动,不要写死业务数据。

示例:一个通用的空状态组件(简化版)

复制代码
<!-- components/base/empty/empty.vue -->
<template>
  <view class="empty-wrap">
    <image class="empty-icon" :src="icon" mode="aspectFit" />
    <text class="empty-text">{{ text }}</text>
    <slot></slot>
  </view>
</template>

<script>
export default {
  name: 'BaseEmpty',
  props: {
    icon: {
      type: String,
      default: '/static/images/empty.png'
    },
    text: {
      type: String,
      default: '暂无数据'
    }
  }
}
</script>

<style lang="scss" scoped>
.empty-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80rpx 0;
  .empty-icon {
    width: 160rpx;
    height: 160rpx;
    margin-bottom: 20rpx;
  }
  .empty-text {
    font-size: 28rpx;
    color: $text-color-secondary;
  }
}
</style>

2.2 第二层:业务组件(Business Components)

这是二开复用率最高的一层。商品卡片、价格展示、规格选择、订单状态条、优惠券卡片,这些和业务强相关、但多个页面都会用到的单元,都封装成业务组件。

最典型的就是商品卡片:首页瀑布流用、分类页用、搜索结果用、活动页也用。如果每个页面各写一遍,改一次样式要改四五处。

正确做法 :封装一个 goods-card 业务组件,通过 Props 控制展示形态(大图模式/列表模式/瀑布流模式),所有页面复用同一个组件。

示例:商品卡片组件(简化版)

复制代码
<!-- components/business/goods-card/goods-card.vue -->
<template>
  <view class="goods-card" :class="mode" @click="goDetail">
    <image class="goods-img" :src="goods.image" mode="aspectFill" />
    <view class="goods-info">
      <text class="goods-name">{{ goods.name }}</text>
      <view class="goods-price">
        <text class="symbol">¥</text>
        <text class="price">{{ goods.price }}</text>
        <text v-if="goods.originalPrice" class="original-price">
          ¥{{ goods.originalPrice }}
        </text>
      </view>
      <view v-if="showSales" class="goods-sales">
        已售 {{ goods.sales }}
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'GoodsCard',
  props: {
    goods: {
      type: Object,
      required: true
    },
    mode: {
      type: String,
      default: 'grid' // grid 网格 / list 列表
    },
    showSales: {
      type: Boolean,
      default: true
    }
  },
  methods: {
    goDetail() {
      uni.navigateTo({
        url: `/pages/goods/detail?id=${this.goods.id}`
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.goods-card {
  background: #fff;
  border-radius: $border-radius-base;
  overflow: hidden;
  .goods-img {
    width: 100%;
    height: 320rpx;
  }
  .goods-info {
    padding: 16rpx;
    .goods-name {
      font-size: 28rpx;
      color: $text-color-primary;
      line-height: 1.4;
      @include text-ellipsis(2);
    }
    .goods-price {
      margin-top: 10rpx;
      .symbol {
        font-size: 24rpx;
        color: $price-color;
      }
      .price {
        font-size: 32rpx;
        font-weight: bold;
        color: $price-color;
      }
      .original-price {
        margin-left: 10rpx;
        font-size: 24rpx;
        color: $text-color-tertiary;
        text-decoration: line-through;
      }
    }
  }
  &.list {
    display: flex;
    .goods-img {
      width: 200rpx;
      height: 200rpx;
      flex-shrink: 0;
    }
  }
}
</style>

页面里使用就非常简洁:

复制代码
<!-- 首页网格模式 -->
<goods-card 
  v-for="item in goodsList" 
  :key="item.id" 
  :goods="item"
  mode="grid"
/>

<!-- 分类页列表模式 -->
<goods-card 
  v-for="item in goodsList" 
  :key="item.id" 
  :goods="item"
  mode="list"
  :show-sales="false"
/>

二开建议:

  • 新增展示字段优先加 Props,不要直接改组件内部逻辑;
  • 新的业务场景(如拼团商品、秒杀商品),优先扩展现有组件,而不是新建一个几乎一样的;
  • 组件内部的颜色、间距尽量用主题变量,不要硬写死色值。

2.3 第三层:页面级区块(Page Sections)

更复杂的页面模块,比如首页轮播区、秒杀专区、品牌专区,它们只在首页出现,但内部又包含多个子组件。这类可以封装成页面区块组件,放在 components/business/ 或单独的 sections/ 目录。

好处是首页不会堆上千行代码,每个区块独立维护,二开加新板块时直接新增一个区块组件,插入首页即可。

三、自定义主题适配:变量化 + 混入

客户最常见的二开需求就是换主题色、改整体风格。如果颜色散落在每个页面文件里,改起来就是灾难。正确的做法是所有视觉常量都抽到 SCSS 变量里,改主题只改变量文件。

3.1 主题变量文件

style/variables.scss 是主题总开关。LikeShop 标准版里已经预设了一套变量,二开时直接覆盖即可。

复制代码
/* style/variables.scss */

// 主题色
$primary-color: #FF6B35;       // 品牌主色
$primary-color-light: #FFE5D9;  // 主色浅色
$price-color: #E63946;          // 价格色
$success-color: #2A9D8F;
$warning-color: #F4A261;
$danger-color: #E63946;

// 文字色
$text-color-primary: #1A1A1A;
$text-color-secondary: #666666;
$text-color-tertiary: #999999;
$text-color-placeholder: #CCCCCC;

// 背景色
$bg-color-page: #F5F5F5;
$bg-color-card: #FFFFFF;

// 圆角
$border-radius-sm: 8rpx;
$border-radius-base: 12rpx;
$border-radius-lg: 20rpx;

// 间距
$spacing-xs: 10rpx;
$spacing-sm: 20rpx;
$spacing-base: 30rpx;
$spacing-lg: 40rpx;

// 字号
$font-size-xs: 22rpx;
$font-size-sm: 24rpx;
$font-size-base: 28rpx;
$font-size-lg: 32rpx;
$font-size-xl: 36rpx;

客户要换品牌色,只需要改 $primary-color 这一个变量,所有按钮、图标、高亮文字、选中状态都会同步变色,不用全局替换。

3.2 全局混入(Mixin)

重复的样式逻辑不要到处复制,用混入复用。

复制代码
/* style/mixin.scss */

// 文本省略
@mixin text-ellipsis($line: 1) {
  @if $line == 1 {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  } @else {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: $line;
    overflow: hidden;
  }
}

// flex 居中
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

// 1px 边框(解决小程序边框变粗问题)
@mixin border-1px($color: #eee, $position: bottom) {
  position: relative;
  &::after {
    content: '';
    position: absolute;
    #{$position}: 0;
    left: 0;
    width: 100%;
    height: 1px;
    background: $color;
    transform: scaleY(0.5);
  }
}

组件和页面里直接引用:

复制代码
@import '@/style/mixin.scss';

.title {
  font-size: $font-size-lg;
  color: $text-color-primary;
  @include text-ellipsis(2);
}

3.3 全局样式引入

在 uni.scss 中统一引入变量和混入,这样每个页面和组件都能直接使用变量,不用每个文件都 import。

复制代码
/* uni.scss */
@import './style/variables.scss';
@import './style/mixin.scss';
@import './style/common.scss';

注意:UniApp 中 uni.scss 里的变量会自动注入到每个页面和组件,不用重复引入;但 mixin 不会自动注入,使用时仍需在 style 块中 import。

3.4 深色模式/多主题切换(进阶)

如果客户需要支持浅色/深色双主题,或者多套品牌主题切换,可以在变量体系基础上扩展:

  1. 定义两套变量映射;
  2. 通过 page 根节点的 class 切换主题;
  3. 变量用 CSS 变量(CSS Custom Properties)实现运行时切换。

简化示例:

复制代码
/* 浅色主题 */
page.theme-light {
  --primary-color: #FF6B35;
  --bg-page: #F5F5F5;
  --text-primary: #1A1A1A;
}

/* 深色主题 */
page.theme-dark {
  --primary-color: #FF8C5A;
  --bg-page: #1A1A1A;
  --text-primary: #F0F0F0;
}

// 组件内使用
.goods-card {
  background: var(--bg-card);
  color: var(--text-primary);
}

JS 端切换主题时给 page 加对应 class 即可。适合做用户可切换的多主题需求。

四、二开最佳实践

4.1 尽量扩展,不要修改源码

原有组件能满足 80% 需求的,就加 Props 扩展,不要复制一份重写。复制的组件越多,后续维护成本越高,官方版本升级时迁移也越痛苦。

4.2 样式用变量,少硬写

颜色、字号、间距、圆角,凡是视觉常量都尽量用主题变量。这样换主题、做风格统一调整时,只改变量文件一处。

4.3 页面逻辑抽组件

页面文件超过 300 行就要考虑拆组件了。把独立的功能区块拆成子组件,页面只负责数据组装和状态管理,可读性和可维护性会好很多。

4.4 多端兼容注意

  • 小程序不支持部分 CSS 特性(如 % 计算、某些伪类),样式改动后要在微信开发者工具里验证;
  • H5 端要注意浏览器默认样式差异,统一用 reset;
  • APP 端的导航栏、状态栏高度和小程序不同,自定义导航栏时要用 uni.getSystemInfoSync() 动态计算。

4.5 保留升级余地

二开时尽量不要改动 components/base/ 和 style/variables.scss 的原有变量名,可以新增变量和扩展类名。这样后续官方更新版本时,冲突最少,合并成本最低。

五、常见坑

  1. 样式不生效
    坑:改了变量文件,页面颜色没变。
    排查:检查 uni.scss 是否正确引入变量文件;组件内是否硬写了颜色覆盖了变量;scoped 样式下深度选择器是否正确。
  2. 组件重复渲染
    坑:列表里用业务组件,滚动卡顿。
    优化:给组件加 v-memo 或合理使用 computed;图片懒加载;长列表用 uni-list 或虚拟列表。
  3. 多端样式不一致
    坑:H5 正常,小程序错位。
    排查:避免使用小程序不支持的 CSS 属性;rpx 单位在不同端的渲染差异;自定义组件样式隔离导致的继承问题。
  4. 主题色改不全
    坑:改了主色变量,个别按钮、图标还是旧颜色。
    原因:那些地方是硬写的色值,没有用变量。全局搜一下色值,统一替换成变量。

结语

UniApp 端的二开,拼的不是写代码的速度,是架构的利用程度。

LikeShop 单商户标准版已经把基础组件、业务组件、主题变量这几层搭好了,二开时顺着这套体系走:复用组件、扩展属性、改变量、抽区块,效率会比直接在页面上硬改高很多,后续维护和升级也更轻松。

对做二开的团队来说,最重要的不是功能实现得快,是改完之后代码还干净、还能迭代。组件复用和主题变量,就是保证代码干净的两件基础工具。

相关推荐
半生过往7 小时前
uni-app多端项目工作笔记
笔记·uni-app
00后程序员张8 小时前
无需 Mac 的 iOS App 上架工具,开心上架Appuploader 负责哪些环节
android·ios·小程序·https·uni-app·iphone·webview
2501_915909061 天前
Ipa Guard 防护上架,混淆、重签名和回归测试怎么做
android·ios·小程序·https·uni-app·iphone·webview
阿俊-全栈开发1 天前
LikeShop 单商户标准版积分体系:签到、消费返积分与积分兑换逻辑
likeshop·likeshop开源系统·likeshop单商户标准版
济南壹软网络科技有限公司1 天前
盲盒源码系统小程序V6MAX底层架构解析,PHP+Uniapp高性能商用底座发布
小程序·架构·uni-app
2501_915918411 天前
iOS 上架注意事项有哪些?Appuploader 的四个阶段自查清单
android·ios·小程序·https·uni-app·iphone·webview
PedroQue992 天前
uni-app x工程化实战:从页面堆到可维护架构
前端·uni-app
2501_916008892 天前
马甲包过审卡在哪?Ipa Guard 能做的和做不到的
android·小程序·https·uni-app·iphone·webview
2501_915921433 天前
iOS应用耗电测试:使用Energy Log、Keymob和Instruments进行性能监控与能源消耗分析
android·ios·小程序·https·uni-app·iphone·webview