preface

Hello, I’m Xu Zhu. How to quickly learn a new school of martial arts, if I do, where to start? Is there a master to guide, or self-reflection? The world martial arts BUG many, only internal and external training. Learning while doing, hands-on practical projects, fast (do not hesitate to do) and response (find problems in time to eliminate).

Recently, I received a new project to develop android version APP and specify the technology stack UNI-app framework. For the front-end technology tools that have never been contacted or used, and are even unfamiliar, I do not know where to start and what pits there are, and I am afraid and uncertain. Think of the ans programmers have a dozen undead spirit of xiaoqiang, encounter problems and difficulties never retreat boldly to prove. A word will do.

A little more nonsense, today to share with you a not so good technical hydrology. The topic is how to quickly learn uniAPP to develop a multi-terminal application of the actual combat project (environment construction, configuration, development, self-testing, deployment, compilation, packaging, release, etc.), and supplement the development of some easy to encounter small pits.

Introduction to uni – app

Uni-app is an open source framework that uses vue.js to develop all front-end applications. Developers write a set of code that can be published to iOS, Android, Web (responsive), and various small programs (wechat/Alipay/Baidu/Toutiao/Feishu /QQ/ Kuaishou/Dingding/Taobao).

Uni-app uses vUE syntax, applets’ labels and apis. Experience in vUE and wechat applets development, can quickly get used to Uni-app.

advantages

  • Good compatibility

Uni-app’s biggest feature is that one set of code can be used in multiple applications. Developers can generate Android, iOS, H5, Baidu mini programs at the same time without having to develop a separate set of code for each platform. A lot of cost savings.

  • Low learning cost

Uni-app is developed based on vue.js, so it is friendly to front-end developers and the barrier to learning uni-App is lowered accordingly. In particular, the packaged plug-in is the same as the components of the small program on the wechat side.

  • Fast development speed

Uni-app is developed using HBuilderX, so it supports vUE syntax. HBuilderX is also fast to develop and compile, which is one of the reasons many people choose UNI-app.

  • Strong ability to expand

Uni-app supports NVUE and encapsulates H5+. It also supports native iOS and Android development. Therefore, it is very convenient to transfer the original H5 and mobile apps to uni-App.

disadvantages

  • The document is not very friendly, it will be a little difficult to learn and use it (but the bosses have been quietly improving and optimizing)

  • Tablet support is not very good, the dominant is wechat small program, APP mobile terminal

The development of specification

In order to achieve multi-terminal compatibility, uni-App has agreed on the following development specifications in consideration of compilation speed and runtime performance:

  • Page file followingVue Single file Component (SFC) specification[1]
  • Component labels are close to the applet specification, as detailedUni-app component specification[2]
  • Interface capability (JS API) is close to wechat applets specification, but the prefix wx should be replaced with UNI, see detailsUni-app interface specification[3]
  • Data binding and event handling are the same as the vue.js specification and complement the App and page lifecycle
  • It is recommended that you develop with a Flex layout for multiterminal compatibility

Environment set up

Install editor HBuilderX 3.2.12: Official IDE download address [4]

HBuilderX is a generic front-end development tool with special enhancements for UNI-App. Download the App development version out of the box.

Install wechat developer tools (installed and ignored) : official download address [5]

Initialize the project

To initialize the project, use the HBuilderX tool. This is documented, but it is important to repeat the following:

Click on the menu bar File -> Create -> Project, as shown below:

Select UNI-app, fill in the project name, select template, and click Create to create it successfully, as shown below:

Run the project

  • Browser run

  • Run on the real phone (mobile phone and tablet will operate in the same way after debugging mode is enabled)

The mobile desktop will automatically install the HBuilderX tool and launch the interface, as shown below:

  • Wechat applets run

Note: If this is the first time to use, you need to configure the related path of the small program IDE to run successfully. As shown below, you need to fill in the installation path of wechat developer tools in the input box. If HBuilderX cannot start wechat developer tool normally, developers need to manually start it, then copy the path of uni-app generating small program project to wechat developer tool, develop in HBuilderX, and you can see the real-time effect in wechat developer tool.

Publish the project

Pack native Apps

Package select issue -> native APP- cloud package -> check Android APK package, select its own certificate (enter the certificate key), check the official package -> click the package button.

Perform the following operations to generate a signature certificate using its own Android certificate:

Install the JAVA environment (JRE8 is recommended)

JAVA JRE8[6]

Download to local, double-click the installation, the default installation directory is “C:\Program Files\Java\jre1.8.0_202\bin”, as shown below:

Generating a Signing Certificate

The command is as follows:

// Go to the default JRE installation directory
cd C:\Program Files\Java\jre18.. 0 _202\bin

// Run keytool -genkey to generate a certificate
.\keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore D:\test.keystore
Copy the code
  • The testAlias is the certificate alias. You can set the testAlias to the character you want. You are advised to use letters and digits
  • Test. keystore is the name of the certificate file. You can change it to the desired file name or specify the full file path
  • 36500 is the validity period of the certificate, which is 100 years, expressed in days. It is recommended to set the validity period to a longer period to avoid certificate expiration

Press Enter and the following message will be displayed:

After the preceding command is executed, the certificate is generated in D:\test.keystore.

Viewing Certificate Information

.\keytool -list -v -keystore D:\test.keystore 
Copy the code

Release the H5

  1. On the manifest.json visual interface, perform the following configuration (the basic application path is not required for publishing in the root directory of the website). In this case, the publishing website path is www.baidu.com/h5

  1. In the HBuilderX toolbar, click Publish, select website -PC or H5 mobile version, as shown below, click to generate H5 related resource files, save in unpackage directory.

