前言
公司要调研小程序开发,学习小程序开发流程。个人以小程序对话gpt为案例,本文主要用作记录。主要参考uni-app官网
前置条件:安装NodeJS(使用的是v18.17.0)、安装HbuildX
新建项目浏览器启动测试
这里使用的是vue2
直接浏览器启动
查看效果
引入uView
uView简介
uView是uni-app生态专用的UI框架,uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码, 可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台(引言自uni-app网)。但目前除微信小程序,其它小程序平台的兼容可能存在一些问题,后续会针对这方面持续优化。
uView安装
安装有多种方式有两种方式:uni-app
插件市场和npm。此处记录npm方式。
在项目根目录执行如下命令即可:
// 如果您的根目录没有package.json文件的话,请先执行如下命令:
// npm init -y
npm install uview-ui@2.0.36
// 更新
// npm update uview-ui
uView配置
关于SCSS
uView依赖SCSS,您必须要安装此插件,否则无法正常运行。
- 如果您的项目是由
HBuilder X
创建的,相信已经安装scss插件,如果没有,请在HX菜单的 工具->插件安装中找到"scss/sass编译"插件进行安装, 如不生效,重启HX即可 - 如果您的项目是由vue-cli创建的,请通过以下命令安装对sass(scss)的支持,如果已安装,请略过。
// 安装sass
npm i sass -D
// 安装sass-loader,注意需要版本10,否则可能会导致vue与sass的兼容问题而报错
npm i sass-loader@10 -D
引入uView主JS库
在项目src
目录中的main.js
中,引入并使用uView的JS库,注意这两行要放在import Vue
之后。
// main.js
import uView from "uview-ui";
Vue.use(uView);
引入uView的全局SCSS主题文件
在项目src
目录的uni.scss
中引入此文件。
/* uni.scss */
@import 'uview-ui/theme.scss';
引入uView基础样式
在App.vue
中首行的位置引入,注意给style标签加入lang="scss"属性
<style lang="scss">
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
@import "uview-ui/index.scss";
</style>
配置easycom组件模式
此配置需要在项目src
目录的pages.json
中进行。
// pages.json
{
"easycom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
},
// 此为本身已有的内容
"pages": [
// ......
]
}
测试
在以下微信小程序启动中一同测试。
微信小程序启动
直接在HbuildX中启动,选择微信小程序。
注意事项
需要在小程序设置中安全设置开启服务端口
功能Demo
设置底部导航
修改pages.json。注意需要在pages文件夹对应路径下建立vue文件。
{
"easycom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
},
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
},
{
"path": "pages/func/funcList",
"style": {
"navigationBarTitleText": ""
}
},{
"path" : "pages/chat/dialog",
"style" : {
"navigationBarTitleText" : "",
"navigationStyle" : "custom"
}
},{
"path" : "pages/my/my",
"style" :
{
"navigationBarTitleText": ""
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#26B3A0",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/index/index",
"iconPath": "static/images/index.png",
"selectedIconPath": "static/images/indexs.png",
"text": "首页"
}, {
"pagePath": "pages/func/funcList",
"iconPath": "static/images/func.png",
"selectedIconPath": "static/images/funcs.png",
"text": "功能"
},
{
"pagePath": "pages/chat/dialog",
"iconPath": "static/images/chat.png",
"selectedIconPath": "static/images/chats.png",
"text": "对话"
},
{
"pagePath": "pages/my/my",
"iconPath": "static/images/my.png",
"selectedIconPath": "static/images/mys.png",
"text": "我的"
}]
},
"uniIdRouter": {}
}
首页设置
<template>
<view class="homeLayout">
<uv-navbar :title="title" :autoBack="false" titleStyle="font-weight: 600">
</uv-navbar>
<view class="banner">
<swiper circular indicator-dots indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#fff" autoplay>
<swiper-item v-for="item in bannerList" :key="item._id">
<navigator v-if="item.target == 'miniProgram'"
:url="item.url"
class="like"
target="miniProgram"
:app-id="item.appid"
>
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
<navigator v-else :url="`/pages/classlist/classlist?${item.url}`" class="like">
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
</swiper-item>
</swiper>
</view>
<view class="u-content">
<uv-parse :content="content"></uv-parse>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const bannerList= ref([]);
const content = ref([]);
const title = ref('维亿智慧');
const getBanner = async ()=>{
// let res =await apiGetBanner();
// bannerList.value = res.data;
setTimeout(() => {
bannerList.value = [
{ _id: '1', target:'miniProgram', picurl: 'http://121.41.225.164/staticfile/%E9%A6%96%E9%A1%B51.jpg' },
{ _id: '2', target:'miniProgram', picurl: 'http://121.41.225.164/staticfile/%E9%A6%96%E9%A1%B52.jpg' },
];
}, 1000);
}
const getContent = async ()=>{
// let res =await apiGetBanner();
// bannerList.value = res.data;
setTimeout(() => {
content.value = `
<p> 维亿科技是一家在国内以工业互联,数字孪生为理念,从工艺流程优化、生产和管理变革出发,通过基于数字孪生的全局优化控制,建设基于工业互联网的能源智能工厂,从设计,生产,运维实现能源企业价值重构,推动能源产业变革升级的企业。地址于武汉中心区域,地区拥有多所知名高校和研究机构,培养了大量的计算机科学和软件工程等专业人才。人才吸收为公司提供了强大的研发和创新能力,经过多年的发展,形成了完善的产业链和供应链,涵盖了软件开发、测试、运维、技术支持等多个环节。</p>
<div class="visible-xs-block" style="margin-top: 20px">
<span class="mobile-title2">智慧能源</span>
</div>
<img style='height:300rpx;width:100%' src="http://121.41.225.164/staticfile/solutions/%E6%99%BA%E6%85%A7%E8%83%BD%E6%BA%90%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.jpg">
<div class="mobile-function-description">
维亿科技与能源紧密结合“互联网+能源管理“与电力改革背景,以”安全、数据、成本、管理”为核心价值,实现对电、水、气、生产环境、设备等用能数据进行集中监控和管理,帮助客户提高能源管理水平,降低用能成本,提升用能质量,减少安全隐患
</div>
<div class="visible-xs-block" style="margin-top: 20px">
<span class="mobile-title2">智慧园区</span>
</div>
<img style='height:300rpx;width:100%' src="http://121.41.225.164/staticfile/solutions/%E6%99%BA%E6%85%A7%E5%9B%AD%E5%8C%BA%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.jpg">
<div class="mobile-function-description">
利用先进的信息技术和可视化技术,通过构建三维可视化平台,实时采集、整合和展示园区内各类资源、设施、企业等信息,实现全局监控和智慧运营。同时,它融合物联网、云计算等技术,优化资源配置,提高决策效率,推动园区产业的创新与发展,有助于提升园区的综合竞争力和可持续发展水平。
</div>
<div class="visible-xs-block" style="margin-top: 20px">
<span class="mobile-title2">智慧城市</span>
</div>
<img style='height:300rpx;width:100%' src="http://121.41.225.164/staticfile/solutions/%E6%99%BA%E6%85%A7%E5%9F%8E%E5%B8%82%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.jpg">
<div class="mobile-function-description">
维亿科技综合利用GIS、物联网、云计算等技术,将环卫设施、人员及车辆等环卫要素融合在一个信息平台上,按照“网格化管理,监管分离”的环卫管理模式,实现对环卫管理所涉及的人、车、物、事进行全过程实时管控,实现精细化管理,规范作业,提高效率,降低运营成本,建立环卫管理的长效机制。
</div>
<div class="visible-xs-block" style="margin-top: 20px">
<span class="mobile-title2">智慧化工</span>
</div>
<img style='height:300rpx;width:100%' src="http://121.41.225.164/staticfile/solutions/%E6%99%BA%E6%85%A7%E5%8C%96%E5%B7%A5%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.jpg">
<div class="mobile-function-description">
维亿以工业互联,数字孪生为理念,从工艺流程优化、生产和管理变革出发,通过基于数字孪生的全局优化控制,建设基于工业互联网的化工智能工厂,从设计,生产,远维实现化工企业价值结重构,推动石化,重化工、精细化工,医药化工等化工产业变革升级。
</div>
`
}, 1000);
}
getBanner();
getContent();
</script>
<style lang="scss" scoped>
.homeLayout{
.banner{
width: 750rpx;
padding:30rpx 0;
swiper{
width: 750rpx;
height: 440rpx;
&-item{
width: 100%;
height: 100%;
padding:0 30rpx;
.like{
width: 100%;
height: 100%;
image{
width: 100%;
height: 100%;
border-radius: 10rpx;
}
}
}
}
}
.u-content {
margin-left: 30rpx;
margin-right: 20rpx;
// margin-top: 100rpx;
}
}
.mobile-btn-img{
width: 100%;
height: 100rpx;
}
</style>
对话页面
<template>
<view class="chat">
<uv-navbar :title="title" :autoBack="false" titleStyle="font-weight: 600">
</uv-navbar>
<mescroll-body top="150" :auto="false" @init="mescrollInit" :down="downOption" @down="downCallback"
:up="upOption" @up="upCallback">
<view style="padding-bottom: 30px;">
<view class="guide">
<view class="guide_title">
{{welcome.title}}
</view>
<view class="guide_text">
{{welcome.info}}
</view>
<view @click="welcomeSend(welcomeItem)" class="guide_select" v-for="welcomeItem, index in welcome.list" :key="index">
<text>{{welcomeItem}}</text>
<uv-icon size="14" name="arrow-right" color="#9f9f9f"></uv-icon>
</view>
</view>
</view>
<view class="chat-item" v-for="item, index in chat" :key="index">
<uv-transition :show="true" mode="fade-right">
<view class="chat-item__right">
<view class="chat-item__right-left">
<view class="chat-item__right-message" @longtap="copy(item.question)">
{{ item.question }}
</view>
</view>
<uv-avatar class="chat-item__right-avatar" shape="circle" size="35" :src="userInfo.head_img"></uv-avatar>
</view>
</uv-transition>
<uv-transition :show="true" mode="fade-left">
<view class="chat-item__left u-flex">
<uv-avatar size="35" shape="circle"></uv-avatar>
<view class="chat-item__left-right">
<view class="chat-item__left-name"> {{channel.name}} </view>
<view class="chat-item__left-bottom">
<view class="chat-item__left-message" @longtap="copy(item.answer)">
{{item.answer}}
<uv-loading-icon v-if="creating"></uv-loading-icon>
</view>
</view>
</view>
</view>
</uv-transition>
</view>
<view class="seize" style="height: 200rpx"></view>
</mescroll-body>
<view class="input-box">
<uv-input @confirm="send" :disabled="creating" confirmType="send" dshape="circle" placeholder="有问题尽管问我~" border="surround" v-model="question" cursorSpacing="10" maxlength="500">
</uv-input>
<uv-icon v-if="creating" color="#1acc89" size="38" name="pause-circle-fill" @click="finish"></uv-icon>
<uv-icon v-if="!creating" color="#1acc89" size="38" name="play-circle-fill" @click="send"></uv-icon>
</view>
</view>
</template>
<script>
import MescrollBody from "mescroll-uni/mescroll-body.vue";
import * as base64 from "base-64";
import CryptoJS from 'crypto-js';
import * as utf8 from "utf8";
import URL from 'url';
export default {
components: {
MescrollBody
},
data() {
return {
userInfo: uni.getStorageSync('userInfo'),
mescroll: null,
upOption: {
use: false,
},
downOption: {
auto: false,
textInOffset:'下拉加载',
beforeEndDelay:1000,
bgColor:'white',
textColor:'black'
},
chat : [],
page : 0,
sessionId : 0,
question : '',
creating : 0,
welcome : {
title : '你好,我是你的智能助手',
info : '作为你的智能伙伴,我既能写文案、想点子,又能陪你聊天、答疑解惑。你可以试着问我:',
list : [
'请帮我生成一对春联,要表达出吉祥、团圆、步步高升的意思。'
]
},
streamDefault:true,
title : '对话',
channel:uni.getStorageSync('channel_info'),
httpUrl: "",
modelDomain: '',
APPID: '',
APISecret: '',
APIKey: '',
sparkResult: '',
historyTextList: [],
tempRes: ''
}
},
onLoad(options) {
this.downCallback();
},
methods: {
async send() {
this.creating = 1;
if (!this.question) {
uni.showToast({
title: '你还没有输入内容呢!',
icon: 'none'
});
this.creating = 0;
return
}
this.chat.push({
question: this.question,
answer: ''
});
this.$nextTick(() => {
this.mescroll.scrollTo(99999999);
});
let myUrl = await this.getWebSocketUrl();
this.tempRes = "";
let realThis = this;
this.socketTask = uni.connectSocket({
url: myUrl,
method: 'GET',
success: res => {
console.log(res, "ws成功连接...", myUrl)
realThis.wsLiveFlag = true;
}
})
realThis.socketTask.onError((res) => {
uni.showToast({
title: '请求失败,请联系管理员。错误内容:'+JSON.stringify(res),
icon: 'none',
duration: 2000
});
})
realThis.socketTask.onOpen((res) => {
realThis.historyTextList.push({
"role": "user",
"content": realThis.question
})
realThis.question = ''
console.info("wss的onOpen成功执行...", res)
let params = {
"header": {
"app_id": realThis.APPID,
"uid": "aef9f963-7"
},
"parameter": {
"chat": {
"domain": realThis.modelDomain,
"temperature": 0.5,
"max_tokens": 1024
}
},
"payload": {
"message": {
"text": realThis.historyTextList
}
}
};
console.log("请求的params:" + JSON.stringify(params))
realThis.socketTask.send({
data: JSON.stringify(params),
success() {
console.log('第一帧发送成功')
}
});
});
realThis.socketTask.onMessage((res) => {
console.log('收到API返回的内容:', res.data);
let obj = JSON.parse(res.data)
let dataArray = obj.payload.choices.text;
for (let i = 0; i < dataArray.length; i++) {
realThis.chat[realThis.chat.length-1]['answer']= realThis.chat[realThis.chat.length-1].answer + dataArray[i].content
realThis.tempRes = realThis.tempRes + dataArray[i].content
}
realThis.$nextTick(() => {
realThis.mescroll.scrollTo(99999999);
});
let temp = JSON.parse(res.data)
if (temp.header.code !== 0) {
console.log(`${temp.header.code}:${temp.message}`);
realThis.socketTask.close({
success(res) {
console.log('关闭成功', res)
realThis.wsLiveFlag = false;
},
fail(err) {
console.log('关闭失败', err)
}
})
}
if (temp.header.code === 0) {
if (temp.header.status === 2) {
realThis.historyTextList.push({
"role": "assistant",
"content": this.tempRes
})
console.log(realThis.historyTextList);
realThis.socketTask.close({
success(res) {
realThis.creating = 0
console.log('关闭成功', res)
},
fail(err) {
realThis.creating = 0
}
})
测试效果运行效果
小程序发布
注册小程序开发者账号(过程略),填写appid后直接上传即可。
小程序成员添加
小程序成员管理中可以添加项目成员和体验成员。便于真机体验和测试。
项目成员:可参与小程序开发、运营的成员,可登陆小程序管理后台,包括运营者、开发者及数据分析者。体验成员:表示参与小程序内测体验的成员,可打开体验版或开发版小程序。
小程序体验版测试
扫码可打开看实际效果
安全域名问题
"errMsg:"createSocketTask:fail wcwss url not in domain list
平台设置小程序域名
按实际情况填写
注意微信小程序支持不支持ws和http等不安全协议头。
注意
配置完成后需要重新进入小程序。
安卓重新进入依旧出错
按以下操作:
在手机端,下拉小程序->把当前的小程序删除->再重新进行进入,重试。