- 你好,你的小程序设计提供播放、观看等服务,请补充选择:文娱-其他视频类目的解决办法。
如果小程序内的视频真的不存在传播的话可以按照如下方式解决: 1.审核不通过的反馈文案:视频播放和观看只限于当前用户自己使用,而视频内容也只是用户自己生成或制作的视频,并不涉及平台内传播或其他多用户平台观看播放等媒体行为,所有视频和制作仅限于用户自己,没有其他传播 2.基本设置>用户生成内容场景信息安全声明中设置:[图片]
07-17 - 微信创新实验室小程序全球编程创新挑战赛
一、活动简介 国家“十四五”规划和《中国教育现代化2035》中强调,建设教育强国,用科技手段、新一代信息技术以及多方社会资源,开展教育新生态的尝试,健全协同育人机制。 当前,人工智能是新一轮科技革命和产业变革的重要驱动力量,国务院印发《新一代人工智能发展规划》提到,到2030年要使中国人工智能理论、技术与应用总体达到世界领先水平,成为世界主要人工智能创新中心。人工智能技术的飞速发展给教育带来了重要机遇和挑战。 2022年9月起,在“信息科技”在新课标课程设置中按要求独立设课并成为全国中小学生必修课的同时,青少年人工智能与未来媒体全球创新挑战赛拉开序幕。去年,共有来自亚洲、欧洲、美洲和非洲的630名学生提交了近500份作品。在开放式命题下,参赛的“小小程序员”们用中国原创编程语言微信小程序等编程工具做出了极具创意的作品。 “创造力就是小程序的生命力”,而青少年们的创造力是无限的。今年,比赛将延续“中国原创”的科技创新精神,鼓励参赛青少年继续以小程序为画布,思考科技如何改变和美好人类社会,并按照大赛基本要求提交作品。 二、参赛条件及分组办法 1、凡在 2024年 9月前,在校1-12年级、中专或职高学生均可参赛; 2、选手所在学段组别分为:潜龙组(1-7年级)、飞龙组(8-12年级)(包含中专和职高); 3、比赛为团队赛,每队参赛人数1-3人,每队最多可有1-2名指导老师,多名学生的指导老师可以重复。指导老师作为责任人,有责任监督竞赛期间人身安全保护、财产,指导参赛学生制定学习计划,督促参赛学生顺利完成比赛。 三、比赛参与办法 1、参加比赛的队伍请通过大赛官网报名,网址:edu.weixin.qq.com; 2、初赛报名和提交截止时间:2024年1月31日23时59分; (在2023年12月31日前上传作品的参赛队伍,有机会参与2024年1月份大赛官方组织的面对面答疑交流会。) 在最终提交截止日期前,以最后提交的作品版本为该队伍最终参赛作品; 3、大赛分初赛和决赛两个环节。初赛通过线上评审遴选出决赛团队,决赛采用专家问辩方式决出最后奖项; 4、比赛作品一律线上提交,参赛队登录大赛官网按提示提交比赛作品。 四、比赛规则 (一)作品整体要求 1、参赛队需提交以微信小程序为载体的作品方案,编程工具不限; 2、初赛阶段,参赛队需按照提交入口提示,提交基于实践操作的3分钟以内视频纪录短片、项目说明书和相关图片,以展示作品创意、创作思路及创作实践过程(具体要求以大赛官网表述为准); 3、作品内容方面,初赛阶段参赛队可在以下命题选其一,并按照要求完成小程序原型设计: 以“校园生活”为灵感,展示真实有趣的校园生活,或为在校师生解决实际问题,设计一款实用有趣的小程序,产品名称自拟;以“我脚下的地方”为灵感,可以介绍家乡特色的美食、风光,也可传授与地域有关的特色技能,设计一款实用有趣的小程序,产品名称自拟;以“生活小能手”为灵感,观察生活中常见却容易被忽略的难题,或挖掘一个公益需求,为这个社会设计一款实用有趣的小程序,产品名称自拟;以“解锁未来”为灵感,结合AR、AI等人工智能元素,畅想科技如何改变学习和生活,设计一款实用有趣的小程序,产品名称自拟。(二)线上选拔参与办法 1、参赛队在规定时间内通过大赛官方网站提交作品方案,采用线上评审方式对竞赛作品设计方案进行打分,按分数高低决定入围决赛名单;初赛收到的所有最终作品将合并综合评审。 2、各组别单独评审,分别确定入围决赛名单; (三)决赛参与办法 1、所有参赛队伍按全国排名遴选优秀团队进入总决赛; 2、总决赛入围名单将通过大赛官方渠道提前公示和告知; 3、入围决赛的参赛队伍在限定时间内可继续优化升级参赛作品,在决赛截止时间内提交决赛作品方案; 4、决赛采用线下专家问辩的方式进行,参赛队由指导老师带领,携带创作作品,现场给专家汇报项目的创作思路及演示功能,汇报时间10-15分钟,专家问答5分钟,现场评审得分,汇总后得出最终名次,随后进行颁奖。特定情况下有需要的决赛队伍可申请线上连线。决赛和颁奖一般1-2天时间完成。 5、各组别单独评审,确定最终奖项归属; 6、入围决赛的参赛队伍需自行承担决赛期间的食宿费用; 7、总决赛的奖项设置及支持政策如下: [图片] 其他支持政策: 参赛者初审提交作品合格颁发“项目综合实践结业证”大赛辅导教师师资培训合格后将获得认证证书参赛者优先获得科创少年远行者计划和互联网公司顶级科技实验室参观的机会优秀组织校或者区域将获得优秀人工智能教育实验区(校)挂牌及共建平台资源优秀作品队伍将优先获得微信颁发的认证证书、大厂参观及优先实习录用机会,和官方推荐信(四)大赛评审原则 大赛希望搭建全球青少年科技创新与分享的平台,共同促进和推动全球青少年人工智能素养提升,大赛评审原则如下: [图片] 五、异议处理机制 1、微信创新实验室小程序全球编程创新挑战赛接受社会的监督,挑战赛的评审工作实行异议制度; 2、任何单位或者个人对微信创新实验室小程序全球编程创新挑战赛参赛选手、参赛单位及其项目的创新性、先进性、实用性及推荐材料真实性、比赛成绩等持有异议的,应当在项目成绩公布之日起 10 日内向大赛组委会提出,逾期不予受理; 3、提出异议的单位或者个人应当提供书面异议材料,并提供必要的证明文件。提出异议的单位、个人应当表明真实身份。个人提出异议的,应当在书面异议材料上签署真实姓名;以单位名义提出异议的,应当加盖本单位公章。以匿名方式提出的异议一般不予受理; 4、提出异议的单位、个人不得擅自将异议材料直接提交评审组织或者评审专家;专家收到异议材料的,应当及时转交大赛组委会,不得提交评审组织讨论和转发其他评审专家; 5、大赛组委会在接到异议材料后应当进行审查,对符合规定并能提供充分证据的异议,应予受理; 6、为维护异议者的合法权益,大赛组委会、推荐单位及其指导老师,以及其他参与异议调查、处理的有关人员应当对异议者的身份予以保密;确实需要公开的,应当事前征求异议者的意见; 7、涉及参赛选手所完成项目的创新性、先进性、实用性及推荐材料真实性、比赛成绩的真实性等内容的异议由大赛组委会负责协调,由有关指导单位或者指导老师协助。参赛选手接到异议通知后,应当在规定的时间内核实异议材料,并将调查、核实情况报送活动组委会审核。必要时,大赛组委会可以组织评审专家进行调查,提出处理意见。涉及参赛选手及其排序的异议由指导单位或者指导老师负责协调,提出初步处理意见报送大赛组委会审核。参赛选手接到异议材料后,在异议通知规定的时间内未提出调查、核实报告和协调处理意见的,该项目不认可其比赛成绩; 8、异议处理过程中,涉及异议的任何一方应当积极配合,不得推诿和延误。参赛选手在规定时间内未按要求提供相关证明材料的,视为承认异议内容;提出异议的单位、个人在规定时间内未按要求提供相关证明材料的,视为放弃异议; 9、异议自异议受理截止之日起 60 日内处理完毕的,可以认可其比赛成绩;自异议受理截止之日起一年内处理完毕的,可以直接参加下一年度比赛; 10、大赛组委会应当向大赛专家评审委员会报告异议核实情况及处理意见,并将决定意见通知异议方和参赛选手; 六、赛事组委会 组委会联系方式: 联系人:祁雪晶、邹雨函 电 话:13811422001、020-81167888 转 862417 大赛官网:edu.weixin.qq.com 赛事技术咨询: 联系人:覃思源、袁嫣 电话:020-81167888 转 860276 、15914355596 七、知识产权声明 大赛组委会鼓励并倡导技术创新以及技术开源,并尊重参赛队的知识产权。参赛队伍比赛中开发的所有知识产权均归所在队伍所有,组委会不参与处理队伍内部成员之间的知识产权纠纷,参赛队伍须妥善处理本队内部学校及其他身份的成员之间对知识产权的所有关系。 所有参赛设计方案及文档一经递交,将不再发还。大赛主办单位拥有全部参赛作品的使用权,并可做出版、发表及宣传等用途。大赛组委会将选择部分有代表性的决赛作品在大赛官方网站上公开,以带动大家共同提高。 八、主办单位免责声明 1、未经主办单位书面授权,任何单位和个人以本赛事名义开展的活动均属假冒、侵权; 2、未经当地防疫和教育部门批准,任何单位和个人不得以本赛事名义组织线下聚集; 3、主办单位不会以本赛事名义向学生收取任何费用; 4、所有参赛作品,均须为参赛个人原创,不能存在任何侵犯第三方权利的内容,不能违反法律法规的规定,主办单位对此不承担责任; 5、请参与活动人员妥善保管自己的贵重物品(如现金、笔记本电脑、手机和参赛设备等),避免丢失或损坏,主办单位对此不承担责任; 九、线下活动注意事项 1、参与活动人员必须严格遵守属地新冠疫情防控相关规定,按照疫情常态化防控要求控制现场聚集人数,凭“绿码”入场,配合工作人员测量体温,严格佩戴口罩等,体温超过 37.2℃或者未戴口罩者禁止入场; 2、参与活动人员必须牢固确立“安全第一”的意识,把活动安全放在首要位置。严格注意用电安全,相关机器人设备须提前充好电,准备好备用电池,规范用电,防止触电。严格注意防火安全,禁止携带易燃易爆等危险品和打火机、火柴等进入赛场。严格注意操作安全,活动期间如有发射弹丸、切割材料、器件焊接等危险操作时必须戴好头盔、手套、护目镜等防护措施。活动期间,参与活动人员应熟悉场地环境,若遇紧急情况,严格服从安保人员指挥; 3、参与活动人员应遵守场地制度,爱护公共设施,自觉保持公共卫生。 十、其它 1、关于挑战赛规则的任何补充、修订,将在大赛公共服务网站及微信公众号上发布; 2、比赛期间,凡是规则中没有说明的事项由专家评审委员会现场决定; 3、主办单位对凡是规则中未说明及有争议的事项拥有最后解释权、补充权和决定权。 相关阅读: 小程序全球编程创新挑战赛总决赛入围名单公示 微信创新实验室全球探索计划之剑桥夏校 小程序全球创新编程挑战赛获奖名单公示
04-18 - 关于新版隐私协议接口wx.onNeedPrivacyAuthorization的适配解读以及实现代码
官方公告地址: https://developers.weixin.qq.com/community/develop/doc/00042e3ef54940ce8520e38db61801 目前,开发工具或者体验版的小程序,调试基础库如果是2.33.0及以上就得适配了,线上版本9月15日之后生效,所以这之前需要尽快改完,发布一版,否则到了9月15号之后 线上就会生效报错了。 其实改起来也很简单,以下是实现步骤和代码: 1、首先看一下这个网址,里边包含涉及到的隐私的接口,这些接口都要适配一下 https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html [图片] 在以上接口用到的页面,需要画一下类似上边的弹窗(这个弹窗可以全局定义个组件,方便多个页面共用),然后里边蓝字可以点击后调用wx.openPrivacyContract(Object object)接口即可,会自动跳转打开隐私协议页面。 拒绝按钮可以加一个点击事件,然后在事件里这样写 [图片] 同意按钮比较特殊,布局需要用button这样写,记得给button加一个Id [图片] 然后在handleAgreePrivacyAuthorization里就可以获取到点击事件,这样写 [图片] 2、最后需要在onLoad或者onShow里加上以下监听代码,在这里边让自定义的隐私弹窗显示出来即可。 [图片] 以上代码加上就可以了,如果业务逻辑用到了需要判断是否授权过,可以加上 wx.getPrivacySetting(Object object)去获取是否授权过,用不到可以不加这个判断。
2023-08-16 - 小程序搜索文字高亮显示怎么实现?
[图片] 后端返回数据,数据中包含关键字的高亮,小程序怎么通过JS添加样式
2020-08-21 - 小程序的智能裁剪接口应该怎么用
在小程序的服务端接口中,有一类是图像处理接口,其中有一个接口是让大家觉得用起来很头疼的,就是 aiCrop — 图片智能裁剪这个接口。 这个接口根据官方的描述,其能力是 “本接口提供基于小程序的图片智能裁剪能力。”,但是,根据文档中给出的结果,似乎也并没有返回图片的 Buffer 流,那么这个接口真正应该怎么用呢?背后又有什么坑呢?今天我就给你讲一讲。 前置条件 你需要已经注册好小程序,并开通小程序云开发(本次演示将基于小程序云开发制作) 业务流程 [图片] 流程说明 用户侧选择图片,并生成临时文件路径(如果是网络图片,需要下载到本地,并修改云函数,改为直接传递 网络图片地址) 将图片上传的云存储中,并拿到 FileID 将 FileID 传递到云函数中,云函数获取到对应的临时 URL 将临时文件 URL 传递到微信的 AI 剪切接口 AI 接口将裁剪结果返回到云函数 云函数将裁剪结果返回到小程序 小程序基于返回结果进行渲染。 服务端代码 这里我们创建一个云函数来完成图片的裁剪,你需要创建一个新的云函数,其中[代码]index.js[代码]的代码如下 [代码]// index.js const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { let fileId = event.file; // 获取文件的临时连接 let tempUrl = await cloud.getTempFileURL({ fileList: [fileId] }) let newUrl = tempUrl.fileList[0].tempFileURL; // 对图片进行裁剪 let cropResult = await cloud.openapi.img.aiCrop({ imgUrl:newUrl, ratios:'1,2.35,0.5,0.25,3.25'//裁剪比例 }) return cropResult } [代码] 以及在该函数目录下创建一个 [代码]config.json[代码] 文件,内容如下 [代码]{ "permissions": { "openapi": [ "img.aiCrop" ] } } [代码] 这样就完成了云函数部分的内容。 上面这段代码帮助我们获取 FileID 对应的文件临时路径,并将其传递给微信接口进行调用。 小程序端调用代码 在小程序端,我们主要是选择文件,将其上传到云端,并调用云函数进行裁剪,在取得返回值后在小程序端进行渲染。 小程序的页面 JS 代码如下 [代码]Page({ /** * 由于此数据仅在逻辑层使用,因此定义一个tempData 进行存储 */ tempData:{ path:null, }, onClick() { /** * 选择文件 */ wx.chooseImage({ success: res => { /** * 获取文件路径,并传递给 tempData */ let file = res.tempFiles[0].path this.tempData.path = file console.log("[info]:开始上传文件") /** * 上传文件到云存储 */ wx.cloud.uploadFile({ filePath: file, cloudPath: "test.jpg" }).then(res => { /** * 调用云函数 */ console.log("[info]:开始调用云端裁剪") wx.cloud.callFunction({ name: "aicrop", data: { file: res.fileID } }).then(res => { /** * 调用裁剪 */ console.log("[info]:云端裁剪成功 ", res) this.crop(res.result); }).catch(err => { console.error("[error]:函数调用错误", err) }) }).catch(err => { console.error("[error]:文件上传错误", err) }) }, fail: err => { console.error("[error]:文件选择错误", err) } }) }, crop(cropOps) { /** * 获取 Context */ let ctx = wx.createCanvasContext('aiCrop', this); /** * 判断是否成功裁剪 */ if (cropOps.results.length == 0) { return } /** * 计算裁剪的值 */ let crop = cropOps.results[0]; let width = crop.cropRight - crop.cropLeft let height = crop.cropBottom - crop.cropTop /** * 绘制图像 */ ctx.drawImage(this.tempData.path, crop.cropLeft, crop.cropTop, width, height, 0, 0, 300, 300); ctx.draw() } }) [代码] 对应页面的 WXML 页面结构如下 [代码]<button bindtap="onClick">Crop MY IMAGE</button> <canvas canvas-id="aiCrop" style="width:300px;height:300px;"></canvas> [代码]
2019-12-22 - 如何使用微信小程序·云开发的Node.js云函数生成Word文档(2021-10-15更新)
编者按 近期一个云开发项目有生成Word文档的需求,经过搜索,发现并没有小程序·云开发有关生成word文档的案例,因为本人还是本科生且非科班出身,一路摸着石头过河,遇到了不少困难,期间还试图向社区的大佬们求助;花了两天时间才搞定这一百行代码,现在分享给大家。 代码有些糙,希望大佬们不要嫌弃。 一、安装云函数依赖officegen、fs 工欲善其事必先利其器,我们知道云函数代码运行在云端Node.js环境中,因此,理论上来说,Node.js能做的事情,小程序·云开发的云函数基本上也能做到。officegen是Github上一款生成微软Office文档的工具,包括.docx、.xlsx、.pptx三种文件,由于我只用了.docx,本文将以Word文件为例。 https://github.com/Ziv-Barber/officegen [图片] 1. 首先我们在微信开发者工具中 新建一个云函数 => 右键云函数名 => 在终端中打开 [图片] 2. npm安装依赖officegen和fs,为了方便本地调试云函数,我们这里也安装wx-server-sdk。 [图片] 代码如下,请逐个安装,如果安装有问题,可以自行搜索“npm”或“npm taobao 镜像” ;这里不再赘述。 npm i officegen npm i fs npm i wx-server-sdk 3. 在云函数index.js开头写下以下代码,引用我们刚刚安装的包。 const cloud = require('wx-server-sdk') const officegen = require('officegen'); const fs = require('fs'); const docx = officegen('docx'); 二、创建Word文档的内容 文档地址: https://github.com/Ziv-Barber/officegen/blob/master/manual/docx/README.md 1. 首先我们根据文档定义(Ctrl CV)两个函数 //文档生成完成后调用,后来其实发现没啥用 // Officegen calling this function after finishing to generate the docx document: docx.on('finalize', async function (written) { console.log('Finish to create a Microsoft Word document.') }) //生成文档出现问题时调用 // Officegen calling this function to report errors: docx.on('error', function (err) { console.log(err) }) 2. 创建段落API: docx.createP(options) //声明一个创建段落的变量p0bj let pObj = docx.createP(options) //创建一个段落并插入文本 pObj = docx.createP({ align: 'center' //文字对齐方式,center、justify、right;默认为left indentLeft = 1440; // 段落缩进 Indent left 1 inch indentFirstLine = 440; // 首行缩进 }) pObj.addText('你要插入的文字,这里可以时变量', { bold: true, //是否加粗,默认false font_face: 'KaiTi', //字体,这里以“楷体为例”,如果填写了打开文档的电脑没有安装的字体名称,将使用默认字体。能不能用中文,我没试过。 font_size: 19, //字号 color: '595959' //文字颜色 }); 上述例子外,还可以添加下划线、设置斜体、超链接、分页等;还可以编辑页眉和页脚、插入图片等。详见后续代码示例或officegen文档。 3. 插入图片 这里以插入小程序码为例,直接上代码。 要注意的是officegen似乎不支持以buffer形式插入图片,因此要先将图片保存。 //首先定义一个用于保存小程序码图片的函数 //save QR saveFile = function (filePath, fileData) { return new Promise((resolve, reject) => { const wstream = fs.createWriteStream(filePath); wstream.on('open', () => { const blockSize = 128; const nbBlocks = Math.ceil(fileData.length / (blockSize)); for (let i = 0; i < nbBlocks; i += 1) { const currentBlock = fileData.slice( blockSize * i, Math.min(blockSize * (i + 1), fileData.length), ); wstream.write(currentBlock); } wstream.end(); }); wstream.on('error', (err) => { reject(err); }); wstream.on('finish', () => { resolve(true); }); }); } //要获取小程序码,首先要修改云函数config.json文件中的云调用权限 { "permissions": { "openapi": [ "wxacode.getUnlimited" ] } } //在云函数main中获取小程序码 //https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.get.html const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/check/check', //小程序页面地址,必须是线上版本中存在的页面的完整地址 scene: '', //小程序码参数 width: 240, //小程序码的宽度(是个正方形) }) const QRcode = result.buffer await saveFile('/tmp/qr.jpg', QRcode); // 这里的fileData是Buffer类型,关于路径会在第三部分生成Word文件中解释。 //将图片插入到文档中 pObj = docx.createP() //创建段落 pObj.options.indentFirstLine = 440; //首行缩进 pObj.addImage('/tmp/qr.jpg', { //图片文件路径 cx: 140, //长度 cy: 140 //宽度 }); 三、生成Word文件 文档内容完成后,就可以生成文档了。officegen似乎只能生成文件,没有文件buffer的接口,而要上传到小程序·云开发的云存储中,只能使用Buffer或fs.ReadStream,怎么办呢?先把文件保存下来再读取呗。 首先提一下云函数运行环境 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/mechanism.html 云函数运行在云端 Linux 环境中,一个云函数在处理并发请求的时候会创建多个云函数实例,每个云函数实例之间相互隔离,没有公用的内存或硬盘空间。云函数实例的创建、管理、销毁等操作由平台自动完成。每个云函数实例都在 [代码]/tmp[代码] 目录下提供了一块 [代码]512MB[代码] 的临时磁盘空间用于处理单次云函数执行过程中的临时文件读写需求,需特别注意的是,这块临时磁盘空间在函数执行完毕后可能被销毁,不应依赖和假设在磁盘空间存储的临时文件会一直存在。如果需要持久化的存储,请使用云存储功能。因此,我们将文件保存在/tmp路径下,文件名随便起,这里我取为exampl.docx。生成文档的代码如下: // Let's generate the Word document into a file: let out = fs.createWriteStream('/tmp/example.docx') // Async call to generate the output file: docx.generate(out) 理论上来说,我们文档生成完毕后,通过fs.ReadFileStream读取文件调用cloud.uploadFile()即可上传到云存储 const fileStream = fs.createReadStream('/tmp/example.docx') return await cloud.uploadFile({ cloudPath: '/tmp/example.docx', fileContent: fileStream, }) 而在测试过程中我发现,云端测试时,云函数调用超时。而后使用本地调试查看问题出在何处。 云函数本地调试的方法不再赘述,看这里即可。https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/local-debug.html 通过本地调试,发现cloud.uplodaFile()的网络请求始终时挂起(pending)状态,没有传输数据。 [图片] 经过一天的调试,通过监听文件,发现officegen生成文件完成,执行了我们开头复制粘贴的生成文档后执行的docx.on("finalize",)函数,打印文档生成成功的日志后,仍有文件变动,也就是说,文件并没有生成完毕。这就导致了后续步骤的失败。 当时调试的界面我没有保存,就贴一下fs监听文件的代码吧。 let watcherObj = '/tmp/example.docx' //eventType 可以是 'rename' 或 'change'; 当改名或出现或消失的时候触发rename; recursive:是否监听到内层子目录,默认false; try { let myWatcher = fs.watch(watcherObj,{encoding:'utf8',recursive:true},(event,filename) => { if(event == 'change'){ console.log("触发change事件") } console.log(event) //encoding:文件名编码格式,buffer、默认:utf8等;filename有可能为空 if(filename){ console.log('filename: ' + filename) } }) //change 事件会触发多次 myWatcher.on('change',function(err,filename){ console.log(filename + '发生变化'); }); //50秒后 关闭监视 setTimeout(function(){ myWatcher.close() },5000); } catch (error) { console.log('文件不存在!!') } 为解决这一问题,我最先想到了await,结果发现await对officegen生成文档的接口并不起作用;最终我用了最原始的笨办法:用setTimeout等一会儿再读取文件,大佬们有更好的解决方案还请赐教。 return new Promise((resolve, reject) => { setTimeout(async function () { let data = fs.readFileSync('/tmp/example.docx'); let bufferData = new Buffer.from(data, 'base64'); console.log(bufferData); setTimeout(async function () { resolve(await cloud.uploadFile({ cloudPath: varpath, fileContent: bufferData, })); }, 1000); //等文件再读1秒 }, 6300); //等文件再写一会儿。根据自己的需求调试后确定等待时长,要预留出一定时间确保文档完全生成完毕。 }) //最终返回内容为文件云存储中的CloudID。 四、完整核心代码 const cloud = require('wx-server-sdk') const officegen = require('officegen'); const fs = require('fs'); const docx = officegen('docx'); cloud.init({ env: '这里填入你的云环境' }) // Officegen calling this function after finishing to generate the docx document: docx.on('finalize', async function (written) { console.log('Finish to create a Microsoft Word document.') }) // Officegen calling this function to report errors: docx.on('error', function (err) { console.log(err) }) //save QR saveFile = function (filePath, fileData) { return new Promise((resolve, reject) => { const wstream = fs.createWriteStream(filePath); wstream.on('open', () => { const blockSize = 128; const nbBlocks = Math.ceil(fileData.length / (blockSize)); for (let i = 0; i < nbBlocks; i += 1) { const currentBlock = fileData.slice( blockSize * i, Math.min(blockSize * (i + 1), fileData.length), ); wstream.write(currentBlock); } wstream.end(); }); wstream.on('error', (err) => { reject(err); }); wstream.on('finish', () => { resolve(true); }); }); } // 云函数入口函数 exports.main = async (event, context) => { var time = new Date() var filePath = 'exportVoluntaryData' var fileName = "zyzm" + Date.parse(new Date()) + '.docx' var varpath = filePath + '/' + fileName //get QRcode const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/check/check', scene: item._id, width: 240, }) const QRcode = result.buffer await saveFile('/tmp/qr.jpg', QRcode); // Add a Footer: var footer = docx.getFooter().createP(); footer.addText('XXXX证明_' + item._id, { font_size: 10 }); footer = docx.getFooter().createP(); footer.addText(time.toString(), { font_size: 10 }); //下方开始文档每一页的循环 for (var i in item.volunteerInfo) { //标题 let pObj = docx.createP({ align: 'center' }) pObj.addText('XXX证明', { bold: true,XXX font_face: 'KaiTi', font_size: 19, color: '595959' }); //此处省略了一些正文内容 pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('微信扫描下方小程序码,可核验此证明。', { font_face: 'FangSong', font_size: 12, color: '595959', italic: true, }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addImage('/tmp/qr.jpg', { cx: 140, cy: 140 }); pObj = docx.createP() pObj = docx.createP({ align: 'right' }) pObj.addText('落款', { font_face: 'FangSong', font_size: 15, color: '595959' }); if (i != ((item.volunteerInfo).length - 1)){ docx.putPageBreak() //分页 } } // Let's generate the Word document into a file: let out = fs.createWriteStream('/tmp/example.docx') // Async call to generate the output file: docx.generate(out) return new Promise((resolve, reject) => { setTimeout(async function () { let data = fs.readFileSync('/tmp/example.docx'); let bufferData = new Buffer.from(data, 'base64'); console.log(bufferData); setTimeout(async function () { resolve(await cloud.uploadFile({ cloudPath: varpath, fileContent: bufferData, })); }, 1000); }, 6300); }) } 本人非计算机相关专业本科生,且本文大部分内容为手打,难免会有差错和疏漏,还请各位指教。 希望本文对你有所帮助。 Soochow University. HaoChen. 2020年2月 ======= 2021-10-15更新 ======= 经过一段时间的使用,上述内容主要存在两点问题:(1)难以判断文件何时生成完毕;(2)连续调用生成文档时,若上一个云函数实例未被销毁,会出现文件内容重复和错乱的问题。 前一段时间进行了更新,因为工作学习忙碌,此次暂不做详解,代码如下。 入口文件index.js// 云函数入口文件 delete require.cache[require.resolve('officegen')]; const cloud = require('wx-server-sdk') var office = require('office.js'); //https://github.com/Ziv-Barber/officegen/blob/master/manual/docx/README.md cloud.init({ env: 'sudaxmt1900' }) const db = cloud.database() const _ = db.command // 云函数入口函数 exports.main = async (event, context) => { return await office.genWord(event); } office.jsconst cloud = require('wx-server-sdk') const fs = require('fs'); function delDir(path) { console.log("delete Dir") let files = []; if (fs.existsSync(path)) { files = fs.readdirSync(path); files.forEach((file, index) => { let curPath = path + "/" + file; if (fs.statSync(curPath).isDirectory()) { delDir(curPath); //递归删除文件夹 } else { fs.unlinkSync(curPath); //删除文件 } }); // fs.rmdirSync(path); // 删除文件夹自身 } } readDocx_fs = function (path) { return new Promise((resolve, reject) => { fs.readFile(path,(err,data)=>{ resolve(data); reject(err); }) }) } //save QR saveFile = function (filePath, fileData) { return new Promise((resolve, reject) => { const wstream = fs.createWriteStream(filePath); wstream.on('open', () => { const blockSize = 128; const nbBlocks = Math.ceil(fileData.length / (blockSize)); for (let i = 0; i < nbBlocks; i += 1) { const currentBlock = fileData.slice( blockSize * i, Math.min(blockSize * (i + 1), fileData.length), ); wstream.write(currentBlock); } wstream.end(); }); wstream.on('error', (err) => { reject(err); }); wstream.on('finish', () => { resolve(true); }); }); } exports.genWord = async (event) => { let officegen = require('officegen'); let fs = require('fs'); let docx = officegen('docx'); //ini delDir('/tmp') var item = event.item var filePath = 'exportVoluntaryData' var fileName = "21zyzm" + Date.parse(new Date()) + '.docx' var varpath = filePath + '/' + fileName //=========以下建构文档内容========== //get QRcode const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/check/check', scene: item.id, width: 140, }) const QRcode = result.buffer await saveFile('/tmp/qr.jpg', QRcode); // 这里的fileData是Buffer类型 timeBottom = time.getFullYear() + '年' + (time.getMonth() + 1) + '月' + time.getDate() + '日' for (var i in item.volunteerInfo) { let pObj = docx.createP({ align: 'center' }) pObj = docx.createP({ align: 'center' }) pObj.addText('志愿服务时间证明', { bold: true, font_face: 'KaiTi', font_size: 19, color: '595959' }); pObj = docx.createP() pObj = docx.createP({ align: 'justify' }) pObj.options.indentFirstLine = 440; if (item.volunteerInfo[i].academy && item.volunteerInfo[i].major && item.volunteerInfo[i].grade) { var txt = item.volunteerInfo[i].academy + ' ' + item.volunteerInfo[i].major + '专业 ' + item.volunteerInfo[i].grade + ' ' + item.volunteerInfo[i].name + ' 同学(学号 ' + item.volunteerInfo[i].idnum + '),于 ' + date + '参加 ' + item.title + ' 工作,志愿服务时间达到 ' + item.hours + ' 小时。' } else { var txt = item.volunteerInfo[i].name + ' 同学(学号 ' + item.volunteerInfo[i].idnum + '),于 ' + date + '参加 ' + item.title + ' 工作,志愿服务时间达到 ' + item.hours + ' 小时。' } pObj.addText(txt, { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('特此证明。', { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('证明人:' + event.tea_info.name + ' ' + event.tea_info.phone, { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP() pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('微信扫描下方小程序码,可核验此证明。核验信息与此证明一致时,此证明不加盖公章仍然有效;若不一致,则以加盖公章的证明为准。', { font_face: 'FangSong', font_size: 12, color: '595959', italic: true, }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addImage('/tmp/qr.jpg', { cx: 140, cy: 140 }); pObj = docx.createP() pObj = docx.createP() pObj = docx.createP({ align: 'right' }) pObj.addText('XXXXX', { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP({ align: 'right' }) pObj.addText(timeBottom, { font_face: 'FangSong', font_size: 15, color: '595959' }); // Add a Footer: pObj = docx.createP() pObj = docx.createP() pObj = docx.createP() pObj.addText('XXXXX证明_' + item._id, { font_face: 'FangSong', font_size: 10, color: '808080' }); pObj = docx.createP() pObj.addText(time.toString(), { font_face: 'FangSong', font_size: 10, color: '808080' }); if (i != ((item.volunteerInfo).length - 1)) { docx.putPageBreak() } } //=======================建构文档内容结束========================= // Let's generate the Word document into a file: let out = fs.createWriteStream('/tmp/' + fileName) return new Promise((resolve, reject) => { docx.generate(out); out.on('close', async function(){ console.log("文件已被关闭,总共写入字节", out.bytesWritten) // console.log('写入的文件路径是'+ out.path); var fileBuf = await readDocx_fs(out.path); var upd = await cloud.uploadFile({ cloudPath: varpath, fileContent: fileBuf, }); console.log(docx) resolve({ event, upd, size: Math.floor(100*out.bytesWritten/1024)/100 + "KB" }) }); out.on('error', (err) => { console.error(err); reject({ errMsg: err }) }); }) }
2021-10-15 - 微信开发者工具下载的 sourcemaps 怎么用。
什么是 Sourcemaps uglifyjs、bable 等工具会对 源代码 进行编译处理生成编译后的代码(下称目标代码),而 sourcemaps 就是保留了目标代码在源代码中的 位置信息 --------- 大神分割线 --------- 如何解读 Sourcemaps Sourcemaps 是一个 json [代码]{ "version": 3, "sources": ["a.js", "b.js"], // 源文件列表,这个表示是由 a.js 和 b.js 合并生成 "names": ["myFn", "test"], // 如果开启了变量名混淆,这里会保留变量名在源文件中名字信息 "sourcesContent: [], // 可选项,保存源码信息,顺序与 sources 字段对应,chrome 的 sources 面板中源码使用了这个字段的内容进行展示 "sourceRoot": "", // 源文件所在的目录信息 "file": "dist.js", // 可选,编译后的文件名 "mappings": "" // 这个是重点,是目标代码和源文件的位置的映射关系 } [代码] mappings 目标文件"行"的信息 mappings 是使用 ; 分隔的,每个部分对应目标代码的行 如: “;AAAA;AAAA,BBBB;;” 本例子目标文件有 4 行 第 0 行和第 3 行没有源文件对应信息,所以这两行是编译过程中加入的代码 目标文件的"列"信息 如: “AAAA,CAEA,CAEA;” ‘,’ 表示行内的位置信息分隔符 本例表示目标文件的这一行有三个有效的位置信息。 位置信息的第一位表示目标文件的列的 偏移 信息 本例中,表示列的信息是 ‘A’、‘C’、‘C’,对应的数字为 0、+1、+1,(vlq 编码,在线编解码工具) 注意,这个是偏移信息; 列数从 0 开始,依次累加偏移值可以算出当前的位置信息对应的真正的列 所以本例中表示的是目标文件的第 n 行中的第 0 列,第 1 列,第 2 列(没错是第 2 列) 源文件的信息 如:‘AAAA;ACAA;ADAA;’ 位置信息的第二位表示源文件的信息,本例子中是 ‘A’、‘C’、‘D’,对应数字是 0、+1、-1 如果 sourcemaps 中的 sources 字段只有一个文件的话,那么位置信息中第二位一直是 A(不需要偏移) 假设 sourcemaps 中 sources: [‘a.js’, ‘b.js’] 本例的意思是 AAAA: 目标文件第 0 行第 0 列 对应 第 0 个文件 a.js ACAA; 目标文件第 1 行第 0 列 对应 第 1 个文件 b.js ADAA; 目标文件第 2 行第 0 列 对了 第 0 个文件 a.js (偏移是 -1 又回到了 a.js) 源文件的行信息 位置信息的第三位表示源文件中的行的信息, 理解了位置偏移的概念,我们很容易理解 如:‘AACA,CACA;AACA;‘ 那么 AACA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 1 行 CACA: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 1+1 行 AACA:目标文件的第 1 行第 0 列 对应 第 0 个文件的第 1 行 (注意:’;’ 后的行列偏移信息归 0) 源文件中的列信息 位置信息的第四位表示源文件中的列的信息 如:'AAAA,CAAC;' 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列 CAAC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列 位置信息的第五位 第五位表示变量的偏移,对应 sourcemaps 中的 names 字段,表示目标文件中的变量名对应域源文件中的变量 如:’AAAA,CAACC;AAAAD;' sourcemaps 中 names 字段是 [‘a’, ‘b’] 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,没有变量的信息 CAACC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列,有变量信息,变量在源文件中是 ‘b’ (0+1=1) AAAAD: 目标文件的第 1 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,有变量信息,变量在源文件中是 ‘a’ (1-1=0) --------- 大神分割线 --------- 怎么使用 Sourcemaps Q: 线上小程序报错,我怎么通过 sourcemaps 还原到源代码中? A: 如报错 appservice.js 1:15000, 表示目标文件第一行 第 15000 列位置报错。根据上文介绍的,通过 mappings 字段算。 Q: 不会。 A: 如果你会写代码的话,参考下边 [代码]import fs = require('fs') import {SourceMapConsumer} from 'source-map' async function originalPositionFor(line, column) { const sourceMapFilePath = '如果你不真的替换的成 sourcemaps 在硬盘中的位置,那你还是放弃自己写代码吧。 ' const sourceMapConsumer = await new SourceMapConsumer(JSON.parse(fs.readFileSync(sourceMapFilePath, 'utf8'))) return sourceMapConsumer.originalPositionFor({ line, column, }) } originalPositionFor(出错的行,出错的列) [代码] Q: 不会写代码 A: 下载最新版的开发者工具,菜单-设置-拓展设置-调试器插件 [图片] [图片] Q: 为啥都是 null? A: 每个小程序版本都应该对应一个sourcemap文件。 运营中心那里下载的 sourcemap 是对应线上最新的小程序版本。但运营中心的报错集合了多个小程序版本。拿旧小程序版本的报错信息,和最新版本的 sourcemap,是匹配不出的。开发者工具和ci 上传的时候,会提示下载对应版本的 sourcemap 信息,可以自助保存。 [图片] Q: 怎么确定有没有版本对应上 A: 下载的 sourcemap 中有个 wx 字段,标明了该 sourcemap 文件对应小程序版本号。 [图片] [图片] 前提 1.确保发生错误的小程序版本和下载回来的 sourcemap 版本是一致的。 a. 下载 sourceMap 文件,可在 mp 后台或开发者工具上传成功弹窗下载 2.确保 map 文件和发生错误的 js 文件是对应的。sourcemap 的目录和文件说明 a. APP 是主包,FULL 是整包(仅在不支持分包的低版本微信中使用),其他目录是分包 b. 每个分包下都有对应的 app-service.js.map 文件。 c. 如果是使用了按需注入特性(app.json中配置了lazyCodeLoading),那么每个分包下还会有 appservice.app.js.map(对应分包下非页面的js),和所有页面的 xxx.js.map 以上事情都确保正确之后,还是出现行列号匹配不出来的情况。那就需要进一步排查。 线上运行的小程序 sourcemap 文件是怎么生成的? 处理流程:源码 [ a.js a.js.map b.js b.js.map ] -> 开发者工具(JS转 ES5,压缩)-> 微信后台(合并 js 文件)[ appservice.app.js appservice.app.js.map]。 注意:如果源码在交给工具之前是经过了 webpack 等打包工具的处理,那源码这里需要有 map 文件。否则不需要存在 map 文件。 可以看出,map 文件经过三个步骤的处理,每个步骤都有可能导致出错,因此开发者需要先排查,是否是前两个步骤出错导致的 map 文件失效的。 如何排查前两个步骤产生的 map 文件是否有问题。 1.排查 a.js.map 文件是否有问题。 a. 可以在 a.js 的代码中写一下 throw new Error(‘test sourcemap’)。 b. 使用了 webpack 的情况下,要构建为生产环境的版本。 c. 在开发者工具模拟器中运行对应的页面,看看控制台中的报错,错误行列号是否能正常映射到源文件。 2.排查 开发者工具(JS转 ES5,压缩)步骤是否有问题。 在排查完第一步的基础上,点击预览,用微信上扫码预览,并打开调试 vConsole 功能,检查 vConsole 中是否有报错信息,检查报错信息中的行列号是否能正常映射到源文件。 如何排查 微信后台(合并 js 文件)是否有问题。 a. 一定要先排查完前两个步骤再来排查这一步,一般情况下,这一步是不会出错的。 b. 如果有问题,也只会导致 map 文件中的行号信息出现偏移。比如 Error 信息中显示报错地址是 100: 200,行号是 100。那么你可能直接用 100: 200 在 map 文件中搜索不出信息,但是如果 用 150: 200 就可以搜索出来,说明行号偏移了 50。那其他报错也可以偏移 50 后再进行搜索就找到结果。 c. 怎么排查偏移了多少?可以结合 error.message 的内容,初步判断大概错误的内容是什么。把对应的 map 文件放到这个网站上 source-map-visualization 进行搜索,找出哪些相同列号的地方。再结合 error.message 的内容进行判断。 d. 如果排查到是这一步导致的问题,请在社区上联系我们,我们会在后续版本进行修复。 依旧排查不出原因? 先整理一下按照上述步骤排查的结论,再在社区上联系我们协助
2023-02-10 - Mac小程序开发说明
Mac微信2.4.0以上版版本中,支持打开聊天中分享的小程序,开发者可下载安装Mac微信版内测版本进行体验和适配。 Mac微信内测版:点击下载 兼容性:系统要求macOS 10.12或更高版本 1、开发工具支持 运行环境要求下载并安装最新开发版开发者工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly.html 小程序预览微信开发者工具菜单栏点击 设置->通用设置,在自动预览部分勾选“启动 MAC 端自动预览”。 使用自动预览功能,点击 预览->自动预览->编译并预览,成功的话将在微信MAC版上自动拉起小程序。 [图片] 小程序真机调试微信开发者工具菜单栏点击 设置->通用设置,在自动预览部分勾选“启动 MAC 端真机调试”。 使用真机调试功能,点击 真机调试->自动真机小时->编译并自动调试,成功的话将在微信MAC版上自动拉起小程序。 [图片] 2、开发者适配 SystemInfowx.getSystemInfo接口中返回的参数和手机返回的定义区别 [图片] 同时,Mac小程序还会根据屏幕的大小自动选择默认的窗口大小。提供的窗口大小从小到大依次为: [图片] web-view组件web-view组件中打开的域名请支持Mac浏览器的UserAgent。 支付小程序在使用wx.requestPayment时,将会拉起二维码让用户使用手机支付 3、常见问题 Q:小程序如何判断是Mac平台? A:通过 getSystemInfo 官方接口(platform 是 mac)/ 通过 UserAgent(Mac UserAgent 包含 MiniProgramEnv/Mac) Q:Mac小程序如何支持横屏? A:"resizable": true 可使小程序在PC上横屏窗口展示,体验可参考腾讯文档小程序。 Q:横屏模式下小程序默认的窗口大小为 A:webview 嵌入的页面在Mac上不能操作或者显示或操作异常? 检查一下页面是否没响应鼠标事件检查是否是UserAgent没有支持Q:页面布局为何出现错乱? A:检查一下是否使用屏幕尺寸来计算布局,Mac 上屏幕尺寸比窗口尺寸大,应该使用窗口尺寸来计算。 Q:为什么有的功能无法使用? A:Mac微信小程序暂不支持地图、蓝牙、卡包、以及硬件相关的功能。后续功能敬请期待。
2021-01-25