Как разработать сложный апплет для одного человека за пять дней (front-end must-see)

Апплет WeChat

При постоянном накоплении потребностей бизнеса небольшие программы стремятся к быстрому результату.

В случае нехватки рабочей силы и коротких циклов разработки нам необходимо найти способ максимизировать эффективность разработки.

А эффективная разработка неотделима от стандартизации, инжиниринга и компонентизации.

С этой целью я напишу резюме и разберу питы и практики в апплете.

Расскажите о наших размышлениях и исследованиях по эффективной разработке небольших программ.

  • План размещения

    • Панель навигации

    • TabBar

    • BasicPage

  • Пользовательская система

    • Схема входа

    • Инициализировать вход

    • Аутентификация

  • Оптимизация и отслеживание ошибок

    • сбор журналов

    • анализ данных

  • Общие схемы оптимизации

    • preLoad

    • Независимая загрузка подпакетов

План размещения

Первым делом думаем, как быстро и качественно восстановить страницу вывода в апплете.

Для этого мы инкапсулируем набор компонентов страницы.

Панель навигации

В настоящее время апплет имеет следующие два типа панелей навигации: обычные и настраиваемые панели навигации.

общепринятый

пользовательская панель навигации

пользовательская панель навигации


При обычном макете верхняя часть панели навигации напрямую использует апплет для предоставления панели навигации.

В пользовательском макете панели навигации мы можем полностью контролировать стиль панели навигации, предоставляя панели навигации больше возможностей для взаимодействия и дизайна пользовательского интерфейса. Как показано на рисунке выше, Readhub добавил кнопку настройки на панель навигации, а HEYTEA добавил эффект затухания заголовка и иммерсивной панели навигации на личной странице.

Вы можете выбрать конкретный план макета в соответствии с вашим конкретным бизнесом.В нашем апплете мы решили использовать все настраиваемые панели навигации и инкапсулировали их.

Решив использовать кастомную схему навбара, мы разобрали навбар

После разборки мы обнаружили, что пользовательскую панель навигации можно разделить на две части: StatusBar и NavigationBar.

Консультируясь с WeChat API, мы передаемwx.getSystemInfoSyncа такжеwx.getMenuButtonBoundingClientRectПолучите информацию о макете StatusBarHeight и MenuButton.

Это видно из схемы разборки

1 NavigationBarPaddingTop = MenuButtonTop - StatusBarHeight
3 NavigationBarPaddingBottom = NavigationBarPaddingTop
5 NavigationBar = StatusBarHeight + NavigationBarPaddingTop + NavigationBarPaddingBottom + MenuButtonHeight

После получения вышеуказанных данных результат просто инкапсулируется, и мы получаем следующую схему

Для раздела StatusBar мы используем PaddingTop для заполнения.

На этой основе некоторые общие компоненты NavigationBar могут быть дополнительно инкапсулированы.

Мы инкапсулируем некоторые общие компоненты NavigationBar следующим образом:

Иммерсивная панель навигации

Пользовательская панель вкладок

На данный момент в апплете TabBar также есть две схемы.

Обычная панель вкладок: WeChat предоставляет решение для изменения значка, текста и их соответствующего выбранного состояния.

Custom TabBar: Базовая библиотека апплетов 2.5.0 поддерживает это. Его можно использовать для создания профилированного TabBar или различных пользовательских стилей.

Обычная панель вкладок

Alien TabBar

Icon TabBar только


В нашем апплете мы решили использовать все настраиваемые панели вкладок для реализации нашего бизнеса.

Так как базовая библиотека апплетов 2.5.0 официально начала поддерживать пользовательские TabBar. Мы не выбираем здесь схему пользовательской панели вкладок напрямую. Выберите сочетание custom-tab-bar , пользовательских компонентов иwx.hideTabBarвыполнение плана.

Конкретное решение состоит в том, чтобы поместить пустой файл пользовательской панели вкладок узла. Добавляйте на страницу пользовательские компоненты TabBar по мере необходимости. Вызывается после завершения инициализации страницыwx.hideTabBarСкрыть оригинальный TabBar .

Преимущество этого в том, что она нормально отображается при использовании базовой библиотеки версии 2.5.0 и выше и совместима с минимальными затратами при использовании более ранней версии.

обычный Alien TabBar