Release wechat mini program

  1. To apply for wechat AppID, please refer to:Official wechat tutorial[7].

  1. Click “release” => “small program – wechat” in the top menu of HBuilderX, input the name and AppId of the small program and click Release to generate the wechat small program project code in unpackage/dist/build/ mp-Weixin.

  1. In the micro channel small program developer tool, import the generated micro channel small program project, test the normal operation of the project code, click the “upload” button, and then follow the “Submit for review” => “publish” small program standard process, step by step to view in detailOfficial wechat tutorial[8].

The directory structure

Chryrane - components in accordance with the vue component standard uni-APP directory │ ├─ Hello-update │ │ ├─ hello-update.vue │ ├─ hello-modal recycled Hello-modal modal box │ └ ─ hello - modal. Vue ├ ─ API method unified interface encapsulation API calls ├ ─ pages business page file storage directory │ ├ ─ index │ │ └ ─ index. The vue homepage │ ├ ─ login │ │ └ ─ the login. The login │ vue ├─ my │ ├─staticStatic resources can only be stored in this ├─uni_modules for [uni_module](/uni_modules) standard plug-in. ├─ ├─ ├─ class.exe, ├─ class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe, Class.exe ├─manifest.json ├─ pages. Json └─ manifest.json ├─ pages, navigation bar, TAB, etcCopy the code

Function module

  • Configuration tabbar
  • Custom navigation bar
  • Encapsulated component library
  • System version detection, download, progress, update
  • Upload pictures and videos
  • Scan code (QR code, bar code)
  • Flat panel landscape lock
  • Global wrapper interceptor method
  • Password to Base64 encryption (view the tread pit record)

Code implementation

Configuration tabbar

The tabBar configuration is provided in pages. Json not only to facilitate rapid development of navigation, but also to improve performance on the App and applet side. On these two platforms, the underlying native engine can directly read tabBar information configured in pages. Json and render the native TAB without waiting for js engine initialization.

Tabbars can be customized, and because native tabbars are relatively fixed configurations, they may not be suitable for all scenarios.

However, note that with the exception of the H5 side, the performance experience of a custom tabBar is lower than that of a native tabBar. App and applet side do not customize unless necessary.

Add the following code to pages. Json:

/ / detailed configuration properties that look here: https://uniapp.dcloud.io/collocation/pages? id=tabbar"tabBar": {
    "color": "# 333333"."selectedColor": "#596CEA"."borderStyle": "black"."backgroundColor": "#ffffff"."fontSize": "14px"."iconWidth": "26px"."list": [{
        "pagePath": "pages/index/index"."iconPath": "static/imgs/home.png"."selectedIconPath": "static/imgs/home-active.png"."text": "Home page"
    }, {
        "pagePath": "pages/my/My"."iconPath": "static/imgs/my.png"."selectedIconPath": "static/imgs/my-active.png"."text": "I"}}]Copy the code

Custom navigation bar

/ / detailed configuration properties that look here: https://uniapp.dcloud.io/collocation/pages? Id =app-plus //"globalStyle": {
    "app-plus": {
        "titleNView": false// Disable native navigation},} // Method two"globalStyle": {
    "navigationStyle": "custom"
}
Copy the code

Native navigation bar renderings:

Disable native navigation:

Note the following points for customizing the navigation bar:

  • For non-H5 terminals, the status bar area at the top of the phone is covered by the page content. This is because the form is immersive, i.e. full screen writable content. Uni-app provides CSS variables for status bar height--status-bar-heightIf you need to remove the position of the status bar from the foreground, write a placeholder DIV with the height set to a CSS variable.
/ / details introduces the built-in CSS variables: https://uniapp.dcloud.io/frame? Id = CSS variables
<template>
    <view class="nav">
        <view class="status-bar">
            <! -- Here is the status bar -->
        </view>
        <uni-nav-bar left-icon="back" title="List of Goods" @clickLeft="goBack" :fixed="true">
            <view slot="left">return</view>
        </uni-nav-bar>
    </view>
</template>
<style>
.status-bar {
    height: var(--status-bar-height);
    width: 100%;
    background-color: #FFF;
}
</style>
Copy the code
  • Uni UI is recommended if native navigation is not sufficientCustom navigation bar NavBar. The front navigation bar automatically handles the status bar height placeholder.
/ / details to introduce custom navigation module: https://ext.dcloud.net.cn/plugin?id=52
<template>
    <view class="nav">
        <uni-nav-bar left-icon="back" title="List of Goods" @clickLeft="goBack" :fixed="true" :statusBar="true">
            <view slot="left">return</view>
            <view slot="right">
                <view class="nav-right">
                    <view class="btn qrcode" @click="onScan">scan</view>
                </view>
            </view>
        </uni-nav-bar>
    </view>
</template>
Copy the code
  • If you want to change the foreground font style of the status bar, you can still set the navigationBarTextStyle property of the page (black or White only).

Encapsulated component library

Uni-popup is a component of pop-up layer, which pops up a message prompt window and prompt box in the application. It is common in business scenarios and will be used in almost every interface. Therefore, it is considered to package this component, customize the easyCOM configuration, and use it directly in the page without reference or registration.

First, in the components directory of the project, and conform to the components/ component name/component name.vue directory structure. The code of the pop-up component is as follows:

