Skip to content

工具应用实战案例

本文将介绍如何使用 uni-app 开发一个功能实用的工具应用,以多功能计算器为例,展示工具类应用的设计思路和实现方法。

1. 工具应用概述

1.1 特点与优势

工具类应用是移动应用中的重要类型,具有以下特点:

  • 功能明确:解决特定问题,目标用户群体明确
  • 界面简洁:注重操作效率,减少不必要的视觉干扰
  • 交互流畅:响应速度快,操作步骤简单
  • 使用频率高:满足用户日常高频需求

常见的工具应用包括:计算器、便签、天气、日历、翻译器、文件管理器等。

1.2 技术架构

前端技术栈

  • uni-app:跨平台开发框架,实现一次开发多端运行
  • Vue.js:响应式数据绑定,提供组件化开发模式
  • Vuex:状态管理,处理复杂组件间通信
  • uni-ui:官方UI组件库,提供统一的界面设计

后端技术栈(按需选择)

  • 云函数:处理业务逻辑,提供无服务器计算能力
  • 云数据库:存储用户数据和应用配置
  • 第三方API:集成专业服务(如天气数据、翻译服务等)

2. 多功能计算器应用

2.1 功能规划

我们将开发一个多功能计算器应用,包含以下核心功能:

  • 标准计算器:支持基本的加减乘除运算
  • 科学计算器:支持三角函数、对数、指数等高级运算
  • 单位换算器:支持长度、面积、体积、重量等单位转换
  • 历史记录:保存计算历史,支持查看和重用

2.2 项目结构

├── components            // 自定义组件
│   ├── calc-keyboard     // 计算器键盘组件
│   ├── calc-display      // 计算器显示组件
│   └── unit-converter    // 单位换算组件
├── pages                 // 页面文件夹
│   ├── index             // 首页(标准计算器)
│   ├── scientific        // 科学计算器
│   ├── unit-converter    // 单位换算
│   └── history           // 计算历史
├── store                 // Vuex 状态管理
│   ├── index.js          // 组装模块并导出
│   ├── calculator.js     // 计算器状态
│   └── history.js        // 历史记录状态
├── utils                 // 工具函数
│   ├── calculator.js     // 计算逻辑
│   └── converter.js      // 换算逻辑
├── static                // 静态资源
├── App.vue               // 应用入口
├── main.js               // 主入口
├── manifest.json         // 配置文件
└── pages.json            // 页面配置

3. 核心功能实现

3.1 状态管理设计

使用 Vuex 管理计算器的状态和历史记录,实现数据的集中管理和组件间通信。

js
// store/index.js - Vuex 入口文件
import Vue from 'vue';
import Vuex from 'vuex';
import calculator from './calculator.js';
import history from './history.js';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    calculator,
    history
  }
});