Дно безопасной зоны под схемой совместимая серия iPhone x заключается в следующем

 1@mixin media-style() { 2  .tab { 3    padding-bottom: 84px; 4  } 5} 6// 适配iPhone X系列下巴 7@media screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) { 8    @include media-style(); 9}1011@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:3) {12    @include media-style();13}1415@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:2) {16    @include media-style();17}18// 下面代码只为适配iPhone X在微信调试模拟器中为724px19@media screen and (device-width: 375px) and (device-height: 724px) and (-webkit-device-pixel-ratio: 3) {20    @include media-style();21}

Рекомендуется, если нет особых требований, для предоставления решения рекомендуется напрямую использовать WeChat.В пользовательском решении TabBar, когда телефон Android тянет вниз для обновления, TabBar будет вытащен из видимой области. Необходимо настроить раскрывающийся компонент обновления для решения

Базовая страница интеграции решений

Поработав какое-то время на более стабильной программе после онлайна. Были интегрированы пользовательская навигация и пользовательские программы TabBar. Инкапсулирует сборку BasicPage.

Взяв в качестве примера наши типичные онлайн-страницы, мы можем разделить страницы на две категории.

трехступенчатая структура Нет панели вкладок


Основываясь на приведенном выше анализе комбинированных требований к линии, мы упаковываем этот базовый компонент.