// hello-modal.vue
<template>
    <view class="modal">
        <uni-popup ref="popup" :mask-click="false">
            <uni-popup-dialog type="info" :title="title" :duration="2000" :before-close="true"
                    @close="close" @confirm="confirm">
                <view default>
                    <view class="modal-content">
                        <slot></slot>
                    </view>
                </view>
            </uni-popup-dialog>
        </uni-popup>
    </view>
</template>

<script>
    export default {
        props: {
            title: {
                type: String.defalut: 'tip'}},data() {
            return{}},methods: {
            openModal() {
                this.$refs.popup.open();
            },
            close() {
                this.$refs.popup.close();
            },
            confirm() {
                this.$emit('confirmFn'); }}}</script>

<style lang="scss" scoped>
    .modal {
        /deep/ .uni-popup-dialog {
            width: 520rpx;
            border-radius: 12rpx;
            transform: scale(1.5);
        }

        /deep/ .uni-dialog-content {
            padding: 20rpx 30rpx 30rpx;
            justify-content: flex-start; }}</style>
Copy the code

Configure easycom in pages. Json, as shown below:

Now you can have fun. No matter how many components are installed in the Components directory, easyCom packs and automatically removes unused components, making the component library especially user-friendly.

The following is an example of a template command:

<template>
    <view>
        <! -- Start mode box -->
            <hello-modal ref="onChild" :title="modalTitle" @confirmFn="confirmFn">
                <view>{{ tips }}</view>
            </hello-modal>
        <! -- End modal box -->
    </view>
