如何用一套代码同时实现企业网站和微信小程序?

如何用一套代码同时实现企业网站和微信小程序

核心思路:跨端统一开发框架

通过组件化架构 + 条件编译/平台适配,实现"一次编写,多端运行"。以下是几种主流方案:


一、主流技术方案对比

方案 技术栈 适用场景 学习成本
Uni-app Vue.js 最成熟,生态丰富 ⭐⭐
Taro React/Vue 阿里系,支持多端 ⭐⭐⭐
Remax React 纯原生小程序体验 ⭐⭐⭐
Flutter Web + Mini Program Dart 高性能UI ⭐⭐⭐⭐

推荐 Uni-app(Vue生态,国内最广泛使用)


二、以 Uni-app 为例的完整实现

1. 项目结构

project/
├── pages/
│   ├── index/          # 首页
│   │   ├── index.vue
│   │   └── index.json
│   ├── about/          # 关于我们
│   │   └── about.vue
│   └── contact/        # 联系我们
│       └── contact.vue
├── components/         # 公共组件
│   ├── Header.vue
│   ├── Footer.vue
│   └── ProductCard.vue
├── static/             # 静态资源
├── utils/              # 工具函数
│   ├── api.js          # API封装
│   └── format.js
├── store/              # 状态管理
├── App.vue
├── main.js
├── manifest.json       # 应用配置
├── pages.json          # 页面路由配置
└── package.json

2. 关键配置

pages.json — 统一路由

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "enablePullDownRefresh": true
      }
    },
    {
      "path": "pages/about/about",
      "style": {
        "navigationBarTitleText": "关于我们"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "企业官网",
    "navigationBarBackgroundColor": "#FFFFFF",
    "backgroundColor": "#F8F8F8"
  },
  "condition": {               // 小程序专属启动模式
    "current": 0,
    "list": [
      {
        "name": "首页",
        "path": "pages/index/index",
        "query": ""
      }
    ]
  }
}

manifest.json — 多端差异化配置

{
  "name": "我的企业",
  "appid": "",
  "description": "企业官网+小程序",
  "versionName": "1.0.0",
  "versionCode": "100",

  "h5": {
    "title": "企业官网",
    "router": {
      "mode": "hash",
      "base": "./"
    },
    "devServer": {
      "port": 8080
    },
    "sdkConfigs": {
      "maps": {}
    }
  },

  "mp-weixin": {
    "appid": "wxxxxxxxxxxxxxx",
    "setting": {
      "urlCheck": false
    },
    "usingComponents": true,
    "permission": {
      "scope.userLocation": {
        "desc": "需要获取您的位置信息"
      }
    }
  },

  "mp-alipay": {
    "usingComponents": true
  }
}

3. 核心代码示例

条件编译 — 处理平台差异

<template>
  <view class="container">
    <!-- #ifdef MP-WEIXIN -->
    <button @tap="shareToFriend">分享给好友</button>
    <!-- #endif -->

    <!-- #ifdef H5 -->
    <a href="/about" class="link">了解更多</a>
    <!-- #endif -->

    <!-- 通用部分 -->
    <product-card v-for="item in products" :key="item.id" :data="item" />
  </view>
</template>

<script>
export default {
  data() {
    return {
      products: []
    };
  },
  methods: {
    shareToFriend() {
      // #ifdef MP-WEIXIN
      wx.showShareMenu({ withShareTicket: true });
      // #endif

      // #ifdef H5
      alert('H5端请使用浏览器分享功能');
      // #endif
    }
  }
};
</script>

页面逻辑 — 统一数据流

<!-- pages/index/index.vue -->
<template>
  <view class="page-index">
    <header-component />

    <!-- Banner -->
    <swiper class="banner" :autoplay="true" :interval="3000" circular>
      <swiper-item v-for="(img, idx) in banners" :key="idx">
        <image :src="img" mode="aspectFill" class="banner-img" />
      </swiper-item>
    </swiper>

    <!-- 产品服务 -->
    <section class="services">
      <view class="section-title">我们的服务</view>
      <scroll-view scroll-x class="service-scroll">
        <view class="service-item" v-for="s in services" :key="s.id">
          <image :src="s.icon" class="service-icon" />
          <text>{{ s.name }}</text>
        </view>
      </scroll-view>
    </section>

    <!-- 新闻动态 -->
    <section class="news">
      <view class="section-title">最新动态</view>
      <news-list :list="newsList" />
    </section>

    <footer-component />
  </view>