// store/calculator.js - 计算器状态管理
export default {
  namespaced: true,
  state: {
    expression: '',    // 表达式
    result: '',        // 计算结果
    error: ''          // 错误信息
  },
  mutations: {
    setExpression(state, expression) {
      state.expression = expression;
    },
    setResult(state, result) {
      state.result = result;
    },
    setError(state, error) {
      state.error = error;
    },
    clearAll(state) {
      state.expression = '';
      state.result = '';
      state.error = '';
    }
  },
  actions: {
    // 执行计算
    calculate({ commit, state }) {
      return new Promise((resolve, reject) => {
        try {
          if (!state.expression) {
            return resolve();
          }
          
          // 替换显示符号为实际运算符
          let expr = state.expression
            .replace(/×/g, '*')
            .replace(/÷/g, '/')
            .replace(//g, '-');
          
          // 处理科学计算函数
          expr = expr
            .replace(/sin\(/g, 'Math.sin(')
            .replace(/cos\(/g, 'Math.cos(')
            .replace(/tan\(/g, 'Math.tan(')
            .replace(/log\(/g, 'Math.log10(')
            .replace(/ln\(/g, 'Math.log(')
            .replace(/\(/g, 'Math.sqrt(')
            .replace(/π/g, 'Math.PI')
            .replace(/e/g, 'Math.E');
          
          // 计算结果
          const result = eval(expr);
          
          // 格式化结果,处理小数点后的零
          const formattedResult = parseFloat(result.toFixed(10)).toString();
          
          commit('setResult', formattedResult);
          resolve();
        } catch (error) {
          commit('setError', '计算错误');
          reject(error);
        }
      });
    }
  }
};

// store/history.js - 历史记录状态管理
export default {
  namespaced: true,
  state: {
    historyList: []    // 历史记录列表
  },
  mutations: {
    setHistoryList(state, list) {
      state.historyList = list;
    },
    addHistoryItem(state, item) {
      state.historyList.unshift(item);
    },
    clearHistory(state) {
      state.historyList = [];
    }
  },
  actions: {
    // 初始化历史记录
    initHistory({ commit }) {
      try {
        const history = uni.getStorageSync('calculator_history');
        if (history) {
          commit('setHistoryList', JSON.parse(history));
        }
      } catch (e) {
        console.error('初始化历史记录失败', e);
      }
    },
    
    // 添加历史记录
    addHistory({ commit, state }, item) {
      commit('addHistoryItem', item);
      
      // 保存到本地存储
      try {
        uni.setStorageSync('calculator_history', JSON.stringify(state.historyList.slice(0, 50)));
      } catch (e) {
        console.error('保存历史记录失败', e);
      }
    },
    
    // 清空历史记录
    clearAllHistory({ commit }) {
      commit('clearHistory');
      
      // 清除本地存储
      try {
        uni.removeStorageSync('calculator_history');
      } catch (e) {
        console.error('清除历史记录失败', e);
      }
    }
  }
};

3.2 标准计算器实现

标准计算器是应用的基础功能,提供常规的数学运算能力。

vue
<!-- pages/index/index.vue - 标准计算器页面 -->
<template>
  <view class="calculator">
    <!-- 显示区域 -->
    <calc-display 
      :expression="expression" 
      :result="result"
      :error="error"
    ></calc-display>
    
    <!-- 键盘区域 -->
    <calc-keyboard 
      type="standard" 
      @key-press="handleKeyPress"
    ></calc-keyboard>
    
    <!-- 历史记录按钮 -->
    <view class="history-btn" @tap="goToHistory">
      <text class="iconfont icon-history"></text>
    </view>
  </view>
</template>

<script>
import calcDisplay from '@/components/calc-display/calc-display.vue';
import calcKeyboard from '@/components/calc-keyboard/calc-keyboard.vue';
import { mapState, mapMutations, mapActions } from 'vuex';

export default {
  components: {
    calcDisplay,
    calcKeyboard
  },
  computed: {
    ...mapState('calculator', ['expression', 'result', 'error'])
  },
  onLoad() {
    // 初始化历史记录
    this.initHistory();
  },
  methods: {
    ...mapMutations('calculator', ['setExpression', 'setResult', 'setError', 'clearAll']),
    ...mapActions('calculator', ['calculate']),
    ...mapActions('history', ['addHistory', 'initHistory']),
    
    // 处理按键点击
    handleKeyPress(key) {
      switch (key) {
        case 'C':
          this.clearAll();
          break;
        case '=':
          this.calculate().then(() => {
            if (!this.error && this.expression && this.result) {
              // 添加到历史记录
              this.addHistory({
                expression: this.expression,
                result: this.result,
                timestamp: Date.now()
              });
            }
          });
          break;
        case 'DEL':
          if (this.expression.length > 0) {
            this.setExpression(this.expression.slice(0, -1));
          }
          break;
        default:
          // 如果有错误,先清除错误
          if (this.error) {
            this.setError('');
          }
          
          // 如果已经计算过结果,并且输入的是数字或小数点,则重新开始
          if (this.result && (key >= '0' && key <= '9' || key === '.')) {
            this.clearAll();
            this.setExpression(key);
          } else {
            this.setExpression(this.expression + key);
          }
          break;
      }
    },
    
    // 跳转到历史记录页面
    goToHistory() {
      uni.navigateTo({
        url: '/pages/history/history'
      });
    }
  }
}
</script>

<style lang="scss">
.calculator {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f8f8f8;
  
  .history-btn {
    position: absolute;
    top: 40rpx;
    right: 40rpx;
    width: 80rpx;
    height: 80rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    
    .iconfont {
      font-size: 40rpx;
      color: #333;
    }
  }
}
</style>

3.3 计算器组件开发

将计算器拆分为显示组件和键盘组件,提高代码复用性和可维护性。

vue
<!-- components/calc-display/calc-display.vue - 计算器显示组件 -->
<template>
  <view class="calc-display">
    <view class="expression">{{ expression || '0' }}</view>
    <view class="result" v-if="result">= {{ result }}</view>
    <view class="error" v-if="error">{{ error }}</view>
  </view>
</template>

<script>
export default {
  props: {
    expression: {
      type: String,
      default: ''
    },
    result: {
      type: String,
      default: ''
    },
    error: {
      type: String,
      default: ''
    }
  }
}
</script>

<style lang="scss">
.calc-display {
  padding: 40rpx;
  background-color: #fff;
  border-bottom: 1rpx solid #eee;
  
  .expression {
    font-size: 48rpx;
    color: #333;
    text-align: right;
    min-height: 70rpx;
    word-break: break-all;
  }
  
  .result {
    font-size: 32rpx;
    color: #666;
    text-align: right;
    margin-top: 20rpx;
  }
  
  .error {
    font-size: 28rpx;
    color: #f56c6c;
    text-align: right;
    margin-top: 20rpx;
  }
}
</style>
vue
<!-- components/calc-keyboard/calc-keyboard.vue - 计算器键盘组件 -->
<template>
  <view class="calc-keyboard" :class="type">
    <!-- 标准计算器键盘 -->
    <template v-if="type === 'standard'">
      <view class="keyboard-row">
        <view class="key function" @tap="emitKeyPress('C')">C</view>
        <view class="key function" @tap="emitKeyPress('(')">&#40;</view>
        <view class="key function" @tap="emitKeyPress(')')">&#41;</view>
        <view class="key operator" @tap="emitKeyPress('/')">÷</view>
      </view>
      <view class="keyboard-row">
        <view class="key number" @tap="emitKeyPress('7')">7</view>
        <view class="key number" @tap="emitKeyPress('8')">8</view>
        <view class="key number" @tap="emitKeyPress('9')">9</view>
        <view class="key operator" @tap="emitKeyPress('*')">×</view>
      </view>
      <view class="keyboard-row">
        <view class="key number" @tap="emitKeyPress('4')">4</view>
        <view class="key number" @tap="emitKeyPress('5')">5</view>
        <view class="key number" @tap="emitKeyPress('6')">6</view>
        <view class="key operator" @tap="emitKeyPress('-')">−</view>
      </view>
      <view class="keyboard-row">
        <view class="key number" @tap="emitKeyPress('1')">1</view>
        <view class="key number" @tap="emitKeyPress('2')">2</view>
        <view class="key number" @tap="emitKeyPress('3')">3</view>
        <view class="key operator" @tap="emitKeyPress('+')">+</view>
      </view>
      <view class="keyboard-row">
        <view class="key number" @tap="emitKeyPress('0')">0</view>
        <view class="key number" @tap="emitKeyPress('.')">.</view>
        <view class="key function" @tap="emitKeyPress('DEL')">DEL</view>
        <view class="key equal" @tap="emitKeyPress('=')">=</view>
      </view>
    </template>
    
    <!-- 科学计算器键盘 -->
    <template v-else-if="type === 'scientific'">
      <view class="keyboard-row">
        <view class="key function" @tap="emitKeyPress('C')">C</view>
        <view class="key function" @tap="emitKeyPress('(')">(</view>
        <view class="key function" @tap="emitKeyPress(')')">)</view>
        <view class="key function" @tap="emitKeyPress('DEL')">DEL</view>
        <view class="key operator" @tap="emitKeyPress('/')">÷</view>
      </view>
      <view class="keyboard-row">
        <view class="key function" @tap="emitKeyPress('sin')">sin</view>
        <view class="key number" @tap="emitKeyPress('7')">7</view>
        <view class="key number" @tap="emitKeyPress('8')">8</view>
        <view class="key number" @tap="emitKeyPress('9')">9</view>
        <view class="key operator" @tap="emitKeyPress('*')">×</view>
      </view>
      <view class="keyboard-row">
        <view class="key function" @tap="emitKeyPress('cos')">cos</view>
        <view class="key number" @tap="emitKeyPress('4')">4</view>
        <view class="key number" @tap="emitKeyPress('5')">5</view>
        <view class="key number" @tap="emitKeyPress('6')">6</view>
        <view class="key operator" @tap="emitKeyPress('-')">−</view>
      </view>
      <view class="keyboard-row">
        <view class="key function" @tap="emitKeyPress('tan')">tan</view>
        <view class="key number" @tap="emitKeyPress('1')">1</view>
        <view class="key number" @tap="emitKeyPress('2')">2</view>
        <view class="key number" @tap="emitKeyPress('3')">3</view>
        <view class="key operator" @tap="emitKeyPress('+')">+</view>
      </view>
      <view class="keyboard-row">
        <view class="key function" @tap="emitKeyPress('π')">π</view>
        <view class="key function" @tap="emitKeyPress('e')">e</view>
        <view class="key number" @tap="emitKeyPress('0')">0</view>
        <view class="key number" @tap="emitKeyPress('.')">.</view>
        <view class="key equal" @tap="emitKeyPress('=')">=</view>
      </view>
    </template>
  </view>
</template>

<script>
export default {
  props: {
    type: {
      type: String,
      default: 'standard',
      validator: value => ['standard', 'scientific'].includes(value)
    }
  },
  methods: {
    emitKeyPress(key) {
      this.$emit('key-press', key);
    }
  }
}
</script>

<style lang="scss">
.calc-keyboard {
  flex: 1;
  display: flex;
  flex-direction: column;
  
  .keyboard-row {
    flex: 1;
    display: flex;
    
    .key {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 36rpx;
      border: 1rpx solid #eee;
      background-color: #fff;
      
      &.number {
        background-color: #fff;
      }
      
      &.operator {
        background-color: #f0f0f0;
      }
      
      &.function {
        background-color: #e0e0e0;
      }
      
      &.equal {
        background-color: #1296db;
        color: #fff;
      }
      
      &:active {
        opacity: 0.7;
      }
    }
  }
}
</style>

3.4 单位换算功能

单位换算是工具应用的实用功能,支持多种单位类型的相互转换。

vue
<!-- pages/unit-converter/unit-converter.vue - 单位换算页面 -->
<template>
  <view class="unit-converter">
    <!-- 分类选择 -->
    <view class="converter-header">
      <view class="category-selector">
        <picker 
          @change="onCategoryChange" 
          :value="categoryIndex" 
          :range="categories"
        >
          <view class="picker-value">
            <text>{{categories[categoryIndex]}}</text>
            <text class="iconfont icon-down"></text>
          </view>
        </picker>
      </view>
    </view>
    
    <!-- 换算区域 -->
    <view class="converter-body">
      <!-- 输入单位 -->
      <view class="unit-input-group">
        <input 
          type="digit" 
          v-model="inputValue" 
          class="unit-input"
          @input="convert"
        />
        <picker 
          @change="onFromUnitChange" 
          :value="fromUnitIndex" 
          :range="currentUnits"
        >
          <view class="unit-picker">
            <text>{{currentUnits[fromUnitIndex]}}</text>
            <text class="iconfont icon-down"></text>
          </view>
        </picker>
      </view>
      
      <!-- 转换按钮 -->
      <view class="convert-btn" @tap="swapUnits">
        <text class="iconfont icon-swap"></text>
      </view>
      
      <!-- 输出单位 -->
      <view class="unit-input-group">
        <input 
          type="digit" 
          v-model="outputValue" 
          class="unit-input" 
          disabled
        />
        <picker 
          @change="onToUnitChange" 
          :value="toUnitIndex" 
          :range="currentUnits"
        >
          <view class="unit-picker">
            <text>{{currentUnits[toUnitIndex]}}</text>
            <text class="iconfont icon-down"></text>
          </view>
        </picker>
      </view>
    </view>
    
    <!-- 常用单位快捷选择 -->
    <view class="common-units">
      <view class="section-title">常用单位</view>
      <view class="unit-tags">
        <view 
          class="unit-tag" 
          v-for="(unit, index) in commonUnits" 
          :key="index"
          @tap="selectCommonUnit(unit)"
        >
          {{unit}}
        </view>
      </view>
    </view>
    
    <!-- 换算公式说明 -->
    <view class="formula-info">
      <view class="section-title">换算公式</view>
      <view class="formula-text">
        1 {{currentUnits[fromUnitIndex]}} = {{conversionRate}} {{currentUnits[toUnitIndex]}}
      </view>
    </view>
  </view>
</template>

<script>
// 单位换算数据
const converterData = {
  '长度': {
    units: [
      { name: '米', value: 1 },
      { name: '厘米', value: 0.01 },
      { name: '毫米', value: 0.001 },
      { name: '千米', value: 1000 },
      { name: '英寸', value: 0.0254 },
      { name: '英尺', value: 0.3048 },
      { name: '码', value: 0.9144 },
      { name: '英里', value: 1609.344 }
    ],
    commonUnits: ['厘米', '米', '千米', '英寸']
  },
  '重量': {
    units: [
      { name: '千克', value: 1 },
      { name: '克', value: 0.001 },
      { name: '毫克', value: 0.000001 },
      { name: '吨', value: 1000 },
      { name: '磅', value: 0.45359237 },
      { name: '盎司', value: 0.0283495231 }
    ],
    commonUnits: ['克', '千克', '磅', '吨']
  },
  '面积': {
    units: [
      { name: '平方米', value: 1 },
      { name: '平方厘米', value: 0.0001 },
      { name: '平方千米', value: 1000000 },
      { name: '公顷', value: 10000 },
      { name: '亩', value: 666.6667 },
      { name: '平方英尺', value: 0.09290304 },
      { name: '平方英寸', value: 0.00064516 }
    ],
    commonUnits: ['平方米', '公顷', '亩']
  }
};

export default {
  data() {
    return {
      categories: Object.keys(converterData),
      categoryIndex: 0,
      currentUnits: [],
      fromUnitIndex: 0,
      toUnitIndex: 1,
      inputValue: '1',
      outputValue: '',
      conversionRate: 1
    }
  },
  computed: {
    // 当前分类的常用单位
    commonUnits() {
      const category = this.categories[this.categoryIndex];
      return converterData[category].commonUnits || [];
    }
  },
  created() {
    this.initConverter();
  },
  methods: {
    // 初始化换算器
    initConverter() {
      const category = this.categories[this.categoryIndex];
      this.currentUnits = converterData[category].units.map(unit => unit.name);
      this.convert();
    },
    
    // 分类变更
    onCategoryChange(e) {
      this.categoryIndex = e.detail.value;
      this.fromUnitIndex = 0;
      this.toUnitIndex = 1;
      this.initConverter();
    },
    
    // 源单位变更
    onFromUnitChange(e) {
      this.fromUnitIndex = e.detail.value;
      this.convert();
    },
    
    // 目标单位变更
    onToUnitChange(e) {
      this.toUnitIndex = e.detail.value;
      this.convert();
    },
    
    // 执行单位换算
    convert() {
      if (!this.inputValue) {
        this.outputValue = '';
        this.conversionRate = 0;
        return;
      }
      
      const category = this.categories[this.categoryIndex];
      const fromUnit = converterData[category].units[this.fromUnitIndex];
      const toUnit = converterData[category].units[this.toUnitIndex];
      
      // 计算换算率
      this.conversionRate = toUnit.value / fromUnit.value;
      
      // 计算结果
      const result = parseFloat(this.inputValue) * this.conversionRate;
      
      // 格式化输出,最多显示8位小数
      this.outputValue = result.toFixed(8).replace(/\.?0+$/, '');
    },
    
    // 交换单位
    swapUnits() {
      const temp = this.fromUnitIndex;
      this.fromUnitIndex = this.toUnitIndex;
      this.toUnitIndex = temp;
      this.convert();
    },
    
    // 选择常用单位
    selectCommonUnit(unit) {
      const index = this.currentUnits.findIndex(u => u === unit);
      if (index !== -1) {
        this.toUnitIndex = index;
        this.convert();
      }
    }
  }
}
</script>

<style lang="scss">
.unit-converter {
  padding: 30rpx;
  
  .converter-header {
    margin-bottom: 40rpx;
    
    .category-selector {
      background-color: #f5f5f5;
      padding: 20rpx;
      border-radius: 10rpx;
      
      .picker-value {
        display: flex;
        align-items: center;
        justify-content: space-between;
        font-size: 32rpx;
      }
    }
  }
  
  .converter-body {
    margin-bottom: 40rpx;
    
    .unit-input-group {
      display: flex;
      align-items: center;
      margin-bottom: 30rpx;
      
      .unit-input {
        flex: 1;
        height: 80rpx;
        border: 1rpx solid #ddd;
        border-radius: 10rpx;
        padding: 0 20rpx;
        font-size: 32rpx;
      }
      
      .unit-picker {
        width: 200rpx;
        height: 80rpx;
        border: 1rpx solid #ddd;
        border-radius: 10rpx;
        margin-left: 20rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 20rpx;
        font-size: 28rpx;
      }
    }
    
    .convert-btn {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 80rpx;
      margin-bottom: 30rpx;
    }
  }
  
  .common-units, .formula-info {
    margin-bottom: 40rpx;
    
    .section-title {
      font-size: 28rpx;
      color: #666;
      margin-bottom: 20rpx;
    }
  }
  
  .unit-tags {
    display: flex;
    flex-wrap: wrap;
    
    .unit-tag {
      padding: 10rpx 30rpx;
      background-color: #f5f5f5;
      border-radius: 30rpx;
      font-size: 24rpx;
      margin-right: 20rpx;
      margin-bottom: 20rpx;
    }
  }
  
  .formula-text {
    font-size: 28rpx;
    color: #333;
    padding: 20rpx;
    background-color: #f9f9f9;
    border-radius: 10rpx;
  }
}
</style>

3.5 历史记录功能

历史记录功能可以让用户查看和重用之前的计算结果。

vue
<!-- pages/history/history.vue - 历史记录页面 -->
<template>
  <view class="history-page">
    <view class="header">
      <text class="title">计算历史</text>
      <view class="clear-btn" @tap="clearHistory">清空历史</view>
    </view>
    
    <view class="empty-tip" v-if="historyList.length === 0">
      <text class="iconfont icon-empty"></text>
      <text>暂无计算历史</text>
    </view>
    
    <scroll-view scroll-y class="history-list" v-else>
      <view 
        class="history-item" 
        v-for="(item, index) in historyList" 
        :key="index"
        @tap="useHistoryItem(item)"
      >
        <view class="item-content">
          <text class="expression">{{item.expression}}</text>
          <text class="result">= {{item.result}}</text>
        </view>
        <text class="time">{{formatTime(item.timestamp)}}</text>
      </view>
    </scroll-view>
  </view>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState('history', ['historyList'])
  },
  methods: {
    ...mapActions('history', ['clearAllHistory']),
    
    // 格式化时间
    formatTime(timestamp) {
      const date = new Date(timestamp);
      const now = new Date();
      
      // 今天的记录显示时间
      if (date.toDateString() === now.toDateString()) {
        return this.padZero(date.getHours()) + ':' + this.padZero(date.getMinutes());
      }
      
      // 昨天的记录
      const yesterday = new Date(now);
      yesterday.setDate(now.getDate() - 1);
      if (date.toDateString() === yesterday.toDateString()) {
        return '昨天 ' + this.padZero(date.getHours()) + ':' + this.padZero(date.getMinutes());
      }
      
      // 其他日期显示完整日期
      return (date.getMonth() + 1) + '月' + date.getDate() + '日 ' + 
             this.padZero(date.getHours()) + ':' + this.padZero(date.getMinutes());
    },
    
    // 数字补零
    padZero(num) {
      return num < 10 ? '0' + num : num;
    },
    
    // 使用历史记录项
    useHistoryItem(item) {
      // 将历史记录项的表达式和结果传回计算器页面
      uni.$emit('use_history_item', item);
      uni.navigateBack();
    },
    
    // 清空历史记录
    clearHistory() {
      uni.showModal({
        title: '提示',
        content: '确定要清空所有计算历史吗?',
        success: (res) => {
          if (res.confirm) {
            this.clearAllHistory();
          }
        }
      });
    }
  }
}
</script>

<style lang="scss">
.history-page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f8f8f8;
  
  .header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 30rpx;
    background-color: #fff;
    border-bottom: 1rpx solid #eee;
    
    .title {
      font-size: 36rpx;
      font-weight: bold;
      color: #333;
    }
    
    .clear-btn {
      font-size: 28rpx;
      color: #999;
    }
  }
  
  .empty-tip {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: #999;
    
    .iconfont {
      font-size: 80rpx;
      margin-bottom: 20rpx;
    }
  }
  
  .history-list {
    flex: 1;
    
    .history-item {
      background-color: #fff;
      padding: 30rpx;
      margin-bottom: 20rpx;
      
      .item-content {
        margin-bottom: 15rpx;
        
        .expression {
          font-size: 32rpx;
          color: #333;
          word-break: break-all;
        }
        
        .result {
          font-size: 36rpx;
          color: #1296db;
          font-weight: bold;
          margin-top: 10rpx;
        }
      }
      
      .time {
        font-size: 24rpx;
        color: #999;
      }
    }
  }
}
</style>

4. 应用优化与最佳实践

4.1 性能优化

工具类应用需要快速响应用户操作,以下是一些性能优化措施:

  1. 避免频繁计算:使用防抖和节流技术,避免频繁触发计算操作
  2. 缓存计算结果:对于复杂计算,可以缓存结果避免重复计算
  3. 延迟加载:非核心功能可以采用延迟加载策略
  4. 减少页面重绘:合理使用Vue的计算属性和侦听器,减少不必要的DOM更新
js
// utils/debounce.js - 防抖函数
export function debounce(fn, delay = 300) {
  let timer = null;
  
  return function(...args) {
    if (timer) clearTimeout(timer);
    
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用示例
const debouncedConvert = debounce(function() {
  this.convert();
}, 300);

// 输入值变化时调用
onInput() {
  debouncedConvert.call(this);
}

4.2 跨端适配

uni-app 支持多端开发,但不同平台仍有差异,需要注意以下几点:

  1. 样式适配:使用 rpx 作为尺寸单位,实现不同屏幕大小的自适应
  2. API 差异:使用条件编译处理平台特有的 API 和组件
  3. 交互适配:针对不同平台的交互习惯进行优化
vue
<!-- 条件编译示例 -->
<template>
  <view class="container">
    <!-- #ifdef APP-PLUS -->
    <view class="app-only">仅在 App 环境显示</view>
    <!-- #endif -->
    
    <!-- #ifdef H5 -->
    <view class="h5-only">仅在 H5 环境显示</view>
    <!-- #endif -->
    
    <!-- #ifdef MP-WEIXIN -->
    <view class="mp-only">仅在微信小程序环境显示</view>
    <!-- #endif -->
    
    <view class="common">所有平台通用内容</view>
  </view>
</template>

<script>
export default {
  methods: {
    platformSpecificFunction() {
      // #ifdef APP-PLUS
      console.log('App 环境特有逻辑');
      // #endif
      
      // #ifdef H5
      console.log('H5 环境特有逻辑');
      // #endif
      
      // 通用逻辑
      console.log('所有平台通用逻辑');
    }
  }
}
</script>

4.3 数据持久化

工具应用通常需要保存用户的设置和历史记录,可以使用以下方式实现数据持久化:

  1. 本地存储:使用 uni.setStorageSync/uni.getStorageSync 存储简单数据
  2. 数据库:使用 SQLite 或云数据库存储大量结构化数据
  3. 文件系统:存储大型数据或导出文件
js
// 数据持久化示例
export const storage = {
  // 保存数据
  set(key, data) {
    try {
      uni.setStorageSync(key, JSON.stringify(data));
      return true;
    } catch (e) {
      console.error('保存数据失败', e);
      return false;
    }
  },
  
  // 获取数据
  get(key, defaultValue = null) {
    try {
      const value = uni.getStorageSync(key);
      return value ? JSON.parse(value) : defaultValue;
    } catch (e) {
      console.error('获取数据失败', e);
      return defaultValue;
    }
  },
  
  // 删除数据
  remove(key) {
    try {
      uni.removeStorageSync(key);
      return true;
    } catch (e) {
      console.error('删除数据失败', e);
      return false;
    }
  },
  
  // 清空所有数据
  clear() {
    try {
      uni.clearStorageSync();
      return true;
    } catch (e) {
      console.error('清空数据失败', e);
      return false;
    }
  }
};

5. 总结与拓展

5.1 开发要点总结

  1. 功能聚焦:工具应用应该专注于解决特定问题,避免功能过于复杂
  2. 用户体验:注重操作流畅度和响应速度,减少不必要的等待
  3. 界面设计:简洁明了,突出核心功能,减少视觉干扰
  4. 数据管理:合理使用状态管理和数据持久化,确保数据安全和一致性
  5. 跨端适配:充分利用 uni-app 的跨平台能力,同时注意平台差异

5.2 功能拓展方向

基于多功能计算器应用,可以考虑以下拓展方向:

  1. 更多计算模式:增加金融计算、日期计算、统计计算等专业功能
  2. 公式库:内置常用公式,如物理公式、化学公式、数学公式等
  3. 数据可视化:添加图表功能,将计算结果以图形方式展示
  4. 云同步:支持多设备间的数据同步,包括历史记录和自定义设置
  5. 社区分享:允许用户分享计算结果或自定义公式到社区

5.3 商业化思路

工具类应用的商业化路径通常包括:

  1. 免费基础版 + 高级功能付费:基本计算功能免费,高级功能(如专业计算模式)付费
  2. 广告变现:在不影响用户体验的前提下,适当展示广告
  3. 订阅制:提供月度/年度订阅,解锁全部功能并移除广告
  4. 企业定制:针对特定行业或企业需求,提供定制化解决方案

6. 参考资源

一次开发,多端部署 - 让跨平台开发更简单