</template>
<script>
    export default {
        data() {
            return {
                modalTitle: 'remind'.// Pop the title
                tips: 'Are you sure you don't want to be my friend on wechat? '.// Pop-up content}},mounted() {
            this.$refs.onChild.openModal(); // Open the pop-up box
        },
        methods: {
            confirmFn() {
                console.log('Click ok');
                this.$refs.onChild.close(); // Close the dialog box}}}</script>
Copy the code

System version detection, download, update

Configure the version number and version name in manifest.json, as shown in the following figure:

It is valid for the app side to detect the system version. Obtain the system version globally in app.vue and use conditional compilation for platform differentiation. The code is as follows:

<script>
    export default {
        globalData: {
            version: '1.0.0'
        },
        onLaunch() {
            console.log('App Launch');
            // #ifdef APP-PLUS
                let that = this;
                plus.runtime.getProperty(plus.runtime.appid, function(getInfo) {
                    that.globalData.version = getInfo.version;
                    console.log('getInfo===', getInfo);
                });
            // #endif
        }
</script>
Copy the code

Create a custom pop-up component (version information) with the following code:

// hello-update.vue
<template>
    <view class="container">
        <uni-popup ref="popup" :mask-click="false">
            <view class="popup-content">
                <view class="section">
                    <view class="popup-header">
                        <text class="title">Discover a new version</text>
                        <text class="close-btn" @click="close" v-if="type === 1 && ! isLoading">Shut down</text>
                    </view>
                    <view class="popup-main">
                        <uni-forms label-align="right">
                            <uni-forms-item label="Latest version:" name="version">
                                <text class="form-value">{{ childType.version }}</text>
                            </uni-forms-item>
                            <uni-forms-item label="New size:" name="size">
                                <text class="form-value">10.6 MB</text>
                            </uni-forms-item>
                        </uni-forms>
                    </view>
                    <view class="popup-footer">
                        <view class="btn-box">
                            <button @click="close" size="mini" v-if="type === 1 && ! isLoading">Temporarily not update</button>
                            <button @click="quit" size="mini" v-if="type === 2">exit</button>
                            <button type="primary" @click="confirm" size="mini" v-if=! "" isLoading">Update now</button>
                            <button class="download" type="primary" size="mini" :loading="isLoading" v-if="isLoading">In the download</button>
                            <progress class="progress" :percent="progressVal" stroke-width="5" v-if="isLoading" />
                        </view>
                        <view class="popup-tips" v-if="type === 2">
                            <text>This upgrade involves important content and needs to be updated before use</text>
                        </view>
                    </view>
                </view>
            </view>
        </uni-popup>
    </view>
</template>
<script>
    export default {
        onLoad() {},
        onReady() {},
        props: {
            childType: Object
        },
        data() {
            return {
                type: 1.// 1: mandatory update 2: mandatory update
                isLoading: false.progressVal: 0Download the initial progress bar value}},methods: {
            // Exit the Android app
            quit() {
                // #ifdef APP-PLUS
                    if (plus.os.name.toLowerCase() === 'android') {
                        plus.runtime.quit();
                    }
                // #endif
            },
            // Check the version update popbox
            open(type) {
                this.type = type;
                this.$refs.popup.open();
            },
            // Close the dialog box
            close() {
                this.$refs.popup.close();
            },
            // 
            async confirm() {
                // #ifdef APP-PLUS
                // Request version information interface
                if (this.childType.version > getApp().globalData.version) {
                        // console.log(this.childType.url)
                        this.isLoading = true;
                        // Create a download task
                        let dtask = plus.downloader.createDownload(this.childType.url, {
                                force: true
                        }, (d, status) = > {
                            // Download complete
                            if (status === 200) {
                                this.isLoading = false;
                                uni.showModal({
                                    title: 'Download complete, about to install'.showCancel: false.success: () = > {
                                        D. filename is the relative path of the file stored locally. Use the following API to convert it to the platform absolute path
                                        // let fileSaveUrl = plus.io.convertLocalFileSystemURL(d.filename);
                                        // plus.runtime.openFile(d.filename);
                                        // Install can only install packages that have been downloaded locally, so find the downloaded address locally and then call isntall
                                        plus.runtime.install(d.filename, {}, () = > {
                                            console.log('Setup successful');
                                            // plus.runtime.restart(); // Restart after the installation is successful
                                    }, (error) = > {
                                            console.log('error===', error.message);
                                            uni.showToast({
                                                icon: 'error'.title: 'Installation failed'})})}})}else {
                                this.isLoading = false;
                                // Download failed
                                plus.downloader.clear(); // Clear the download task
                                uni.showToast({
                                    icon: 'error'.title: 'Download failed'
                                })
                            }
                        })

                        dtask.addEventListener('statechanged'.(task) = > {
                            if(! dtask) {return;
                            }

                            switch (task.state) {
                                case 1:
                                    console.log('Start downloading');
                                    break;
                                case 2:
                                    console.log('Link to server... ');
                                    break;
                                case 3:
                                    this.progressVal = parseInt(parseFloat(task.downloadedSize) / parseFloat(task.totalSize) * 100);
                                    console.log('progressVal==='.this.progressVal);
                                    break;
                                case 4:
                                    console.log('Listening download completed');
                                    break; }});// Start downloading
                        dtask.start();
                } else {
                    uni.showModal({
                        title: 'Current is the latest version'.showCancel: false})}// #endif}}}</script>
<style lang="scss" scoped>
    .popup-content {
        width: 520rpx;
        height: 500rpx;
        background-color: #fff;
        border-radius: 14rpx;
        transform: scale(1.5);
        .section {
            width: 100%;
            height: 100%;
            overflow: hidden;
            .popup-header {
                position: relative;
                height: 160rpx;
                background: url(. /.. /static/imgs/modal-bg.png) no-repeat center;
                background-size: cover;
                text-align: center;
                .close-btn {
                    position: absolute;
                    top: 20rpx;
                    right: 20rpx;
                    z-index: 999;
                    width: 32rpx;
                    height: 32rpx;
                    background: url(. /.. /static/imgs/close.png) no-repeat center;
                    background-size: cover;
                    display: block;
                    font-size: 0;
                }
                .title {
                    font-size: 36rpx;
                    color: #FFFFFF;
                    display: block;
                    padding: 62rpx 0; }}.popup-main {
                margin: 30rpx auto;
                text-align: center;
                display: flex;
                justify-content: center;

                .form-value {
                        line-height: 36rpx; }}.popup-footer {
                .btn-box {
                    text-align: center;
                    .progress {
                        margin: 30rpx 20rpx 0;
                    }
                    /deep/ uni-button{&:first-child {
                            margin-right: 40rpx;
                        }
                        width: 180rpx;
                        &.download{&:first-child {
                                margin-right: 0;
                            }
                            width: auto; }}}.popup-tips {
                    font-size: 20rpx;
                    color: #E22014;
                    padding-top: 20rpx;
                    text-align: center;
                }
            }
        }

        /deep/ .uni-forms-item__content {
            min-height: 48rpx;
        }
        /deep/ .uni-forms-item__inner {
            padding-bottom: 10rpx;
        }
        /deep/ .uni-forms-item__label {
            height: 48rpx;
        }
        .uni-forms-item,
        /deep/ .uni-forms-item__label .label-text {
            font-size: 24rpx;
            color: #1F2625; }}</style>
Copy the code

Example code for use is as follows:

<template>
    <view>
        <! Start dialog box -->
        <hello-update ref="onChildUpdate" :childType="childType"></hello-update>
        <! -- end dialog -->
    </view>
</template>
<script>
    export default {
        data() {
            return {
                childType: {}}}.mounted() {
            this.getVersion();
        },
        methods: {
            // Get the version number
            getVersion() {
                // #ifdef APP-PLUS
                this.$http({
                    url: `${checkStatus}? code=qms_appVersion`,
                }).then(res= > {
                    console.log('Get version information ===', res, getApp().globalData.version);
                    if (res.code === 0) {
                        this.childType = {};
                        if (res.data.version > getApp().globalData.version) {
                            this.childType = {
                                version: res.data.version,
                                url: res.data.downloadApk
                            }
                            this.$refs.onChildUpdate.open(res.data.forceUpdate);
                        } else {
                            uni.switchTab({
                                url: '/pages/index/index'}}}})// #endif}}}</script>
Copy the code

The effect picture is as follows:

Upload pictures and videos

In addition to upload function, also includes preview, delete operation, directly intercept this function code contains comments as follows:

<template>
    <view class="upload-files">
        <view class="upload-item" v-for="(v, i) in item.fileList" :key="i">
            <view v-if="i < 3">
                <video v-if="v.type === 'video'" :id="'myVideo' + item.clientId + '' + i" :src="v.url" :show-center-play-btn="false" :controls="isShowControls" :poster="v.url" @fullscreenchange="onFullScreenChange" class="active-video">
                    <cover-image v-if="isEditPage" class="controls-close img" @click="deleteItem(item.fileList, i)" src="/static/imgs/clean.png">
                    </cover-image>
                    <cover-image class="controls-play img" @click="playVedio(item.clientId, i)" src="/static/imgs/player.png"></cover-image>
                </video>
                <view class="img-box">
                    <view v-if="isEditPage" class="close" @click="deleteItem(item.fileList, i)">Shut down</view>
                    <image :src="v.url" @click="previewImg(v, i)" class="active-img"></image>
                </view>
            </view>
        </view>
        <view class="upload-item" v-if="item.fileList && item.fileList.length < 3 && isEditPage">
            <view @click="chooseImages(item)" class="upload-bg"></view>
        </view>
    </view>
</template>
<script>
    export default {
        onLoad(option) {
            console.log('Get URL parameter ===', option);
            this.isEditPage = option.status === '3'; // true: edit false: view
        },
        data() {
            return {
                isEditPage: false.// Whether to edit the page
                imgIndex: -1.// Image index
                imageArr: [].// Array of images
                isShowControls: false.// Whether to display the video controller
                videoContext: {}, // Video context object
                operation: -1.0: delete operation 1: submit operation
                imageValue: [].imageStyles: {
                  border: false}}},methods: {
            // Play the video in full screen
            playVedio(id, index) {
                let that = this;
                // Get the videoContext object
                console.log(`myVideo${id}${index}`)
                that.videoContext = uni.createVideoContext(`myVideo${id}${index}`);
                // Enter the full screen state
                that.videoContext.requestFullScreen();
                that.videoContext.play();
                that.isShowControls = true;
            },
            // Triggered when the video enters and exits full screen
            onFullScreenChange(e) {
                console.log(e);
                let that = this;
                if(! e.detail.fullScreen) {console.log('Exit full screen');
                    that.videoContext.pause();
                    that.isShowControls = false; that.videoContext.exitFullScreen(); }},// Image/video preview
            previewImg(row, index) {
                console.log('preview');
                uni.previewImage({
                    urls: [row.url], // A list of HTTP links to images that need to be previewed
                    current: ' '.// The current HTTP link to display the image, the default is the first
                    success: (res) = > {
                        console.log('Preview successful ===', res); }})},// Delete files
            deleteItem(row, index) {
                console.log('delete = = =', row);
                this.tips = 'Delete this picture? ';
                this.imgIndex = index;
                this.imageArr = row;
                this.operation = 0;
                this.$refs.onChild.openModal();
            },
            // Select an upload type
            chooseVideoImage(row) {
                uni.showActionSheet({ // Pop up the action menu from the bottom up
                    title: 'Select upload Type'.itemList: ['images'.'video'].success: (res) = > {
                        console.log(res)
                        if (res.tapIndex === 0) {
                            // Upload the image
                            this.chooseImages(row);
                        } else {
                            // Upload the video
                            this.chooseVideo(row); }}})},// Upload the image
            chooseImages(row) {
                let count = parseInt(3 - row.fileList.length);
                console.log('count===', count);
                uni.chooseImage({ // Select images from your local album or take photos with your camera
                    count: count, // Limit the selection to three images
                    sizeType: ['original'.'compressed'].sourceType: ['album'.'camera'].success: (res) = > {
                        // List of local temporary file paths
                        console.log('Upload picture ===', res, row);
                        let tempFilePaths = res.tempFiles;
                        tempFilePaths.forEach((file, index) = > {
                            console.log('file===', file);
                            const isSize = file.size / (1024 * 1024) < 200
                            if(! isSize) { uni.showToast({title: 'Image size cannot exceed 200M'.icon: 'none',})return false
                            }

                            // #ifndef APP-PLUS
                            const isType = file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/gif' || file.type === 'image/webp';
                            // #endif

                            // #ifdef APP-PLUS
                            const isType = file.path.indexOf('.png')! = = -1 || file.path.indexOf('.jpg')! = = -1 || file.path.indexOf('.jpeg')! = = -1 || file.path
                                .indexOf('.gif')! = = -1 || file.path.indexOf('.webp')! = = -1;
                            // #endif

                            if(! isType) { uni.showToast({title: 'Image format only supports PNG/JPG/GIF /webp'.icon: 'none',})return false
                            }

                            let form = {}

                            this.$upload({
                                url: uploadFile,
                                filePath: res.tempFilePaths[index],
                                name: 'file'.formData: form
                            }).then(res= > {
                                let result = JSON.parse(res);
                                console.log('upload = = =', result);
                                if (result.code === 0) {
                                    result.data.files.map(v= > {
                                        row.fileList.push({
                                            clientId: result.data.clientId,
                                            uploadId: v.uploadId,
                                            url: `${serviceUrl}/file/pms/${v.path}`.type: v.type,
                                            suffix: v.suffix }); })}})})}})})},// Upload the video
            chooseVideo(row) {
                uni.chooseVideo({ // Take a video or select a video from your phone's album to return to the temporary file path of the video
                    maxDuration: 10.count: parseInt(3 - row.fileList.length), // Limit the selection of a maximum of three videos
                    compressed: true.sourceType: ['album'.'camera'].success: (res) = > {
                        // List of local temporary file paths
                        console.log('res===', res);
                        // #ifndef APP-PLUS
                        let file = res.tempFile;
                        const isType = file.type === 'video/mp4' || file.type === 'video/3gp' || file.type === 'video/mov';
                        // #endif

                        //#ifdef APP-PLUS
                        let file = res;
                        const isType = file.tempFilePath.indexOf('.mp4')! = = -1 || file.tempFilePath.indexOf('.3gp')! = = -1 || file.tempFilePath.indexOf('.mov')! = = -1;
                        // #endif

                        const isSize = file.size / (1024 * 1024) < 200;
                        if(! isSize) { uni.showToast({title: 'Video size cannot exceed 200M'.icon: 'none',})return false
                        }
                        if(! isType) { uni.showToast({title: 'Video format mp4/3GP/MOV only'.icon: 'none',})return false
                        }
                        console.log('file===', file)
                        row.fileList.push({
                            url: res.tempFilePath,
                            status: 1
                        });
                        console.log('video = = =', row.fileList); }})},}}</script>
Copy the code

The effect picture is as follows:

Scan code (QR code, bar code)

Uniapp provides an API interface to invoke the client scanning interface Uni. scanCode. Example code is as follows:

/ / details here: https://uniapp.dcloud.io/api/system/barcode? id=scancode
// Allows scanning from camera and album
uni.scanCode({
    success: (res) = > {
        console.log('Barcode Type:' + res.scanType);
        console.log(Bar Code Content:+ res.result); }});// Only scanning through the camera is allowed
uni.scanCode({
    onlyFromCamera: true.success: (res) = > {
        console.log('Barcode Type:' + res.scanType);
        console.log(Bar Code Content:+ res.result); }});// Enable bar code scanning
uni.scanCode({
    scanType: ['barCode'].success: (res) = > {
        console.log('Barcode Type:' + res.scanType);
        console.log(Bar Code Content:+ res.result); }});Copy the code

Bar code Generator recommends a direct download from the plug-in market to use, as shown below:

/ / details here: https://ext.dcloud.net.cn/plugin?id=406
<template>
    <view>
        <tki-barcode
        ref="barcode"
        :show="show"
        :format="format"
        :cid="cid"
        :val="val"
        :unit="unit"
        :opations="opations"
        :onval="onval"
        :loadMake="loadMake"
        @result="barresult" />
    </view>
</template>
<script>
import tkiBarcode from "@/components/tki-barcode/tki-barcode.vue"
export default {
    components: { tkiBarcode }
}
</script>
Copy the code

The effect picture is as follows:

Flat panel landscape lock

Add the following code to pages. Json:

/ / detailed configuration properties that look here: https://uniapp.dcloud.io/collocation/pages? id=globalstyle"globalStyle": {
    "pageOrientation": "landscape"// Landscape configuration, screen rotation Settings, auto/portrait/landscape}Copy the code

Global wrapper interceptor method

// request.js
/ * * *@Des Request interface encapsulation *@Author Jack Chen @lazy coder *@Date The 2021-11-22 09:18:51 *@LastEditors Jack Chen
 * @LastEditTime The 2021-11-22 09:45:31 * /
 
import { getToken, setToken, removeToken } from './validate.js';
// #ifdef H5
let baseUrl = '/api'
// #endif

// #ifdef APP-PLUS
let baseUrl = 'http://192.168.11.59:8000'; // Development server
// #endif

export const myRequest = (options) = > { // Expose a function: myRequest, using options to receive arguments from the page
    return new Promise((resolve, reject) = > { // Encapsulate the interface asynchronously, using Promise to handle asynchronous requests
        let header = options.header || {};
        if (getToken()) {
            if (options.url.indexOf('/logout') = = = -1) {
                header['Authorization'] = 'Bearer ' + getToken();
            }
        }

        uni.request({ // Send the request
            url: baseUrl + options.url, // The API for receiving requests
            method: options.method || 'GET'.// How to receive requests. If no request is sent, the default is GET
            header: header || {},
            data: options.data || {}, // The data received by the request is null by default
            sslVerify: false.success: (res) = > { // Data is successfully obtained
                // console.log('success===', res.statusCode);
                if (res.statusCode === 500) {
                        uni.showToast({
                                title: 'Network exception please hold on'.icon: 'error'
                        })
                        return;
                }
                if(res.data.code ! = =0) { // Because 0 is the status code of return success, if not equal to 0, it indicates failure to obtain
                        if (res.data.code === 5000) {
                                uni.showToast({
                                    title: 'Network exception please hold on'.icon: 'error'})}else if (res.data.code === 2000) {
                                uni.showToast({
                                    title: res.data.message,
                                    icon: 'error'
                                })
                                removeToken();
                                setTimeout(() = > {
                                    uni.redirectTo({
                                        url: '/pages/login/Login'
                                    });
                                }, 1500)}else {
                            uni.showToast({
                                title: res.data.message,
                                icon: 'error'
                            })
                        }
                }
                resolve(res.data) // Successful, return data
            },
            fail: (err) = > { // Failed operation
                console.log('err==='.JSON.stringify(err));
                // uni.hideLoading();
                uni.showToast({
                    title: 'Network exception please hold on'.icon: 'error'}) reject(err); }})})}Copy the code

In main.js file, as shown below:

Hit the pit

The password is encrypted using Base64

Encapsulate a JavaScript method that supports base64 encryption and decryption across ends as follows:

/ * * *@desc Base64 encryption and decryption *@param {string} input
 * @returns {String}* /
export function Base64() {
 // private property
 let _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
 // public method for encoding
 this.encode = (input) = > {
  let output = ' ';
  let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  let i = 0;
  input = this._utf8_encode(input);
  while (i < input.length) {
   chr1 = input.charCodeAt(i++);
   chr2 = input.charCodeAt(i++);
   chr3 = input.charCodeAt(i++);
   enc1 = chr1 >> 2;
   enc2 = ((chr1 & 3) < <4) | (chr2 >> 4);
   enc3 = ((chr2 & 15) < <2) | (chr3 >> 6);
   enc4 = chr3 & 63;
   if (isNaN(chr2)) {
    enc3 = enc4 = 64;
   } else if (isNaN(chr3)) {
    enc4 = 64;
   }
   output = output +
   _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
   _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
  }
  return output;
 }
 // public method for decoding
 this.decode = (input) = > {
  let output = ' ';
  let chr1, chr2, chr3;
  let enc1, enc2, enc3, enc4;
  let i = 0;
  input = input.replace(/[^A-Za-z0-9\+\/\=]/g.' ');
  while (i < input.length) {
   enc1 = _keyStr.indexOf(input.charAt(i++));
   enc2 = _keyStr.indexOf(input.charAt(i++));
   enc3 = _keyStr.indexOf(input.charAt(i++));
   enc4 = _keyStr.indexOf(input.charAt(i++));
   chr1 = (enc1 << 2) | (enc2 >> 4);
   chr2 = ((enc2 & 15) < <4) | (enc3 >> 2);
   chr3 = ((enc3 & 3) < <6) | enc4;
   output = output + String.fromCharCode(chr1);
   if(enc3 ! =64) {
    output = output + String.fromCharCode(chr2);
   }
   if(enc4 ! =64) {
    output = output + String.fromCharCode(chr3);
   }
  }
  output = _utf8_decode(output);
  return output;
 }
 // private method for UTF-8 encoding
 this._utf8_encode = (string) = > {
  string = string.replace(/\r\n/g.'\n');
  let utftext = ' ';
  for (let n = 0; n < string.length; n++) {
   let c = string.charCodeAt(n);
   if (c < 128) {
    utftext += String.fromCharCode(c);
   } else if((c > 127) && (c < 2048)) {
    utftext += String.fromCharCode((c >> 6) | 192);
    utftext += String.fromCharCode((c & 63) | 128);
   } else {
    utftext += String.fromCharCode((c >> 12) | 224);
    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
    utftext += String.fromCharCode((c & 63) | 128); }}return utftext;
 }
 // private method for UTF-8 decoding
 this._utf8_decode = (utftext) = > {
  let string = ' ';
  let i = 0;
  let c = c1 = c2 = 0;
  while ( i < utftext.length ) {
   c = utftext.charCodeAt(i);
   if (c < 128) {
    string += String.fromCharCode(c);
    i++;
   } else if((c > 191) && (c < 224)) {
    c2 = utftext.charCodeAt(i+1);
    string += String.fromCharCode(((c & 31) < <6) | (c2 & 63));
    i += 2;
   } else {
    c2 = utftext.charCodeAt(i+1);
    c3 = utftext.charCodeAt(i+2);
    string += String.fromCharCode(((c & 15) < <12) | ((c2 & 63) < <6) | (c3 & 63));
    i += 3; }}returnstring; }}Copy the code

Page call method:

<script>
import { Base64 } from '@/utils/validate.js';
export default {
    onLoad() {
        // #ifdef H5
        // Only H5 is supported
        let str64 = window.btoa('12345678');
        console.log('str64===', str64);
        // #endif

        // App side, H5 side, wechat applets are supported
        let b = new Base64();
        let base64 = b.encode('12345678'); / / encryption
        console.log('base64===', base64);
    }
}
</script>
Copy the code

Fix input-Autocomplete plugin

Plugin address: ext.dcloud.net.cn/plugin?id=4…

  • Prompt box beautification

  • The input field is changed to an extension component
<! -- Plugins section -->
<uni-easyinput 
    class="iac-input" 
    :id="id" 
    :placeholder="placeholder" 
    :value="value" 
    @input="onInput" 
    autocomplete="off" 
/>
Copy the code
<! -- Template section -->
<input-autocomplete
    class="content"
    :value="formData.gh"
    v-model="formData.gh"
    placeholder="Please enter your work number"
    :isDisabled="isDisabled"
    highlightColor="#FF0000"
    :loadData="loadAutocompleteData"
    @selectItem="selectItemGh"
></input-autocomplete>
Copy the code

The benefits of changing the basic component input field to an extension component are: you can unify the form style and control the display and hiding of verification error messages.

  • New Disable Attribute

Return to previous page refresh

The navigation back button at the top of the operation returns to the previous page. The example code is as follows:

<script>
    export default {
        methods: {
            // return to the previous page
            goBack() {
                // #ifdef H5
                history.back();
                // #endif

                // #ifdef APP-PLUS
                uni.navigateBack({
                    delta: 1
                });
                // #endif
            }
        }
    }
</script>
Copy the code

To return to the previous page and refresh the data, sample code is as follows:

<script>
    export default {
        methods: {
            onSubmit() {
                const pages = getCurrentPages(); // Get the current page stack, which is an array
                let currPage = pages[pages.length - 1]; // Current page
                let prevPage = pages[pages.length - 2]; // Previous page
                console.log('prevPage===', prevPage);
                
                // #ifdef H5
                prevPage.submitSearch(); // The previous page submitSearch() method can be called to refresh the page
                // #endif

                // #ifdef APP-PLUS
                prevPage.$vm.submitSearch();
                // #endif
                uni.navigateBack();
            }
        }
    }
</script>
Copy the code

Conditional compilation

Conditional compilation is marked with special comments that are used to compile code for different platforms at compile time.

Write conditional compilation in pages. Json as shown below:

If no conditional compilation is added, the wechat applet developer tool will display a warning, as shown in the picture below:

Writing: Start with #ifdef or #ifndef plus %PLATFORM% and end with #endif.

  • #ifdef: If defined exists only on a platform
  • #ifndef: if not defined
  • %PLATFORM% : PLATFORM name (HBuildex will prompt)

Details please click here: uniapp dcloud. IO/platform? Id…

Note:

  • Conditional compilation is implemented using comments, which are written differently in different syntax and used by JS/ / comment, CSS use/ * comment * /, vUE/NVUE template<! - comments -- >;
  • Conditional compilation app-Plus includes app-nvUE and app-vUE, app-plus-nvue and app-nvue are the same, app-nvue for shorthand;
  • Be sure to use conditional compilationBefore compilingandThe compiledFile correctness, such as no extra commas in JSON files;
  • VUE3Need in the projectmanifest.jsonFile root configuration"vueVersion" : "3"

RPX units are not suitable for large screens

Mobile devices also have a lot of screen widths, and UI designers tend to draw at 750px screen widths. The advantage of using RPX at this time is that the screen width of various mobile devices is not very different. Compared with the effect of 750px, it restores the designer’s design as much as possible.

But once you’re off the mobile device, on a PC screen, or a pad landscape, because the screen width is much wider than 750. At this point, the results of RPX depending on the screen width are seriously out of line with expectations, and it is a terrible thing to see.

Uniapp adaptor tablet in pages. Json configuration globalStyle code is as follows:

/ / method
"globalStyle": {
    "rpxCalcMaxDeviceWidth": 960
}

/ / method 2
"globalStyle": {
    "rpxCalcMaxDeviceWidth": 0,}Copy the code

uni-table

A native UNI-UI table component that displays multiple pieces of similarly-structured data to meet basic business requirements. The disadvantage is that the table head cannot be fixed, the contents of the APP/wechat mini program list items will not be vertically centered, and the operation of new buttons will not be vertically centered.

PC side effect picture:

Wechat mini program effect picture:

Flat end effect picture:

After the improvement based on uni-Table table component, the table head can be fixed and color can be interlaced. The effect picture is as follows:

The code is as follows:

<template>
    <uni-table :loading="isLoading" emptyText="No data at present" class="table">
        <uni-tr class="theader">
            <uni-th width="100">The serial number</uni-th>
            <uni-th width="150">The user name</uni-th>
            <uni-th width="100">age</uni-th>
            <uni-th width="150">Creation date</uni-th>
            <uni-th width="300">note</uni-th>
            <uni-th width="300">operation</uni-th>
        </uni-tr>
        <tbody id="hello-tbody" class="tbody">
            <! -- No data at present column, self-set condition judgment -->
            <uni-tr v-if="false">
		<uni-td style="position: absolute; width: 100%; text-align: center; padding: 20px 10px;">Temporarily no data</uni-td>
	    </uni-tr>
            <uni-tr v-for="(item, index) in 20" :key="item" class="tbody-tr">
                <uni-td width="100">{{ index + 1 }}</uni-td>
                <uni-td width="150">A lazy person code farmers</uni-td>
                <uni-td width="100">18</uni-td>
                <uni-th width="150">The 2021-12-01 10:58:58</uni-th>
                <uni-th width="300">6666666666666666666666666666</uni-th>
                <uni-td width="300">
                    <view class="btn-group">
                        <button type="primary" @click="handleEdit" class="btn">The editor</button>
                        <button @click="handleDetail" class="btn">details</button>
                    </view>
                </uni-td>
            </uni-tr>
        </tbody>
    </uni-table>
</template>
<script>
    export default {
        mounted() {
            let wHeight = document.body.clientHeight,
            dom = document.getElementById('hello-tbody'),
            tablePoseY = dom.getBoundingClientRect().y;
            dom.style.height = wHeight - tablePoseY +'px'; }}</script>
<style lang="scss" scoped>
    .table {
        .theader {
            display: table;
            width: 100%;
            background-color: #F1F1F1;
        }
        .tbody {
            display: block;
            overflow-y: auto; &::-webkit-scrollbar { display: none; } & -tr {
                width: 100%;
                display: table;
                table-layout: fixed; }}.btn-group {
            display: flex;
            justify-content: center;

            .btn {
                width: 100px;
                margin: 0 20px 0 0;
            }
        }
    }

    /deep/ .uni-table-tr:nth-of-type(2n) {
        background-color: #F7F8FE;
        &:hover {
            background-color: #F7F8FE ! important;
        }
    }

    /deep/ .uni-table-tr:nth-child(n + 2):hover {
        background-color: transparent;
    }
</style>
Copy the code

Wechat applet error reported

  • Error: Cannot read property ‘forceUpdate’ of undefined

  • The solution

Uni-app needs to configure wechat applets AppID, find manifest.json file in the root directory of the project, open it, select wechat applets configuration, fill in the applets ID, close wechat developer tools, and restart the project.

conclusion

Above is my initial experience of developing a small tablet app using uniApp framework for the first time. I have combed and summarized the process myself. Please point out if there is anything wrong. Also hope to know more like-minded friends through here, if you also like to toss, welcome to add my friends, wave together, progress together 🦄.

If this article is helpful to readers, please give a thumbs up to 👍 or share is my biggest support.

Pay attention to the public number [lazy code farmers], get more project actual combat experience and a variety of source resources. If you also love technology and are fascinated by it, welcome to add me to wechat [Lazycode520], of course, you can also add me to pull you into the group, after all, I am also an interesting front end, it is not bad to know me 🌟 ~

The relevant data

  • 1. Vue single file components (SFC) specification — vue-loader.vuejs.org/zh/spec.htm…
  • 2. Uni – app component specifications – uniapp. Dcloud. IO/component/R…
  • 3. The uni – app interface specification — uniapp. Dcloud. IO/API/README
  • 4. HBuilderX editor tool – www.dcloud.io/hbuilderx.h…
  • 5. WeChat developer tools — developers.weixin.qq.com/miniprogram…
  • 6. Java jre8 download website – www.oracle.com/java/techno…
  • 7. Apply for WeChat applet AppID — developers.weixin.qq.com/miniprogram…
  • 8. WeChat small program released on-line process — developers.weixin.qq.com/miniprogram…