</template>

<script>
import headerComponent from '@/components/Header.vue';
import footerComponent from '@/components/Footer.vue';
import newsList from '@/components/NewsList.vue';

export default {
  components: {
    headerComponent,
    footerComponent,
    newsList
  },
  data() {
    return {
      banners: [],
      services: [],
      newsList: []
    };
  },
  onLoad() {
    this.loadData();
  },
  onPullDownRefresh() {
    this.loadData().then(() => {
      uni.stopPullDownRefresh();
    });
  },
  methods: {
    async loadData() {
      try {
        const [bannersRes, servicesRes, newsRes] = await Promise.all([
          uni.$http.get('/api/banners'),
          uni.$http.get('/api/services'),
          uni.$http.get('/api/news?limit=5')
        ]);

        this.banners = bannersRes.data;
        this.services = servicesRes.data;
        this.newsList = newsRes.data;
      } catch (e) {
        console.error('加载失败:', e);
      }
    }
  }
};
</script>

<style scoped>
.banner {
  height: 400rpx;
}
.banner-img {
  width: 100%;
  height: 100%;
}
.section-title {
  font-size: 36rpx;
  font-weight: bold;
  margin: 30rpx 0;
}
.service-scroll {
  white-space: nowrap;
}
.service-item {
  display: inline-block;
  text-align: center;
  margin-right: 40rpx;
}
.service-icon {
  width: 100rpx;
  height: 100rpx;
}
</style>

公共组件 — 高度复用

<!-- components/ProductCard.vue -->
<template>
  <view class="product-card" @tap="handleClick">
    <image :src="data.image" mode="aspectFill" class="card-image" />
    <view class="card-content">
      <text class="card-title">{{ data.title }}</text>
      <text class="card-desc">{{ data.description }}</text>
      <view class="card-footer">
        <text class="card-price">¥{{ data.price }}</text>
        <!-- #ifdef MP-WEIXIN -->
        <button size="mini" type="primary" @tap.stop="addToCart">加入购物车</button>
        <!-- #endif -->
        <!-- #ifdef H5 -->
        <button size="mini" type="primary" @tap.stop="addToCart">加入购物车</button>
        <!-- #endif -->
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    data: {
      type: Object,
      required: true
    }
  },
  methods: {
    handleClick() {
      uni.navigateTo({
        url: `/pages/product/detail?id=${this.data.id}`
      });
    },
    addToCart() {
      // 购物车逻辑
      uni.showToast({ title: '已添加', icon: 'success' });
    }
  }
};
</script>

API 统一封装

// utils/api.js
const BASE_URL = process.env.NODE_ENV === 'development' 
  ? 'http://localhost:3000/api' 
  : 'https://api.yourcompany.com';

export function request(url, method = 'GET', data = {}) {
  return new Promise((resolve, reject) => {
    uni.request({
      url: `${BASE_URL}${url}`,
      method,
      data,
      header: {
        'Content-Type': 'application/json',
        // #ifdef MP-WEIXIN
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
        // #endif
        // #ifdef H5
        'Authorization': `Bearer ${localStorage.getItem('token')}`
        // #endif
      },
      success(res) {
        if (res.statusCode === 200) {
          resolve(res.data);
        } else {
          reject(res);
        }
      },
      fail(err) {
        reject(err);
      }
    });
  });
}

export const http = {
  get: (url, data) => request(url, 'GET', data),
  post: (url, data) => request(url, 'POST', data),
  put: (url, data) => request(url, 'PUT', data),
  delete: (url) => request(url, 'DELETE')
};

// 挂载到全局
uni.$http = http;

4. 登录鉴权 — 多端统一