Псевдокод фреймворка Taro, который можно инкапсулировать в соответствии с соответствующими фреймворками, с той же идеей.

 1class BasicPage extends Taro.Component { 2 3  state = { 4    menuButtonHeight: 32, 5    menuButtonTop: 48, 6    statusBarHeight: 44, 7  }; 8 9  componentDidMount() {10        // ...获取并设置 menuButtonHeight 、 menuButtonTop 、 statusBarHeight11  }1213  render() {14    return (15      <View className='basic-page'>16        {17          this.props.header && <View className={`basic-page-header${this.props.fixed ? ' fixed' : ''}`} style={{18            paddingTop: `${this.state.statusBarHeight}px`,19            height: `${(this.state.menuButtonTop - this.state.statusBarHeight) * 2 + this.state.menuButtonHeight}px`,20          }}21          >22            {this.props.renderHeader}23          </View>24        }25        <View className={`basic-page-body${this.props.tab ? ' tab' : ''}`}>26          {this.props.renderBody}27        </View>28        {this.props.tab && <TabBar active={this.props.tabActive} />}29      </View>30    );31  }32}3334BasicPage.defaultProps = {35  fixed: false, // header 是否浮动36  tab: false,37  header: false,38  tabActive: 'template',39};40

Пользовательские данные макета TabBar и пользовательского макета NavigationBar часто используются. Затем инкапсулируйте класс инструмента, чтобы получить его.

 1import Taro from "@tarojs/taro"; 2 3function rpx2px(rpx, windowWidth) { 4  return rpx / 750 * windowWidth; 5} 6 7export default class customConfig { 8 9  static fetchAllConfig() {10    const menuButton = Taro.getMenuButtonBoundingClientRect();11    const systemInfo = Taro.getSystemInfoSync();1213    const statusBarHeight = systemInfo.statusBarHeight;14    const headerHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height;15    const footerHeight = systemInfo.model.indexOf('iPhone X') === -116      ?17      rpx2px(100, systemInfo.windowWidth)18      :19      rpx2px(168, systemInfo.windowWidth);  // 50  8420    const bodyHeight = systemInfo.windowHeight - statusBarHeight - headerHeight - footerHeight;21    const noTabBodyHeight = systemInfo.windowHeight - statusBarHeight - headerHeight;2223    let data = {24      source: {25        menu: menuButton,26        system: systemInfo,27      },28      height: {29        statusBar: statusBarHeight,30        header: headerHeight,31        body: bodyHeight,32        noTabBody: noTabBodyHeight,33        footer: footerHeight,34      },35    };36    Taro.setStorageSync('customConfig', data);37    return data;38  }3940  static get config() {41    let storageInfoSync = Taro.getStorageSync('customConfig');42    if(!storageInfoSync) {43      storageInfoSync = this.fetchAllConfig();44    }45    return storageInfoSync;46  }47}

На данный момент мы завершили инкапсуляцию основных компонентов страницы. В настоящее время все страницы онлайн-апплета для бега разрабатываются на основе этого компонента.

Вам нужно только ссылаться на этот компонент при разработке новой страницы.

1<BasicPage header tab tabActive='index' 2        renderHeader={ 3          <View 4            className='my-index-header' 5          > 6            <Text>Title</Text> 7          </View> 8        } 9        renderBody={10          <View className='my-index-header'>11            Body12          </View>13        }14/>

Пользовательская система

В приложении пользовательская система имеет решающее значение. Разработав несколько небольших программ, мы собрали набор пользовательских системных практик, которые используем в настоящее время.

Авторизация, получение информации о пользователе

Процесс входа Получить информацию о пользователе



Как показано на рисунке выше, мы разделили вход в апплет и получение информации о пользователе на две части.

Основные соображения заключаются в следующем:

  1. Снижая пользовательский порог, пользователи могут сначала испытать некоторые функции. Предлагать авторизовать и улучшать информацию о пользователе во время последующего обмена или взаимодействия

  2. Убедитесь, что состояние входа пользователя всегда сохраняется, что удобно для обработки программы. Если вы объедините логин пользователя и совершенную информацию о пользователе, вы не сможете получить настраиваемый статус входа без авторизации. Суждение становится сложным, и formId не может быть собран заранее

  3. Под той же учетной записью разработчика, совместимость с несколькими апплетами, авторизация пользователя, если программа слишком мала, может возвращать информацию о прямой синхронизации unionid, которая больше не авторизована для улучшения взаимодействия с пользователем.

Точки обработки

При авторизации для получения информации о пользователе, если сервер не записывает пользовательский sessionKey , используйте его в событии обратного вызова Button type = getUserInfowx.loginЕсли метод получит код, это приведет к изменению sessionKey. В результате sessionKey, используемый в getUserInfo, не соответствует новому sessionKey. В результате происходит сбой расшифровки информации о пользователе.

Есть два решения:

  • Тип кнопки = используется в событии обратного вызова getUserInfowx.loginметод, позвоните еще разwx.getUserInfoМетод получения зашифрованной информации о пользователе.

  • Сервер записывает sessionKey , Button type = getUserInfo не нужно вызывать после обратного вызоваwx.login, который напрямую передается на обработку сервером.

Первое решение подходит для простого реновации старых проектов и быстрой разработки, но настоятельно рекомендуется использовать метод обработки на стороне сервера.

При улучшении пользовательской информации обращайтесь к официальному документу по расшифровке пользовательской информации Конкретный процесс здесь не описан https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html

механизм unionid

Кроме того, в процессе входа в систему, когда сервер обменивается sessionKey с WeChat, при соблюдении определенных условий он напрямую возвращает unionid. При наличии нескольких апплетов под одной и той же учетной записью разработчика можно использовать unionid для синхронизации информации о пользователе без авторизации. Улучшить пользовательский опыт.

механизм unionid: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html

Инициализация мини-программы и обработка инициализации страницы

В ежедневной разработке мы обычно размещаем логин и получаем операцию токена при инициализации апплета, которая определяется app.js.onLaunchсередина.而该生命周期与页面初始化生命周期为同步进行。

На данный момент, если вам необходимо нести интерфейс запроса состояния состояния пользователя пользователя, чтобы получить информацию во время инициализации страницы, могут возникнуть следующие ситуации

Потому что инициализация апплета и инициализация страницы выполняются синхронно. Когда страница инициализируется, запрос на вход во время инициализации апплета еще не завершен. В результате маркер или другая аутентификационная информация не передаются, и аутентификация завершается неудачно.

Изначально мы монтируем специальное событие в компонентеcomponentDidInit, и после того, как апплет инициализирует запрос на вход, он получает текущий экземпляр страницы и вызывает его. Но эта схема слишком навязчива для кода, и в конце концов мы решили поддерживать очередь запросов на вход.

Причина использования очереди заключается в том, что часто необходимо сначала перейти на первую страницу, а затем перейти с первой страницы на вторую, чтобы пользователи могли вернуться на домашнюю страницу после того, как вернулись один раз. но приведет к почти одновременным вызовам на разных страницахloginметод.

在第一种方案中,解决该问题需要获得所有页面实例进行调用。而引入队列后只需要轮询消费队列中函数执行即可。上述流程可解决此问题。 Псевдокод выглядит следующим образом:

Код понимает только идеи

 1let loginDoing = false; 2const loginEvent = []; 3 4const userProfile = observable({ 5  user: { 6    avatar: '', 7    isCompleted: false, 8    nickname: '', 9    uid: 0,10    token: '',11  },12  async loginProcess() {13    if(this.user.token) {14      return this.user;15    }16    loginDoing = true;17    let code;18    try {19      const codeResult = await Taro.login();20      if(codeResult.errMsg !== 'login:ok') {21        throw new Error('Taro.login 失败');22      }23      code = codeResult.code;24    } catch (e) {25      loginDoing = false;26      throw e;27    }28    const result = await post(URL().user.login, {29      code,30    });31    let user = {32      ...result.user,33      token: result.token,34    };35    this.user = user;36    loginDoing = false;37    setTimeout(() => {38      let length = loginEvent.length;39      for(let i = 0; i < length; i++) {40        loginEvent.pop()(user);41      }42    });43    return user;44  },45  login() {46    if(loginDoing) {47      return new Promise((resolve) => {48        loginEvent.push(resolve);49      });50    } else {51      return this.loginProcess()52    }53  },54});

Аутентификация

В бизнес-требованиях обычно есть определенные операции, которые требуют [авторизации пользователя для заполнения информации] перед продолжением.В ранних проектах коды аутентификации были написаны на соответствующих страницах. В результате задействовано много повторяющегося кода, что не способствует быстрой разработке. Для этого мы инкапсулируем набор схем аутентификации.

BasePage

Базовый класс BasePage, на котором основаны все страницы. Напишите логику аутентификации в BasePage для реализации. Используйте компонент AuthorizationModal на главной странице для реализации аутентификации.

Код предназначен только для понимания

1 export default class BasePage extends Component { 2 3    state = { 4        // 鉴权相关 5        showAuthorizationModal: false, 6    }; 7 8    /** 9     * 鉴权相关10     */11    // 授权成功事件12    authSuccessEvent() {13    }1415    // 取消授权事件16    authFailEvent() {17    }1819    async checkAuthorization() {20        // 当前是否有已验证21        let globalData = getGlobalData(STORAGE_KEY.VERIFY);22        if(globalData) {23            return {24                isNew: false,25            };26        } else {27            Taro.showLoading({28                title: '检查授权中...',29                mask: true,30                showTicketModal: false,31            });32            // 如果本地不存在时,先请求接口33            // 未登录过,或新机器34            // 请求token及授权状态35            let res;36            try {37                res = await Taro.login();38            } catch() {39                Toast.fail('登录失败~');40                Taro.hideLoading();41                throw new Error('Taro.login 失败');42            }43            // 请求授权接口44            const result = {};45            if(result.errno === 0) {46                resolve({47                    isNew: false,48                });49            } else {50                // 未授权过51                // 弹窗提示授权52                this.setState({53                    showAuthorizationModal: true,54                });55                this.authSuccessEvent = () => {56                    this.setState({57                        showAuthorizationModal: false,58                    });59                    resolve({60                        isNew: true,61                    });62                };63                this.authFailEvent = () => {64                    this.setState({65                        showAuthorizationModal: false,66                    });67                    reject();68                };69            }70        }71    }72}

Страницы наследуют этот базовый класс

1 class LaunchIndex extends BasePage {}

Размещение компонентов на странице

1 {this.state.showAuthorizationModal &&2 <AuthorizationModal onSuccess={this.authSuccessEvent} onFail={this.authFailEvent}/>}3

Компонент AuthorizationModal

Далее нам нужно использовать только следующее в операциях, требующих аутентификации

1this.checkAuthorization()2  .then((res) => {3   // 授权成功逻辑4       console.log('是否新用户', res.isNew);5   })6   .catch(() => {7    // 授权失败逻辑8    })

Преимущество этой схемы в том, что авторизация управляется состоянием, достаточно вызвать в коде метод checkAuthorization.

AuthorizationView

Позже, поскольку первое решение слишком тяжелое, оно более навязчиво для кода страницы. Для этого мы инкапсулируем набор более легких компонентов.

В большей части логики аутентификация требуется только тогда, когда пользователь активно нажимает. Мы инкапсулируем AuthorizationView на основе этой идеи. Предоставьте внешнему миру методы onAgree и onDeny, чтобы реализовать операции проверки подлинности по щелчку в некоторых областях.

Код предназначен только для понимания

1 class AuthorizationView extends Taro.Component { 2 3  state = { 4    showLoginPanel: false, 5  }; 6 7  /** 8   * 登录 9   */10  click() {11    const { userProfile: { user, }, } = this.props;12    if(user.isCompleted) {13      this.props.onAgree(user);14    } else {15      // 显示登录框16      this.setState({17        showLoginPanel: true,18      });19    }20  }2122  /**23   * 授权登录24   * @param e25   */26  async bindGetUserInfo(e) {27    if(e.detail.errMsg === 'getUserInfo:ok') {28      const { userProfile, } = this.props;29      const userResult = await userProfile.login(true);30      this.setState({31        showLoginPanel: false,32      });33      this.props.onAgree(userResult);34    } else {35      this.props.onDeny();36    }37  }3839  cancel() {40    this.setState({41      showLoginPanel: false,42    });43  }4445  render() {46    return (47      <Block>48        <View onClick={this.click}>{this.props.children}</View>49        {50          this.state.showLoginPanel && <View className='login-panel'>51            <View className='login-panel-main'>52              <View className='login-panel-main-title'>您还未登录</View>53              <View className='login-panel-main-subtitle'>请先登录再进行操作</View>54              <Image className='login-panel-main-image' src='https://p0.ssl.qhimg.com/t01a1e495cc2be1e651.png' />55              <View className='login-panel-main-footer'>56                <View className='login-panel-main-footer-button cancel' onClick={this.cancel.bind(this)}>暂不登录</View>57                <Button className='btn-reset' openType='getUserInfo' onGetUserInfo={this.bindGetUserInfo}>58                  <View className='login-panel-main-footer-button confirm'>立即登录</View>59                </Button>60              </View>61            </View>62          </View>63        }64      </Block>65    );66  }67}6869AuthorizationView.defaultProps = {70  onAgree: () => {71  },72  onDeny: () => {73  },74};7576export default AuthorizationView;77

В коде вам нужно использовать только компонент, чтобы обернуть дочерний компонент для использования

1 <AuthorizationView onAgree={this.onAgree.bind(this)} onDeny={this.onDeny.bind(this)}>2  <View>生成海报</View>3</AuthorizationView>4

Эти две программы имеют онлайн-использование, конкретный выбор см. Бизнес-решения

Оптимизация и отслеживание ошибок

На этапе обслуживания мы будем уделять больше внимания воспроизведению сцены и анализу данных, когда пользователи сообщают об ошибках.

сбор журналов

После базовой библиотеки Mini Program версии 2.1.0 WeChat предоставляет набор интерфейсов, связанных с журналами: LogManager.

Когда пользователи оставляют отзывы, журналы, записанные через этот интерфейс, будут синхронно загружаться в фоновый режим WeChat, и их можно загружать для просмотра и отслеживания ошибок.

Мы реализуем механизм сбора журналов, просто инкапсулируя его.

1 const _logger = Taro.getLogManager({ level: 0, }); 2 3const Logger = { 4  debug(...args) { 5    _logger.debug(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} 📝`, ...args); 6    console.debug(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} 📝`, ...args); 7  }, 8  info(...args) { 9    _logger.info(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} 🍺`, ...args);10    console.info(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} 🍺`, ...args);11  },12  warn(...args) {13    _logger.warn(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} ⚠️`, ...args);14    console.warn(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} ⚠️`, ...args);15  },16  error(...args) {17    _logger.warn(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} [ Error ] ❌️`, ...args);18    console.error(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} [ Error ] ❌️`, ...args);19  },20};2122export default Logger;

在使用时,最好按照一定规范进行使用,方便后续查找。 Например

1 Logger.error('[ MyIndex ] 获取用户信息失败', e);2 Logger.debug('[ LaunchIndex ] init response', info);

Анализ журнала в реальном времени: после базовой библиотеки Mini Program 2.7.1 также предоставляется функция анализа журнала в реальном времени. https://developers.weixin.qq.com/miniprogram/dev/api/base/debug/wx.getRealtimeLogManager.html

анализ данных

модель анализа данных

В процессе итерации продукта мы обычно итерируем в соответствии с приведенной выше моделью.

Сбор данных → анализ данных → применение данных → обратная связь по данным

В апплете схема для данных в основном

  • Пользовательский анализ фона апплета

    Платформа данных, предоставляемая самим апплетом.

    Преимущество заключается в том, что вы можете добавить местоположение точки данных без выпуска версии в любое время. Может удовлетворить большинство потребностей.

    Продукты с добавлением фоновых данных в основном полагаются на проекты RBI.

  • сторонняя платформа данных

    Вот пример пользовательского анализа данных Aladdin. Используйте сторонние платформы для предоставления API для управления.

    • Аладдин

    • ...

  • Собственная платформа для анализа данных

    Как правило, крупные фабрики будут иметь свои собственные платформы анализа данных, и вы можете обратиться в группу данных для расширения.

Рекомендуется использовать бэкэндский пользовательский анализ апплета для управления. Управление каждой платформой данных похоже, а возможность добавления управления данными без выпуска версии является большим убийцей.

Пакет управления платформой данных Aladdin, код предназначен только для понимания идей

1import Taro from '@tarojs/taro'; 2 3export default class Monitor { 4  static sendEvent(moduleName, eventName, options) { 5    let aldstat = Taro.getApp().aldstat; 6    if(aldstat) { 7      aldstat.sendEvent(`[ ${moduleName} ] ${eventName}`, options); 8    } 9  }10}1112Monitor.sendEvent('LaunchIndex', '返回', {13  id: this.state.id,14});1516Monitor.sendEvent('LaunchIndex', '点击制作', {17  id: this.state.id,18});

Пользовательский метод API анализа апплета может быть инкапсулирован в соответствии с совком рисования тыквы.

Следует отметить, что инкапсуляция должна быть логичной и регулярной инкапсуляцией, удобной для последующего отсеивания конкретных страниц для конкретных операций.

Общие схемы оптимизации

preLoad

В апплете WeChat при скачке маршрута страницы (например, при вызовеwx.navigateTo,wx.redirectToилиwx.switchTab), к триггеру страницыcomponentWillMountБудет некоторая задержка. Поэтому некоторые сетевые запросы могут быть расширены до запроса непосредственно перед переходом. а затем вызватьcomponentWillMountЗатем получите экземпляр запроса.

В настоящее время каждый фреймворк предоставляет реализацию запроса предварительной загрузки. Нативная разработка может быть расширена сама по себе, и идеи непротиворечивы. Возьмите Таро в качестве примера ниже. Код предназначен только для понимания идей.


1export default class Preload extends BasePage { 2    componentWillMount() { 3        let initData; 4        // 兼容直接进入的场景 5        if(this.$preloadData) { 6            initData = this.$preloadData; 7        } else { 8            initData = request(URL().user.defaultAddress, { 9                token: getGlobalData(STORAGE_KEY.ACCESS_TOKEN),10            });11        }12        initData13            .then((initInfo) => {14            })15            .catch(() => {16            });17    }1819    componentWillPreload (params) {20        return request(URL().user.defaultAddress, {21            token: getGlobalData(STORAGE_KEY.ACCESS_TOKEN),22        });23    }24}

Независимая загрузка подпакетов

https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages.html

В дополнение к попыткам, перечисленным выше. Мы также сделали следующее:

  • Универсальное решение для совместного использования графов

  • Мини-программа Облачное приложение для разработки

  • Настройте раскрывающиеся компоненты обновления RefreshView

  • Protobuf

  • Компонент обрезки изображения

Есть также некоторые ямы, с которыми вы можете не столкнуться

  • Проблемы с использованием нативных компонентов

  • Видео , внутренний аудиоконтекст

Поскольку это не обязательная часть и место ограничено, она не будет здесь указана.

стоимость

Проделав описанную выше практику с апплетом, мы смогли быстро разработать апплет-копию на основе этой практики. Наша недавняя мини-программа [Dudu Card Point Album] была запущена всего через 5 дней разработки.

На бумаге в конце концов я чувствую себя мелким, и я абсолютно точно знаю, что это дело должно быть сделано.

Содержание статьи в основном охватывает моменты, которые могут быть использованы на этапе разработки и обслуживания, и наш ответ на это. для справки.

Эта статья предназначена только для ознакомления. В разработке программного обеспечения нет серебряной пули. Хорошее решение должно быть тесно связано с бизнесом. Обмен приветствуется.

лицом к будущему

Мини-программа CLI строительных лесов