// utils/auth.js
export function getUserInfo() {
  // #ifdef MP-WEIXIN
  return new Promise((resolve, reject) => {
    uni.login({
      provider: 'weixin',
      success: (loginRes) => {
        uni.getUserProfile({
          desc: '用于完善用户资料',
          success: (userRes) => {
            const token = loginRes.code; // 后端换取openId
            uni.setStorageSync('token', token);
            uni.setStorageSync('userInfo', userRes.userInfo);
            resolve(userRes.userInfo);
          },
          fail: reject
        });
      },
      fail: reject
    });
  });
  // #endif

  // #ifdef H5
  return Promise.resolve(localStorage.getItem('userInfo'));
  // #endif
}

5. 构建与发布

# 安装依赖
npm install

# 开发 - 微信开发者工具预览
npm run dev:mp-weixin

# 开发 - H5 网页预览
npm run dev:h5

# 构建 - 微信小程序
npm run build:mp-weixin

# 构建 - H5 网站
npm run build:h5

# 构建 - 支付宝小程序
npm run build:mp-alipay

三、关键差异处理策略

1. 导航差异

// 统一导航方法
export function navigateTo(url) {
  // #ifdef MP-WEIXIN
  uni.navigateTo({ url });
  // #endif

  // #ifdef H5
  window.location.href = url;
  // #endif
}

2. 支付差异

export function pay(orderId) {
  // #ifdef MP-WEIXIN
  uni.requestPayment({
    timeStamp: '',
    nonceStr: '',
    package: '',
    signType: 'MD5',
    paySign: '',
    success() {
      console.log('支付成功');
    },
    fail(err) {
      console.error('支付失败', err);
    }
  });
  // #endif

  // #ifdef H5
  // 跳转至H5支付页面或调用微信支付JSAPI
  window.location.href = `/pay/h5?orderId=${orderId}`;
  // #endif
}

3. 地图定位差异

export function getLocation() {
  // #ifdef MP-WEIXIN
  return new Promise((resolve, reject) => {
    uni.getLocation({
      type: 'gcj02',
      success: resolve,
      fail: reject
    });
  });
  // #endif

  // #ifdef H5
  return new Promise((resolve, reject) => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(resolve, reject);
    } else {
      reject(new Error('浏览器不支持定位'));
    }
  });
  // #endif
}

四、最佳实践总结

┌─────────────────────────────────────────────┐
│              一套代码 · 多端运行               │
├─────────────────────────────────────────────┤
│                                             │
│  📦 共享层                                  │
│  ├─ 业务逻辑 (utils/store)                  │
│  ├─ 公共组件 (components/)                  │
│  └─ API 接口 (api.js)                       │
│                                             │
│  🔀 条件编译                                │
│  ├─ #ifdef MP-WEIXIN / #endif               │
│  ├─ #ifdef H5 / #endif                      │
│  └─ 平台差异化处理                          │
│                                             │
│  🎨 样式适配                                │
│  ├─ rpx 单位 (自动换算)                     │
│  ├─ CSS 变量 (主题切换)                     │
│  └─ 媒体查询 (响应式)                       │
│                                             │
│  🚀 构建输出                                │
│  ├─ dist/mp-weixin  → 微信小程序             │
│  ├─ dist/h5       → H5 网站                 │
│  └─ dist/mp-alipay→ 支付宝小程序             │
│                                             │
└─────────────────────────────────────────────┘

✅ 优势

  • 代码复用率 70%~90%,维护成本大幅降低
  • 统一的技术栈,团队只需掌握一门语言
  • 快速迭代,新功能同时上线所有平台

⚠️ 注意事项

  1. 避免过度条件编译,尽量抽象共性
  2. 性能优化需针对各平台单独调优
  3. 第三方 SDK 可能不兼容所有平台
  4. SEO 仅 H5 有效,小程序需额外做搜索优化

这套方案已在大量企业级项目中验证,是性价比最高的跨端解决方案。

未经允许不得转载:云知识CLOUD » 如何用一套代码同时实现企业网站和微信小程序?