- 如何用SVG代码排版公众号文章
[视频] 案例代码 <section style="width: 345px;margin: 0px auto;font-size: 0px;line-height: 0;"> <section style="overflow: hidden;"> <section style="height: 0px;"> <section style="padding-top: 500px;margin: 0px auto;font-size: 0px;line-height: 0;"> <section style="width: 345px;height: 2500px;background-position: 0px 0px;background-repeat: no-repeat;background-size: 100%;background-image: url(https://mmbiz.qpic.cn/mmbiz_jpg/JpraGu7eGqS3XzaoEexZuQycqusR5zvgmp9rnRyDoIBz1iag3bLZrKasw1m5iaDTlvrY1vpJocku4kwK6IvEgRHA/0?wx_fmt=jpeg);"></section> </section> </section> <section style="background-position: 0px 0px;background-size: 100% 500px;background-repeat: no-repeat;background-image: url(https://mmbiz.qpic.cn/mmbiz_jpg/JpraGu7eGqS3XzaoEexZuQycqusR5zvgLHQfib9uS78vUI4rG4RZvJOAJibLeykkA1xaNJ32Ku3ZgMyeu283LIbQ/0?wx_fmt=jpeg);"> <svg height="500px" style="background-size: 100% 500px; background-repeat: no-repeat;background-position: center top;background-color: rgb(0, 0, 0);background-image: url(https://mmbiz.qpic.cn/mmbiz_jpg/JpraGu7eGqS3XzaoEexZuQycqusR5zvg1ymWUDTGBkHF6ia3qCm7EwcuYJb2lkDsFuV89RQS9lPaiaLop1z7RZ6g/0?wx_fmt=jpeg);width: 100%;height: 500px;" viewBox="0 0 345 500" xmlns="http://www.w3.org/2000/svg" width="1"> <animate attributeName="width" style="box-sizing: border-box;" fill="freeze" to="0" from="100" duration="0.01" begin="click+0.6s"></animate> <animate attributeName="opacity" style="box-sizing: border-box;" fill="freeze" dur="0.01s" begin="click+0.6s" from="1" to="0"></animate> <animate attributeName="height" fill="freeze" restart="never" calcMode="spline" keySplines="0.42 0 0.58 1.0; 0.42 0 0.58 1.0; 0.42 0 0.58 1.0" values="500; 0; 3000; 3000" dur="6s" begin="click+0.1s" keyTimes="0; 0.1; 0.5; 1"></animate> </svg> </section> </section> </section>
2020-07-01 - 社区每周 | 规范使用wx.login接口获取登录凭证的通知、上周问题反馈(11.28-12.02)
各位微信开发者: 以下是关于规范使用小程序 wx.login 接口获取登录凭证 (code) 的通知以及上周我们在社区收到的问题反馈的处理进度,希望与大家一同打造更好的小程序生态! 关于规范使用小程序 wx.login 接口获取登录凭证 (code) 的通知 根据平台设计,开发者通过 wx.login 接口获取 code 后需要调用 服务端接口 auth.code2Session ,将 code 换取 openid、unionid、session_key 等信息。但目前存在部分开发者不规范使用 code,通过在小程序中获取的 code 换取 网页授权 access_token。 为规范接口使用,自 2023 年 1 月 8 日 14 时起,开发者通过小程序中获取的 code 将无法换取网页授权 access_token ,返回错误码为 40242 。请存在不规范使用的开发者关注并及时调整,避免对业务造成影响。 上周问题反馈和处理进度(11.28-12.02) 已修复的问题iOS input 组件手动聚焦时,光标结束位置不在文字最右边的问题 查看详情 使用 uniapp 开发小程序时,运行结果显示页面无渲染的问题 查看详情 微信客服登录失败的问题 查看详情 创建公众号视频合集但找不到已发表视频的问题 查看详情 微信团队 2022.12.09
2022-12-09 - 微信小程序加了体验和开发权限,仍提示无体验权限
小程序后台已经添加此用户为开发者,但进入小程序后仍然提示无体验权限(注:小程序为企业微信内打开,已做关联) [图片] [图片]
2021-01-13 - 微信公众号网页用户授权登录过一次,怎样下次访问不需要再次授权登录?
微信公众号网页用户授权登录过一次,怎样下次访问不需要再次授权登录? 如题,用户访问网页,弹出的授权登录页面已确认同意,为何过了两天又弹出授权了,不授权不能访问页面。 请问哪位知道怎么解决此问题? 感谢
2019-09-11 - 小程序登录、用户信息相关接口调整说明
公告更新时间:2021年04月15日考虑到近期开发者对小程序登录、用户信息相关接口调整的相关反馈,为优化开发者调整接口的体验,回收wx.getUserInfo接口可获取用户授权的个人信息能力的截止时间由2021年4月13日调整至2021年4月28日24时。为优化用户的使用体验,平台将进行以下调整: 2021年2月23日起,若小程序已在微信开放平台进行绑定,则通过wx.login接口获取的登录凭证可直接换取unionID2021年4月28日24时后发布的小程序新版本,无法通过wx.getUserInfo与<button open-type="getUserInfo"/>获取用户个人信息(头像、昵称、性别与地区),将直接获取匿名数据(包括userInfo与encryptedData中的用户个人信息),获取加密后的openID与unionID数据的能力不做调整。此前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。新增getUserProfile接口(基础库2.10.4版本开始支持),可获取用户头像、昵称、性别及地区信息,开发者每次通过该接口获取用户个人信息均需用户确认。具体接口文档:《getUserProfile接口文档》由于getUserProfile接口从2.10.4版本基础库开始支持(覆盖微信7.0.9以上版本),考虑到开发者在低版本中有获取用户头像昵称的诉求,对于未支持getUserProfile的情况下,开发者可继续使用getUserInfo能力。开发者可参考getUserProfile接口文档中的示例代码进行适配。请使用了wx.getUserInfo接口或<button open-type="getUserInfo"/>的开发者尽快适配。开发者工具1.05.2103022版本开始支持getUserProfile接口调试,开发者可下载该版本进行改造。 小游戏不受本次调整影响。 一、调整背景很多开发者在打开小程序时就通过组件方式唤起getUserInfo弹窗,如果用户点击拒绝,无法使用小程序,这种做法打断了用户正常使用小程序的流程,同时也不利于小程序获取新用户。 二、调整说明通过wx.login接口获取的登录凭证可直接换取unionID 若小程序已在微信开放平台进行绑定,原wx.login接口获取的登录凭证若需换取unionID需满足以下条件: 如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用2月23日后,开发者调用wx.login获取的登录凭证可以直接换取unionID,无需满足以上条件。 回收wx.getUserInfo接口可获取用户个人信息能力 4月28日24时后发布的新版本小程序,开发者调用wx.getUserInfo或<button open-type="getUserInfo"/>将不再弹出弹窗,直接返回匿名的用户个人信息,获取加密后的openID、unionID数据的能力不做调整。 具体变化如下表: [图片] 即wx.getUserInfo接口的返回参数不变,但开发者获取的userInfo为匿名信息。 [图片] 此外,针对scope.userInfo将做如下调整: 若开发者调用wx.authorize接口请求scope.userInfo授权,用户侧不会触发授权弹框,直接返回授权成功若开发者调用wx.getSetting接口请求用户的授权状态,会直接读取到scope.userInfo为true新增getUserProfile接口 若开发者需要获取用户的个人信息(头像、昵称、性别与地区),可以通过wx.getUserProfile接口进行获取,该接口从基础库2.10.4版本开始支持,该接口只返回用户个人信息,不包含用户身份标识符。该接口中desc属性(声明获取用户个人信息后的用途)后续会展示在弹窗中,请开发者谨慎填写。开发者每次通过该接口获取用户个人信息均需用户确认,请开发者妥善保管用户快速填写的头像昵称,避免重复弹窗。 插件用户信息功能页 插件申请获取用户头像昵称与用户身份标识符仍保留功能页的形式,不作调整。用户在用户信息功能页中授权之后,插件就可以直接调用 wx.login 和 wx.getUserInfo 。 三、最佳实践调整后,开发者如需获取用户身份标识符只需要调用wx.login接口即可。 开发者若需要在界面中展示用户的头像昵称信息,可以通过<open-data>组件进行渲染,该组件无需用户确认,可以在界面中直接展示。 在部分场景(如社交类小程序)中,开发者需要在获取用户的头像昵称信息,可调用wx.getUserProfile接口,开发者每次通过该接口均需用户确认,请开发者妥善处理调用接口的时机,避免过度弹出弹窗骚扰用户。 微信团队 2021年4月15日
2021-04-15 - vue 中调用微信分享wx.onMenuShareAppMessage之后,导致其他页面都可以分享?
我在router.beforEach路由守卫中判断,to.name='home'时(当前为首页)进行调用微信分享,之后我切换到classlist(产品详情页面)路由,没有调用微信分享接口,但是还是可以进行分享,而且分享的还是home页面调用的分享内容,在刷新一下页面之后classlist(产品详情)页面就不能分享了。 自己感觉问题原因:由于vue是单页面应用,切换路由并不是跳转页面,而只是dom进行了重新的渲染,home页面调用的微信分享,在classlist页面也能使用,刷新页面的话router.beforEach会判断是否是home页面,如果不是就不进行加载微信分享。 求解决方案 首页: [图片] 产品列表页: [图片]
2020-06-19 - 微信公众号菜单点击进入链接,ios页面下方出现了两个箭头
安卓机上没问题,苹果手机使用浏览器打开没问题,一旦使用微信公众号菜单跳转的,页面下方就会出现两个导航性质的箭头,如图: [图片] 请问如何让这两个箭头不显示?
2018-09-30 - 通过授权登录介绍小程序原生开发如何引入async/await、状态管理等工具
登陆和授权是小程序开发会遇到的第一个问题,这里把相关业务逻辑、工具代码抽取出来,展示我们如何引入的一些包使得原生微信小程序内也可以使用 async/await、fetch、localStorage、状态管理、GraphQL 等等特性,希望对大家有所帮助。 前端 目录结构 [代码]├── app.js ├── app.json ├── app.wxss ├── common │ └── api │ └── index.js ├── config.js ├── pages │ └── index │ ├── api │ │ └── index.js │ ├── img │ │ ├── btn.png │ │ └── bg.jpg │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── project.config.json ├── store │ ├── action.js │ └── index.js ├── utils │ └── index.js └── vendor ├── event-emitter.js ├── fetch.js ├── fetchql.js ├── http.js ├── promisify.js ├── regenerator.js ├── storage.js └── store.js [代码] 业务代码 app.js [代码]import store from './store/index' const { loginInfo } = store.state App({ store, onLaunch() { // 打开小程序即登陆,无需用户授权可获得 openID if(!loginInfo) store.dispatch('login') }, }) [代码] store/index.js [代码]import Store from '../vendor/store' import localStorage from '../vendor/storage' import actions from './action' const loginInfo = localStorage.getItem('loginInfo') export default new Store({ state: { // 在全局状态中维护登陆信息 loginInfo, }, actions, }) [代码] store/action.js [代码]import regeneratorRuntime from '../vendor/regenerator'; import wx from '../vendor/promisify'; import localStorage from '../vendor/storage' import api from '../common/api/index'; export default { async login({ state }, payload) { const { code } = await wx.loginAsync(); const { authSetting } = await wx.getSettingAsync() // 如果用户曾授权,直接可以拿到 encryptedData const { encryptedData, iv } = authSetting['scope.userInfo'] ? await wx.getUserInfoAsync({ withCredentials: true }) : {}; // 如果用户未曾授权,也可以拿到 openID const { token, userInfo } = await api.login({ code, encryptedData, iv }); // 为接口统一配置 Token getApp().gql.requestObject.headers['Authorization'] = `Bearer ${token}`; // 本地缓存登陆信息 localStorage.setItem('loginInfo', { token, userInfo } ) return { loginInfo: { token, userInfo } } } } [代码] common/api/index.js [代码]import regeneratorRuntime from '../../vendor/regenerator.js' export default { /** * 登录接口 * 如果只有 code,只返回 token,如果有 encryptedData, iv,同时返回用户的昵称和头像 * @param {*} param0 */ async login({ code, encryptedData, iv }) { const query = `query login($code: String!, $encryptedData: String, $iv: String){ login(code:$code, encryptedData:$encryptedData, iv:$iv, appid:$appid){ token userInfo { nickName avatarUrl } } }` const { login: { token, userInfo } } = await getApp().query({ query, variables: { code, encryptedData, iv } }) return { token, userInfo } }, } [代码] pages/index/index.js [代码]import regeneratorRuntime from '../../vendor/regenerator.js' const app = getApp() Page({ data: {}, onLoad(options) { // 将用户登录信息注入到当前页面的 data 中,并且当数据在全局范围内被更新时,都会自动刷新本页面 app.store.mapState(['loginInfo'], this) }, async login({ detail: { errMsg } }) { if (errMsg === 'getUserInfo:fail auth deny') return app.store.dispatch('login') // 继续处理业务 }, }) [代码] pages/index/index.wxml [代码]<view class="container"> <form report-submit="true" bindsubmit="saveFormId"> <button form-type="submit" open-type="getUserInfo" bindgetuserinfo="login">登录</button> </form> </view> [代码] 工具代码 事件处理 vendor/event-emitter.js [代码]const id_Identifier = '__id__'; function randomId() { return Math.random().toString(36).substr(2, 16); } function findIndexById(id) { return this.findIndex(item => item[id_Identifier] === id); } export default class EventEmitter { constructor() { this.events = {} } /** * listen on a event * @param event * @param listener */ on(event, listener) { let { events } = this; let container = events[event] || []; let id = randomId(); let index; listener[id_Identifier] = id; container.push(listener); return () => { index = findIndexById.call(container, id); index >= 0 && container.splice(index, 1); } }; /** * remove all listen of an event * @param event */ off (event) { this.events[event] = []; }; /** * clear all event listen */ clear () { this.events = {}; }; /** * listen on a event once, if it been trigger, it will cancel the listner * @param event * @param listener */ once (event, listener) { let { events } = this; let container = events[event] || []; let id = randomId(); let index; let callback = () => { index = findIndexById.call(container, id); index >= 0 && container.splice(index, 1); listener.apply(this, arguments); }; callback[id_Identifier] = id; container.push(callback); }; /** * emit event */ emit () { const { events } = this; const argv = [].slice.call(arguments); const event = argv.shift(); ((events['*'] || []).concat(events[event] || [])).map(listener => self.emitting(event, argv, listener)); }; /** * define emitting * @param event * @param dataArray * @param listener */ emitting (event, dataArray, listener) { listener.apply(this, dataArray); }; } [代码] 封装 wx.request() 接口 vendor/http.js [代码]import EventEmitter from './event-emitter.js'; const DEFAULT_CONFIG = { maxConcurrent: 10, timeout: 0, header: {}, dataType: 'json' }; class Http extends EventEmitter { constructor(config = DEFAULT_CONFIG) { super(); this.config = config; this.ctx = wx; this.queue = []; this.runningTask = 0; this.maxConcurrent = DEFAULT_CONFIG.maxConcurrent; this.maxConcurrent = config.maxConcurrent; this.requestInterceptor = () => true; this.responseInterceptor = () => true; } create(config = DEFAULT_CONFIG) { return new Http(config); } next() { const queue = this.queue; if (!queue.length || this.runningTask >= this.maxConcurrent) return; const entity = queue.shift(); const config = entity.config; const { requestInterceptor, responseInterceptor } = this; if (requestInterceptor.call(this, config) !== true) { let response = { data: null, errMsg: `Request Interceptor: Request can\'t pass the Interceptor`, statusCode: 0, header: {} }; entity.reject(response); return; } this.emit('request', config); this.runningTask = this.runningTask + 1; let timer = null; let aborted = false; let finished = false; const callBack = { success: (res) => { if (aborted) return; finished = true; timer && clearTimeout(timer); entity.response = res; this.emit('success', config, res); responseInterceptor.call(this, config, res) !== true ? entity.reject(res) : entity.resolve(res); }, fail: (res) => { if (aborted) return; finished = true; timer && clearTimeout(timer); entity.response = res; this.emit('fail', config, res); responseInterceptor.call(this, config, res) !== true ? entity.reject(res) : entity.resolve(res); }, complete: () => { if (aborted) return; this.emit('complete', config, entity.response); this.next(); this.runningTask = this.runningTask - 1; } }; const requestConfig = Object.assign(config, callBack); const task = this.ctx.request(requestConfig); if (this.config.timeout > 0) { timer = setTimeout(() => { if (!finished) { aborted = true; task && task.abort(); this.next(); } }, this.config.timeout); } } request(method, url, data, header, dataType = 'json') { const config = { method, url, data, header: { ...header, ...this.config.header }, dataType: dataType || this.config.dataType }; return new Promise((resolve, reject) => { const entity = { config, resolve, reject, response: null }; this.queue.push(entity); this.next(); }); } head(url, data, header, dataType) { return this.request('HEAD', url, data, header, dataType); } options(url, data, header, dataType) { return this.request('OPTIONS', url, data, header, dataType); } get(url, data, header, dataType) { return this.request('GET', url, data, header, dataType); } post(url, data, header, dataType) { return this.request('POST', url, data, header, dataType); } put(url, data, header, dataType) { return this.request('PUT', url, data, header, dataType); } ['delete'](url, data, header, dataType) { return this.request('DELETE', url, data, header, dataType); } trace(url, data, header, dataType) { return this.request('TRACE', url, data, header, dataType); } connect(url, data, header, dataType) { return this.request('CONNECT', url, data, header, dataType); } setRequestInterceptor(interceptor) { this.requestInterceptor = interceptor; return this; } setResponseInterceptor(interceptor) { this.responseInterceptor = interceptor; return this; } clean() { this.queue = []; } } export default new Http(); [代码] 兼容 fetch 标准 vendor/fetch.js [代码]import http from './http'; const httpClient = http.create({ maxConcurrent: 10, timeout: 0, header: {}, dataType: 'json' }); function generateResponse(res) { let header = res.header || {}; let config = res.config || {}; return { ok: ((res.statusCode / 200) | 0) === 1, // 200-299 status: res.statusCode, statusText: res.errMsg, url: config.url, clone: () => generateResponse(res), text: () => Promise.resolve( typeof res.data === 'string' ? res.data : JSON.stringify(res.data) ), json: () => { if (typeof res.data === 'object') return Promise.resolve(res.data); let json = {}; try { json = JSON.parse(res.data); } catch (err) { console.error(err); } return json; }, blob: () => Promise.resolve(new Blob([res.data])), headers: { keys: () => Object.keys(header), entries: () => { let all = []; for (let key in header) { if (header.hasOwnProperty(key)) { all.push([key, header[key]]); } } return all; }, get: n => header[n.toLowerCase()], has: n => n.toLowerCase() in header } }; } export default (typeof fetch === 'function' ? fetch.bind() : function(url, options) { options = options || {}; return httpClient .request(options.method || 'get', url, options.body, options.headers) .then(res => Promise.resolve(generateResponse(res))) .catch(res => Promise.reject(generateResponse(res))); }); [代码] GraphQL客户端 vendor/fetchql.js [代码]import fetch from './fetch'; // https://github.com/gucheen/fetchql /** Class to realize fetch interceptors */ class FetchInterceptor { constructor() { this.interceptors = []; /* global fetch */ this.fetch = (...args) => this.interceptorWrapper(fetch, ...args); } /** * add new interceptors * @param {(Object|Object[])} interceptors */ addInterceptors(interceptors) { const removeIndex = []; if (Array.isArray(interceptors)) { interceptors.map((interceptor) => { removeIndex.push(this.interceptors.length); return this.interceptors.push(interceptor); }); } else if (interceptors instanceof Object) { removeIndex.push(this.interceptors.length); this.interceptors.push(interceptors); } this.updateInterceptors(); return () => this.removeInterceptors(removeIndex); } /** * remove interceptors by indexes * @param {number[]} indexes */ removeInterceptors(indexes) { if (Array.isArray(indexes)) { indexes.map(index => this.interceptors.splice(index, 1)); this.updateInterceptors(); } } /** * @private */ updateInterceptors() { this.reversedInterceptors = this.interceptors .reduce((array, interceptor) => [interceptor].concat(array), []); } /** * remove all interceptors */ clearInterceptors() { this.interceptors = []; this.updateInterceptors(); } /** * @private */ interceptorWrapper(fetch, ...args) { let promise = Promise.resolve(args); this.reversedInterceptors.forEach(({ request, requestError }) => { if (request || requestError) { promise = promise.then(() => request(...args), requestError); } }); promise = promise.then(() => fetch(...args)); this.reversedInterceptors.forEach(({ response, responseError }) => { if (response || responseError) { promise = promise.then(response, responseError); } }); return promise; } } /** * GraphQL client with fetch api. * @extends FetchInterceptor */ class FetchQL extends FetchInterceptor { /** * Create a FetchQL instance. * @param {Object} options * @param {String} options.url - the server address of GraphQL * @param {(Object|Object[])=} options.interceptors * @param {{}=} options.headers - request headers * @param {FetchQL~requestQueueChanged=} options.onStart - callback function of a new request queue * @param {FetchQL~requestQueueChanged=} options.onEnd - callback function of request queue finished * @param {Boolean=} options.omitEmptyVariables - remove null props(null or '') from the variables * @param {Object=} options.requestOptions - addition options to fetch request(refer to fetch api) */ constructor({ url, interceptors, headers, onStart, onEnd, omitEmptyVariables = false, requestOptions = {}, }) { super(); this.requestObject = Object.assign( {}, { method: 'POST', headers: Object.assign({}, { Accept: 'application/json', 'Content-Type': 'application/json', }, headers), credentials: 'same-origin', }, requestOptions, ); this.url = url; this.omitEmptyVariables = omitEmptyVariables; // marker for request queue this.requestQueueLength = 0; // using for caching enums' type this.EnumMap = {}; this.callbacks = { onStart, onEnd, }; this.addInterceptors(interceptors); } /** * operate a query * @param {Object} options * @param {String} options.operationName * @param {String} options.query * @param {Object=} options.variables * @param {Object=} options.opts - addition options(will not be passed to server) * @param {Boolean=} options.opts.omitEmptyVariables - remove null props(null or '') from the variables * @param {Object=} options.requestOptions - addition options to fetch request(refer to fetch api) * @returns {Promise} * @memberOf FetchQL */ query({ operationName, query, variables, opts = {}, requestOptions = {}, }) { const options = Object.assign({}, this.requestObject, requestOptions); let vars; if (this.omitEmptyVariables || opts.omitEmptyVariables) { vars = this.doOmitEmptyVariables(variables); } else { vars = variables; } const body = { operationName, query, variables: vars, }; options.body = JSON.stringify(body); this.onStart(); return this.fetch(this.url, options) .then((res) => { if (res.ok) { return res.json(); } // return an custom error stack if request error return { errors: [{ message: res.statusText, stack: res, }], }; }) .then(({ data, errors }) => ( new Promise((resolve, reject) => { this.onEnd(); // if data in response is 'null' if (!data) { return reject(errors || [{}]); } // if all properties of data is 'null' const allDataKeyEmpty = Object.keys(data).every(key => !data[key]); if (allDataKeyEmpty) { return reject(errors); } return resolve({ data, errors }); }) )); } /** * get current server address * @returns {String} * @memberOf FetchQL */ getUrl() { return this.url; } /** * setting a new server address * @param {String} url * @memberOf FetchQL */ setUrl(url) { this.url = url; } /** * get information of enum type * @param {String[]} EnumNameList - array of enums' name * @returns {Promise} * @memberOf FetchQL */ getEnumTypes(EnumNameList) { const fullData = {}; // check cache status const unCachedEnumList = EnumNameList.filter((element) => { if (this.EnumMap[element]) { // enum has been cached fullData[element] = this.EnumMap[element]; return false; } return true; }); // immediately return the data if all enums have been cached if (!unCachedEnumList.length) { return new Promise((resolve) => { resolve({ data: fullData }); }); } // build query string for uncached enums const EnumTypeQuery = unCachedEnumList.map(type => ( `${type}: __type(name: "${type}") { ...EnumFragment }` )); const query = ` query { ${EnumTypeQuery.join('\n')} } fragment EnumFragment on __Type { kind description enumValues { name description } }`; const options = Object.assign({}, this.requestObject); options.body = JSON.stringify({ query }); this.onStart(); return this.fetch(this.url, options) .then((res) => { if (res.ok) { return res.json(); } // return an custom error stack if request error return { errors: [{ message: res.statusText, stack: res, }], }; }) .then(({ data, errors }) => ( new Promise((resolve, reject) => { this.onEnd(); // if data in response is 'null' and have any errors if (!data) { return reject(errors || [{ message: 'Do not get any data.' }]); } // if all properties of data is 'null' const allDataKeyEmpty = Object.keys(data).every(key => !data[key]); if (allDataKeyEmpty && errors && errors.length) { return reject(errors); } // merge enums' data const passData = Object.assign(fullData, data); // cache new enums' data Object.keys(data).map((key) => { this.EnumMap[key] = data[key]; return key; }); return resolve({ data: passData, errors }); }) )); } /** * calling on a request starting * if the request belong to a new queue, call the 'onStart' method */ onStart() { this.requestQueueLength++; if (this.requestQueueLength > 1 || !this.callbacks.onStart) { return; } this.callbacks.onStart(this.requestQueueLength); } /** * calling on a request ending * if current queue finished, calling the 'onEnd' method */ onEnd() { this.requestQueueLength--; if (this.requestQueueLength || !this.callbacks.onEnd) { return; } this.callbacks.onEnd(this.requestQueueLength); } /** * Callback of requests queue changes.(e.g. new queue or queue finished) * @callback FetchQL~requestQueueChanged * @param {number} queueLength - length of current request queue */ /** * remove empty props(null or '') from object * @param {Object} input * @returns {Object} * @memberOf FetchQL * @private */ doOmitEmptyVariables(input) { const nonEmptyObj = {}; Object.keys(input).map(key => { const value = input[key]; if ((typeof value === 'string' && value.length === 0) || value === null || value === undefined) { return key; } else if (value instanceof Object) { nonEmptyObj[key] = this.doOmitEmptyVariables(value); } else { nonEmptyObj[key] = value; } return key; }); return nonEmptyObj; } } export default FetchQL; [代码] 将wx的异步接口封装成Promise vendor/promisify.js [代码]function promisify(wx) { let wxx = { ...wx }; for (let attr in wxx) { if (!wxx.hasOwnProperty(attr) || typeof wxx[attr] != 'function') continue; // skip over the sync method if (/sync$/i.test(attr)) continue; wxx[attr + 'Async'] = function asyncFunction(argv = {}) { return new Promise(function (resolve, reject) { wxx[attr].call(wxx, { ...argv, ...{ success: res => resolve(res), fail: err => reject(err) } }); }); }; } return wxx; } export default promisify(typeof wx === 'object' ? wx : {}); [代码] localstorage vendor/storage.js [代码]class Storage { constructor(wx) { this.wx = wx; } static get timestamp() { return new Date() / 1000; } static __isExpired(entity) { if (!entity) return true; return Storage.timestamp - (entity.timestamp + entity.expiration) >= 0; } static get __info() { let info = {}; try { info = this.wx.getStorageInfoSync() || info; } catch (err) { console.error(err); } return info; } setItem(key, value, expiration) { const entity = { timestamp: Storage.timestamp, expiration, key, value }; this.wx.setStorageSync(key, JSON.stringify(entity)); return this; } getItem(key) { let entity; try { entity = this.wx.getStorageSync(key); if (entity) { entity = JSON.parse(entity); } else { return null; } } catch (err) { console.error(err); return null; } // 没有设置过期时间, 则直接返回值 if (!entity.expiration) return entity.value; // 已过期 if (Storage.__isExpired(entity)) { this.remove(key); return null; } else { return entity.value; } } removeItem(key) { try { this.wx.removeStorageSync(key); } catch (err) { console.error(err); } return this; } clear() { try { this.wx.clearStorageSync(); } catch (err) { console.error(err); } return this; } get info() { let info = {}; try { info = this.wx.getStorageInfoSync(); } catch (err) { console.error(err); } return info || {}; } get length() { return (this.info.keys || []).length; } } export default new Storage(wx); [代码] 状态管理 vendor/store.js [代码]module.exports = class Store { constructor({ state, actions }) { this.state = state || {} this.actions = actions || {} this.ctxs = [] } // 派发action, 统一返回promise action可以直接返回state dispatch(type, payload) { const update = res => { if (typeof res !== 'object') return this.setState(res) this.ctxs.map(ctx => ctx.setData(res)) return res } if (typeof this.actions[type] !== 'function') return const res = this.actions[type](this, payload) return res.constructor.toString().match(/function\s*([^(]*)/)[1] === 'Promise' ? res.then(update) : new Promise(resolve => resolve(update(res))) } // 修改state的方法 setState(data) { this.state = { ...this.state, ...data } } // 根据keys获取state getState(keys) { return keys.reduce((acc, key) => ({ ...acc, ...{ [key]: this.state[key] } }), {}) } // 映射state到实例中,可在onload或onshow中调用 mapState(keys, ctx) { if (!ctx || typeof ctx.setData !== 'function') return ctx.setData(this.getState(keys)) this.ctxs.push(ctx) } } [代码] 兼容 async/await vendor/regenerator.js [代码]/** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var regeneratorRuntime = (function (exports) { "use strict"; var Op = Object.prototype; var hasOwn = Op.hasOwnProperty; var undefined; // More compressible than void 0. var $Symbol = typeof Symbol === "function" ? Symbol : {}; var iteratorSymbol = $Symbol.iterator || "@@iterator"; var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator"; var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; function wrap(innerFn, outerFn, self, tryLocsList) { // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator. var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator; var generator = Object.create(protoGenerator.prototype); var context = new Context(tryLocsList || []); // The ._invoke method unifies the implementations of the .next, // .throw, and .return methods. generator._invoke = makeInvokeMethod(innerFn, self, context); return generator; } exports.wrap = wrap; // Try/catch helper to minimize deoptimizations. Returns a completion // record like context.tryEntries[i].completion. This interface could // have been (and was previously) designed to take a closure to be // invoked without arguments, but in all the cases we care about we // already have an existing method we want to call, so there's no need // to create a new function object. We can even get away with assuming // the method takes exactly one argument, since that happens to be true // in every case, so we don't have to touch the arguments object. The // only additional allocation required is the completion record, which // has a stable shape and so hopefully should be cheap to allocate. function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } } var GenStateSuspendedStart = "suspendedStart"; var GenStateSuspendedYield = "suspendedYield"; var GenStateExecuting = "executing"; var GenStateCompleted = "completed"; // Returning this object from the innerFn has the same effect as // breaking out of the dispatch switch statement. var ContinueSentinel = {}; // Dummy constructor functions that we use as the .constructor and // .constructor.prototype properties for functions that return Generator // objects. For full spec compliance, you may wish to configure your // minifier not to mangle the names of these two functions. function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} // This is a polyfill for %IteratorPrototype% for environments that // don't natively support it. var IteratorPrototype = {}; IteratorPrototype[iteratorSymbol] = function () { return this; }; var getProto = Object.getPrototypeOf; var NativeIteratorPrototype = getProto && getProto(getProto(values([]))); if (NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) { // This environment has a native %IteratorPrototype%; use it instead // of the polyfill. IteratorPrototype = NativeIteratorPrototype; } var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; GeneratorFunctionPrototype.constructor = GeneratorFunction; GeneratorFunctionPrototype[toStringTagSymbol] = GeneratorFunction.displayName = "GeneratorFunction"; // Helper for defining the .next, .throw, and .return methods of the // Iterator interface in terms of a single ._invoke method. function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function(method) { prototype[method] = function(arg) { return this._invoke(method, arg); }; }); } exports.isGeneratorFunction = function(genFun) { var ctor = typeof genFun === "function" && genFun.constructor; return ctor ? ctor === GeneratorFunction || // For the native GeneratorFunction constructor, the best we can // do is to check its .name property. (ctor.displayName || ctor.name) === "GeneratorFunction" : false; }; exports.mark = function(genFun) { if (Object.setPrototypeOf) { Object.setPrototypeOf(genFun, GeneratorFunctionPrototype); } else { genFun.__proto__ = GeneratorFunctionPrototype; if (!(toStringTagSymbol in genFun)) { genFun[toStringTagSymbol] = "GeneratorFunction"; } } genFun.prototype = Object.create(Gp); return genFun; }; // Within the body of any async function, `await x` is transformed to // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test // `hasOwn.call(value, "__await")` to determine if the yielded value is // meant to be awaited. exports.awrap = function(arg) { return { __await: arg }; }; function AsyncIterator(generator) { function invoke(method, arg, resolve, reject) { var record = tryCatch(generator[method], generator, arg); if (record.type === "throw") { reject(record.arg); } else { var result = record.arg; var value = result.value; if (value && typeof value === "object" && hasOwn.call(value, "__await")) { return Promise.resolve(value.__await).then(function(value) { invoke("next", value, resolve, reject); }, function(err) { invoke("throw", err, resolve, reject); }); } return Promise.resolve(value).then(function(unwrapped) { // When a yielded Promise is resolved, its final value becomes // the .value of the Promise<{value,done}> result for the // current iteration. result.value = unwrapped; resolve(result); }, function(error) { // If a rejected Promise was yielded, throw the rejection back // into the async generator function so it can be handled there. return invoke("throw", error, resolve, reject); }); } } var previousPromise; function enqueue(method, arg) { function callInvokeWithMethodAndArg() { return new Promise(function(resolve, reject) { invoke(method, arg, resolve, reject); }); } return previousPromise = // If enqueue has been called before, then we want to wait until // all previous Promises have been resolved before calling invoke, // so that results are always delivered in the correct order. If // enqueue has not been called before, then it is important to // call invoke immediately, without waiting on a callback to fire, // so that the async generator function has the opportunity to do // any necessary setup in a predictable way. This predictability // is why the Promise constructor synchronously invokes its // executor callback, and why async functions synchronously // execute code before the first await. Since we implement simple // async functions in terms of async generators, it is especially // important to get this right, even though it requires care. previousPromise ? previousPromise.then( callInvokeWithMethodAndArg, // Avoid propagating failures to Promises returned by later // invocations of the iterator. callInvokeWithMethodAndArg ) : callInvokeWithMethodAndArg(); } // Define the unified helper method that is used to implement .next, // .throw, and .return (see defineIteratorMethods). this._invoke = enqueue; } defineIteratorMethods(AsyncIterator.prototype); AsyncIterator.prototype[asyncIteratorSymbol] = function () { return this; }; exports.AsyncIterator = AsyncIterator; // Note that simple async functions are implemented on top of // AsyncIterator objects; they just return a Promise for the value of // the final result produced by the iterator. exports.async = function(innerFn, outerFn, self, tryLocsList) { var iter = new AsyncIterator( wrap(innerFn, outerFn, self, tryLocsList) ); return exports.isGeneratorFunction(outerFn) ? iter // If outerFn is a generator, return the full iterator. : iter.next().then(function(result) { return result.done ? result.value : iter.next(); }); }; function makeInvokeMethod(innerFn, self, context) { var state = GenStateSuspendedStart; return function invoke(method, arg) { if (state === GenStateExecuting) { throw new Error("Generator is already running"); } if (state === GenStateCompleted) { if (method === "throw") { throw arg; } // Be forgiving, per 25.3.3.3.3 of the spec: // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume return doneResult(); } context.method = method; context.arg = arg; while (true) { var delegate = context.delegate; if (delegate) { var delegateResult = maybeInvokeDelegate(delegate, context); if (delegateResult) { if (delegateResult === ContinueSentinel) continue; return delegateResult; } } if (context.method === "next") { // Setting context._sent for legacy support of Babel's // function.sent implementation. context.sent = context._sent = context.arg; } else if (context.method === "throw") { if (state === GenStateSuspendedStart) { state = GenStateCompleted; throw context.arg; } context.dispatchException(context.arg); } else if (context.method === "return") { context.abrupt("return", context.arg); } state = GenStateExecuting; var record = tryCatch(innerFn, self, context); if (record.type === "normal") { // If an exception is thrown from innerFn, we leave state === // GenStateExecuting and loop back for another invocation. state = context.done ? GenStateCompleted : GenStateSuspendedYield; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } else if (record.type === "throw") { state = GenStateCompleted; // Dispatch the exception by looping back around to the // context.dispatchException(context.arg) call above. context.method = "throw"; context.arg = record.arg; } } }; } // Call delegate.iterator[context.method](context.arg) and handle the // result, either by returning a { value, done } result from the // delegate iterator, or by modifying context.method and context.arg, // setting context.delegate to null, and returning the ContinueSentinel. function maybeInvokeDelegate(delegate, context) { var method = delegate.iterator[context.method]; if (method === undefined) { // A .throw or .return when the delegate iterator has no .throw // method always terminates the yield* loop. context.delegate = null; if (context.method === "throw") { // Note: ["return"] must be used for ES3 parsing compatibility. if (delegate.iterator["return"]) { // If the delegate iterator has a return method, give it a // chance to clean up. context.method = "return"; context.arg = undefined; maybeInvokeDelegate(delegate, context); if (context.method === "throw") { // If maybeInvokeDelegate(context) changed context.method from // "return" to "throw", let that override the TypeError below. return ContinueSentinel; } } context.method = "throw"; context.arg = new TypeError( "The iterator does not provide a 'throw' method"); } return ContinueSentinel; } var record = tryCatch(method, delegate.iterator, context.arg); if (record.type === "throw") { context.method = "throw"; context.arg = record.arg; context.delegate = null; return ContinueSentinel; } var info = record.arg; if (! info) { context.method = "throw"; context.arg = new TypeError("iterator result is not an object"); context.delegate = null; return ContinueSentinel; } if (info.done) { // Assign the result of the finished delegate to the temporary // variable specified by delegate.resultName (see delegateYield). context[delegate.resultName] = info.value; // Resume execution at the desired location (see delegateYield). context.next = delegate.nextLoc; // If context.method was "throw" but the delegate handled the // exception, let the outer generator proceed normally. If // context.method was "next", forget context.arg since it has been // "consumed" by the delegate iterator. If context.method was // "return", allow the original .return call to continue in the // outer generator. if (context.method !== "return") { context.method = "next"; context.arg = undefined; } } else { // Re-yield the result returned by the delegate method. return info; } // The delegate iterator is finished, so forget it and continue with // the outer generator. context.delegate = null; return ContinueSentinel; } // Define Generator.prototype.{next,throw,return} in terms of the // unified ._invoke helper method. defineIteratorMethods(Gp); Gp[toStringTagSymbol] = "Generator"; // A Generator should always return itself as the iterator object when the // @@iterator function is called on it. Some browsers' implementations of the // iterator prototype chain incorrectly implement this, causing the Generator // object to not be returned from this call. This ensures that doesn't happen. // See https://github.com/facebook/regenerator/issues/274 for more details. Gp[iteratorSymbol] = function() { return this; }; Gp.toString = function() { return "[object Generator]"; }; function pushTryEntry(locs) { var entry = { tryLoc: locs[0] }; if (1 in locs) { entry.catchLoc = locs[1]; } if (2 in locs) { entry.finallyLoc = locs[2]; entry.afterLoc = locs[3]; } this.tryEntries.push(entry); } function resetTryEntry(entry) { var record = entry.completion || {}; record.type = "normal"; delete record.arg; entry.completion = record; } function Context(tryLocsList) { // The root entry object (effectively a try statement without a catch // or a finally block) gives us a place to store values thrown from // locations where there is no enclosing try statement. this.tryEntries = [{ tryLoc: "root" }]; tryLocsList.forEach(pushTryEntry, this); this.reset(true); } exports.keys = function(object) { var keys = []; for (var key in object) { keys.push(key); } keys.reverse(); // Rather than returning an object with a next method, we keep // things simple and return the next function itself. return function next() { while (keys.length) { var key = keys.pop(); if (key in object) { next.value = key; next.done = false; return next; } } // To avoid creating an additional object, we just hang the .value // and .done properties off the next function object itself. This // also ensures that the minifier will not anonymize the function. next.done = true; return next; }; }; function values(iterable) { if (iterable) { var iteratorMethod = iterable[iteratorSymbol]; if (iteratorMethod) { return iteratorMethod.call(iterable); } if (typeof iterable.next === "function") { return iterable; } if (!isNaN(iterable.length)) { var i = -1, next = function next() { while (++i < iterable.length) { if (hasOwn.call(iterable, i)) { next.value = iterable[i]; next.done = false; return next; } } next.value = undefined; next.done = true; return next; }; return next.next = next; } } // Return an iterator with no values. return { next: doneResult }; } exports.values = values; function doneResult() { return { value: undefined, done: true }; } Context.prototype = { constructor: Context, reset: function(skipTempReset) { this.prev = 0; this.next = 0; // Resetting context._sent for legacy support of Babel's // function.sent implementation. this.sent = this._sent = undefined; this.done = false; this.delegate = null; this.method = "next"; this.arg = undefined; this.tryEntries.forEach(resetTryEntry); if (!skipTempReset) { for (var name in this) { // Not sure about the optimal order of these conditions: if (name.charAt(0) === "t" && hasOwn.call(this, name) && !isNaN(+name.slice(1))) { this[name] = undefined; } } } }, stop: function() { this.done = true; var rootEntry = this.tryEntries[0]; var rootRecord = rootEntry.completion; if (rootRecord.type === "throw") { throw rootRecord.arg; } return this.rval; }, dispatchException: function(exception) { if (this.done) { throw exception; } var context = this; function handle(loc, caught) { record.type = "throw"; record.arg = exception; context.next = loc; if (caught) { // If the dispatched exception was caught by a catch block, // then let that catch block handle the exception normally. context.method = "next"; context.arg = undefined; } return !! caught; } for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; var record = entry.completion; if (entry.tryLoc === "root") { // Exception thrown outside of any try block that could handle // it, so set the completion value of the entire function to // throw the exception. return handle("end"); } if (entry.tryLoc <= this.prev) { var hasCatch = hasOwn.call(entry, "catchLoc"); var hasFinally = hasOwn.call(entry, "finallyLoc"); if (hasCatch && hasFinally) { if (this.prev < entry.catchLoc) { return handle(entry.catchLoc, true); } else if (this.prev < entry.finallyLoc) { return handle(entry.finallyLoc); } } else if (hasCatch) { if (this.prev < entry.catchLoc) { return handle(entry.catchLoc, true); } } else if (hasFinally) { if (this.prev < entry.finallyLoc) { return handle(entry.finallyLoc); } } else { throw new Error("try statement without catch or finally"); } } } }, abrupt: function(type, arg) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { var finallyEntry = entry; break; } } if (finallyEntry && (type === "break" || type === "continue") && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc) { // Ignore the finally entry if control is not jumping to a // location outside the try/catch block. finallyEntry = null; } var record = finallyEntry ? finallyEntry.completion : {}; record.type = type; record.arg = arg; if (finallyEntry) { this.method = "next"; this.next = finallyEntry.finallyLoc; return ContinueSentinel; } return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === "throw") { throw record.arg; } if (record.type === "break" || record.type === "continue") { this.next = record.arg; } else if (record.type === "return") { this.rval = this.arg = record.arg; this.method = "return"; this.next = "end"; } else if (record.type === "normal" && afterLoc) { this.next = afterLoc; } return ContinueSentinel; }, finish: function(finallyLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.finallyLoc === finallyLoc) { this.complete(entry.completion, entry.afterLoc); resetTryEntry(entry); return ContinueSentinel; } } }, "catch": function(tryLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc === tryLoc) { var record = entry.completion; if (record.type === "throw") { var thrown = record.arg; resetTryEntry(entry); } return thrown; } } // The context.catch method must only be called with a location // argument that corresponds to a known catch block. throw new Error("illegal catch attempt"); }, delegateYield: function(iterable, resultName, nextLoc) { this.delegate = { iterator: values(iterable), resultName: resultName, nextLoc: nextLoc }; if (this.method === "next") { // Deliberately forget the last sent value so that we don't // accidentally pass it on to the delegate. this.arg = undefined; } return ContinueSentinel; } }; // Regardless of whether this script is executing as a CommonJS module // or not, return the runtime object so that we can declare the variable // regeneratorRuntime in the outer scope, which allows this module to be // injected easily by `bin/regenerator --include-runtime script.js`. return exports; }( // If this script is executing as a CommonJS module, use module.exports // as the regeneratorRuntime namespace. Otherwise create a new empty // object. Either way, the resulting object will be used to initialize // the regeneratorRuntime variable at the top of this file. typeof module === "object" ? module.exports : {} )); [代码] 后端 [代码]const typeDefs = gql` # schema 下面是根类型,约定是 RootQuery 和 RootMutation schema { query: Query } # 定义具体的 Query 的结构 type Query { # 登陆接口 login(code: String!, encryptedData: String, iv: String): Login } type Login { token: String! userInfo: UserInfo } type UserInfo { nickName: String gender: String avatarUrl: String } `; const resolvers = { Query: { async login(parent, { code, encryptedData, iv }) { const { sessionKey, openId, unionId } = await wxService.code2Session(code); const userInfo = encryptedData && iv ? wxService.decryptData(sessionKey, encryptedData, iv) : { openId, unionId }; if (userInfo.nickName) { userService.createOrUpdateWxUser(userInfo); } const token = await userService.generateJwtToken(userInfo); return { token, userInfo }; }, }, }; [代码]
2019-04-21 - 微信小程序三种授权登录的方式
经过一段时间对微信小程序的研发后 总结出以下三种授权登录的方式,我给他们命名为‘一次性授权’‘永久授权’‘不授权’ 1.一次性授权 常规写法,需要获取用户公开信息(头像,昵称等)时,判断调取授权登录接口,但是此方法如果不经处理的话 用户如果拒绝授权或者删除该微信小程序后 需要重新调取并获取用户公开信息(头像,昵称等),此方法用户体验较差,不建议使用; 2.永久授权 在不必要使用用户公开信息(头像,昵称等)时,不调取授权登录接口,只有在必要的时候再去判断调取授权登录接口并把获取到的用户公开信息存入数据库,这样在每次登录时直接先运行指定函数从数据库索取需要的用户公开信息(头像,昵称等)即可,此方法在删除小程序后不用再次去授权登录(因为在用户第一次授权登录时已经把用户的公开信息存入数据库了以后直接向数据库索取即可),建议使用; 3.不授权 不需要授权登录获取用户公开信息(头像,昵称等),使用wx.login获取用户code并传入后台,后台可以通过用户的code值向微信要一个值(具体需要问后台,我只是个小前端,后台的东西不是很懂,只是知道一些逻辑而且也已经成功实现)然后通过这个用code换取的值就可以识别到指定用户,如果需要的话,前端要显示的头像、昵称等这些信息可以使用自定义可编辑的功能,当然,也可以通过<open-data type=“userAvatarUrl”></open-data><open-data type=“userNickName”></open-data>小程序提供的这个组件显示用户的头像及昵称(不过这个组件只有显示功能),用户如果想直接使用自己的头像昵称,也可以自行授权(比如添加个引导按钮什么之类的),建议使用; [图片][图片] 文中使用的微信自带接口、组件及函数: <open-data type=“userAvatarUrl”></open-data> <open-data type=“userNickName”></open-data> wx.login({ success(res){ console.log(res.code) } }) 微信授权登录 以上三种方式可以灵活运用,也可以把需要的结合到一起,并不冲突; 当然,大佬很多,我也只是个小前端而已,第一次发表技术方面的帖子,希望互相学习,互相指导,如有说的不对的地方还望大佬们及时指出!!! 谢谢
2019-04-18 - 社区每周 |接入IP变更通知、接口授权修改公告、社区更新、大赛投票及问题反馈(9.14-9.18)
各位微信开发者: 以下是api.weixin.qq.com接入IP变更通知、小程序收货地址、发票接口授权修改公告、小程序私密消息功能发布、小程序云开发挑战赛初赛投票中、社区支持微信消息提醒及上周我们在社区收到的问题反馈、需求的处理进度,希望同大家一同打造小程序生态。 api.weixin.qq.com接入IP变更通知由于内部网络设备裁撤,api.weixin.qq.com(sz.api.weixin.qq.com)需要更新部分接入点IP。 具体详情,请点击《api.weixin.qq.com接入IP变更通知》详细了解或转发周知。 小程序收货地址、发票接口授权修改公告当前小程序调用收货地址与发票相关接口需要用户进行相应授权: [图片]由于此3个接口均为拉起微信原生页面由用户进行选择,为了方便开发者更好地使用微信开放能力,同时开发者无需进行额外适配,针对以上接口授权进行如下修改: 开发者可以直接调用以上3个接口,无需获取用户授权若开发者调用wx.authorize接口请求以上3个授权,用户侧不会触发授权弹框,直接返回授权成功若开发者调用wx.getSetting接口请求用户的授权状态,会直接读取到以上3个授权为true以上修改从2020年9月25日起生效。 小程序私密消息功能发布小程序私密消息功能是这样一种能力:当分享者分享小程序卡片给其他用户或者微信群后,其他用户点击此小程序卡片时,开发者可以鉴别出点击卡片的用户是否被分享者分享过小程序卡片。能力可详见 文档 小程序云开发挑战赛初赛投票中小程序云开发挑战赛目前已进入初赛阶段: 初赛采用专家评审+投票方式进行,结果计算公式为:专家评分(80%)+ 投票(20%)= 初赛总分。 投票方式:微信开放社区作品介绍文章上点赞,点击参与投票 投票时间:9月21日12:00-9月25日24:00 初赛评选结果公布时间为9月30日,每个赛道将评选出10支队伍进入复赛。 想要了解大赛更多详情,欢迎前往 官网 详细了解。 小程序云开发支持公众号网页开发与环境共享小程序云开发现已支持跨账号环境(资源)共享,也就是一个小程序的云开发资源可以授权给其他小程序 / 公众号使用。 详情可查看 “微信开发者”公众号推文 或 文档 社区支持微信消息提醒问答互动提醒在社区提出问题时,可通过勾选「有回复后微信提醒我」来设置微信客户端接收回复提醒,随时关注问题互动 [图片] 订阅提醒后,也可以在相应问题详情页随时关闭微信提醒 [图片] 公告发布提醒可以在公告的列表页选择微信提醒接收公告发布消息,第一时间了解公告资讯 [图片] 私信接收提醒可以在私信通知栏点击设置图标进入「微信提醒设置页」 [图片] 在「微信提醒设置页」具体配置微信客户端接收私信提醒 [图片] 上周问题反馈和处理进度(09.14-09.18)已修复的问题今天发现短链接无法打开,报 404 的问题 查看详情 小程序在提交修改关联公众号时,显示“系统繁忙,请稍后再试”的问题 查看详情 阅读量清空重新计数的问题 查看详情 微信公众平台管理后台小程序功能设置-其他小程序修改报错的问题 查看详情 修复中的问题视频广告提前关闭,isEnded 返回 true 的问题 查看详情 使用腾讯长地址转短地址之后的短地址,都无法打开的问题 查看详情 iOS新版本下,Map组件的Callout位置向左偏移的问题 查看详情 报错找不到fs模块怎么回事 查看详情 公众号上传视频出现:上传分片错误, ret code:-1 查看详情 scrollview中video全屏退出后,scrollview会回到顶部 查看详情 开发者工具调试器Wxml节点元素是空的 查看详情 编译几次后,调试器选择不了dom元素的问题 查看详情 真机调试看不到日志,每次编译项目都会弹窗“更改appID失败,登录用户不是开发者”的问题 查看详情 ipone7,picker的问题 查看详情 玩小游戏的时候分享出去后游戏概率卡死,整个微信也有几率闪退的问题 查看详情 父级设置缩放后 子级movable-view拖动不到边界的问题 查看详情 iOS14上picker mode=date 选择日期的弹出框变形了的问题 查看详情 picker组件 省市区里面 缺少 四川 - 宜宾 -叙州 这个区的问题 查看详情 使用【新的编译模块】:host 部分样式被过滤掉的问题 查看详情 小程序开发指南文档编辑错误的问题 查看详情 scroll-top和scroll-with-animation冲突bug 查看详情 开发工具要求升级,升级后编译不了的问题 查看详情 公众号里面自带的投票管理功能的问题 查看详情 关于开放域设置数据 setUserCloudStorage 的问题 查看详情 需求反馈需求评估中movable-view组件设置为scale模式后能否设置双指缩放的中心点的需求 查看详情 小程序助手中发布小程序的时候的验证能不能改成反光验证的需求 查看详情 公众号设置视频智能推荐的开关的需求 查看详情 更改公众号显示设置,把原创篇数更改为已发送篇数的需求 查看详情 公众号视频观看更多关闭的需求 查看详情 建议新增文章底部阅读量展示的开关 查看详情 wx.showModal能否新增个open-type属性的需求 查看详情 希望官方能解除【视频消息】下方强制插入的推荐视频的需求 查看详情 input 和 textarea 输入的内容在 iOS 上旁白不朗读的需求 查看详情 微信平台后台素材能否添加删除分组图片的需求 查看详情 定时群发设置更新的需求 查看详情 音频素材批量上传的需求 查看详情 wx.canvasPutImageData 绘制的需求 查看详情 微信团队 2020.09.24
2020-09-24 - 小程序分享朋友圈能不能跳转指定页面?
在A页面有一个分享朋友圈功能,填写的query 路径跳转的是B页面,但是从朋友圈点击进入的还是A页面,可以做到跳转指定页面吗?
2020-08-19 - 社区每周|微信iOS新版众测邀请、8月突出贡献者公示及上周问题反馈(09.07-09.11)
各位微信开发者: 以下是微信团队邀请开发者参与iOS微信7.0.16内部体验、8月社区突出贡献者公示及上周我们在社区收到的问题反馈、需求的处理进度,希望同大家一同打造小程序生态。 微信团队邀请开发者参与iOS微信7.0.16内部体验微信团队邀请开发者参与内部体验并反馈问题。 * 体验需长按下方二维码报名,若报名成功,则一天内会收到内测推送,内测名额8000人 [图片] 请基于以上提供的资源体验。使用过程中若发现问题,欢迎点击进入微信开放社区 #微信客户端内测 主页发表标题包含「微信iOS 7.0.16」的问答帖子反馈交流,发帖时建议提供以下信息方便定位问题: 手机型号手机操作系统版本必要时可提供代码片段如有需要,可访问社区#微信客户端内测 主页原公告:《微信团队邀请开发者参与iOS微信7.0.16内部体验》 上周问题反馈和处理进度(09.07-09.11)已修复的问题公众号菜单点击事件自动回复的图文消息图片无法显示的问题 查看详情 地图组件真机marker 点击后无法获取 markerId 的问题 查看详情 msgSecCheck大量错误的问题 查看详情 api要求https协议,但是debug工具用的是http协议的问题 查看详情 公众号发文显示系统错误的问题 查看详情 社区回答变成“1”的问题 查看详情 修复中的问题小游戏在pc上绘图有一个像素偏移的问题 查看详情 微信小游戏跳转 iOS第一次跳转打印成功了 开始跳转时候闪了一下 就回到微信主界面的问题 查看详情 小游戏制作工具声音播放问题 查看详情 CocosCreator版本2.3.3,加分包图片有机率失败?报错4930的问题 查看详情 小游戏 iOS 跳转其它小游戏,其它小游戏点击重启小游戏,启动后,1s内必定闪退的问题 查看详情 微信后台-场景分析界面的问题的问题 查看详情 live-pusher 关闭摄像头重新推流时,获取音量事件不触发,个别安卓版本必现的问题 查看详情 wx.chooseVideo 录像后播放无法调节音量的问题 查看详情 console.error 爆栈的问题 查看详情 useExtendedLib扩展组件,开发工具跟真机显示样式不一致的问题 查看详情 服务器合法域名配置成功,但是request请求后接受不到服务器返回,是哪里配置有问题 查看详情 基础库2.13.0SearchBar样式出错的问题 查看详情 小程序私密消息测试报错的问题 查看详情 小程序更多资料重复的问题 查看详情 canvas2d自定义组件 disable-scroll="true" iOS不生效,页面出现滚动条的问题 查看详情 小程序后台怎么出现两个基础库最低版本设置的问题 查看详情 微信公众平台小程序后台服务器域名没更新的问题 查看详情 社区小程序端的问题 查看详情 需求反馈需求评估中建议开通标题修改功能 查看详情 智能插入广告需求 查看详情 已发出的图文消息添加图片修改功能的需求 查看详情 删掉后能把整个消息都删掉的需求 查看详情 社区的私聊框,加个发送时间框的需求 查看详情 小游戏数据分析的开放接口API什么时候能够开放 查看详情 发表公众号文章的相关需求 查看详情 订阅号助手添加打标签功能的需求 查看详情 把“读者讨论”与浏览量、再看、点赞放在一块的需求 查看详情 文章页面模板分组的需求 查看详情 pc端小游戏希望可以出一个虚拟按键的需求 查看详情 希望可以在订阅号助手app上查看和管理问答(读者讨论)相关内容以及加强数据统计功能 查看详情 社区2020.08月度突出贡献者名单公示 以下为上个月(2020年08月)社区月度“突出贡献者”名单,具体用户主页可从社区首页右侧公示版块中进入: [图片] 为了鼓励大家持续为社区生产优质内容,以上社区月度突出贡献者将获得如下奖励: 一份微信官方正版周边礼物小程序极速审核奖励*注:极速审核机制说明:在获得突出贡献者前已绑定(作为管理员或开发者)的小程序,在本月3号后至月底可享受极速审核的奖励。小程序将在2小时内审核完毕,工作时间:周一到周五,9点-21点;周六周日,9点-19点 微信团队 2020.09.17
2020-09-17 - 社区每周|安卓新版众测、基础库更新、MP统计变化通知、云开发挑战赛回放及问题反馈(8.31-9.4)
各位微信开发者 以下是微信团队邀请开发者参与内部体验(安卓微信7.0.19)、小程序云开发挑战赛答疑、关于MP首页“数据来源概况”统计变化的通知、小程序基础库2.13.0更新及上周我们在社区收到的问题反馈、需求的处理进度,希望同大家一同打造小程序生态。 微信团队邀请开发者参与内部体验(安卓微信7.0.19) 本次更新概要如下小程序 调整右上角胶囊布局及层级;重构折叠屏适配的实现;优化识别二维码打开小程序的速度;fix video 若干bug,需关注video seek及进度更新等特性;需关注使用小窗时,前后台及页面切换逻辑是否正常;需关注小程序返回逻辑是否正常;worker 支持 CPU Profile;优化启动速度,需关注使用音视频编辑组件等功能时的表现;解决音视频编辑组件播放功能解决音频播空的时候噪音问题。 小游戏 调整右上角胶囊布局及层级;重构折叠屏适配的实现;worker 支持 CPU Profile;优化启动速度,需关注使用音视频编辑组件等功能时的表现;解决音视频编辑组件播放功能解决音频播空的时候噪音问题。 请基于以下提供的资源体验。使用过程中若发现问题,欢迎点击进入微信开放社区 #微信客户端内测 主页发表标题包含「微信7.0.19」的问答帖子反馈交流。 [图片] (扫描二维码下载) 如有需要,可查看并转发原公告:《微信团队邀请开发者参与内部体验(安卓微信7.0.19)》 关于MP首页“数据来源概况”统计变化的通知 近期对小程序“数据来源概况”进行了统计优化,具体内容如下: 原“Top5访问来源”和“日增长top5来源”为访问uv数据; 目前调整为“打开次数”的数据; 请开发者留意,具体统计方式以下方数据文档中“打开次数”的解释为准。 https://developers.weixin.qq.com/miniprogram/analysis/ 小程序基础库2.13.0更新 小程序基础库 2.13.0 已经开始灰度开发者,请大家基于业务情况关注相关变更。如遇问题请及时在本帖下方留言或在小程序交流专区发表标题包含「基础库2.13.0」的帖子反馈。本次更新如下: A 新增 框架 小程序私密消息支持 A 新增 API 检测手机开启视觉无障碍功能 A 新增 API 增加扫一扫识物接口 U 更新 API 震动接口增加强度参数 U 更新 API 地图 scale 支持小数 U 更新 API translateMarker 支持同时旋转和平移 U 更新 API 地图支持点聚合 U 更新 API 地图平滑动画 U 更新 API iOS 支持保存 gif & webp 到相册 U 更新 API iOS 蓝牙从机增加特征值订阅关系 U 更新 框架 iOS worker 性能优化(可选) U 更新 框架 小游戏新增 mDNS 接口 U 更新 框架 提供获取 deviceOrientation 的接口 U 更新 组件 label 支持 width & height U 更新 组件 textarea 组件增加选择键盘功能 U 更新 组件 地图获取定位点点击事件 U 更新 组件 地图指定最大/最小缩放级别 U 更新 组件 canvas 2d 支持自定义字体 U 更新 组件 canvas 2d 新增 getTransform 接口 U 更新 组件 地图支持彩虹线 U 更新 组件 地图 regionChange 事件返回 scale U 更新 组件 video 和 live-player 支持后台播放 U 更新 组件 picker内容过长时支持完整显示 U 更新 组件 textarea右下角的按钮可以像input一样定制 F 修复 框架 iOS小游戏销毁camera再创建时获取不到帧数据 F 修复 框架 iOS小游戏2d渲染抗锯齿模式下toTempFilePath截屏空白 F 修复 组件 安卓 textarea 同层下 bind:input 没触发 F 修复 组件 android video 播放结束拖动滚动条会自动播放且播放进度未更新 F 修复 组件 从分享卡片进入小窗后 switchTab 失效 F 修复 组件 安卓下键盘弹起后露出上一页面的内容 F 修复 API 安卓一些特定机型调 connectWifi 接口大概率出现 12003 错误 交流回放:小程序云开发挑战赛答疑为了解答大家可能出现的问题,今天(2020.09.08)晚上20:00,官方组委会联合专家评审委员会,专程举办新一场赛事答疑交流会。旨在和大家在线交流互动关于比赛规程与作品提交上的一些问题和注意点,帮助大家能够更好的开发与提交作品,一举夺得好的成绩。 「交流地址」名额 300,先到先得 已结束 https://meeting.tencent.com/s/LCoV1Hy3nD36 「直播链接」不限名额 可观看回放 https://meeting.tencent.com/l/krlBnaWRXoux 「交流议题」 大赛赛事激励解读(通过比赛你能获得什么)作品提交需注意的点(专家评委现身解答,从评委角度来看待作品)Q&A答疑环节 更多详情,欢迎点击 活动页面 详细了解。 上周问题反馈和处理进度(08.31-09.04)已修复的问题素材管理中视频素材不见了的问题 查看详情 微信公众平台后台语音听不到的问题 查看详情 上传商品图片上传不的问题 查看详情 文章发布时间和文章显示时间不一致的问题 查看详情 公众号视频下方出现 undefined 字样的问题 查看详情 修复中的问题小游戏看视频广告时, 没看完就关闭,onClose事件里的isEnded也为true的问题 查看详情 CustomAD 报5047错误的问题 查看详情 真机调试时,报错导致黑屏:error occurs:EEXIST 的问题 查看详情 innerAudioContext onCanplay 在能播时加上不会生效的问题 查看详情 米大师支付 403,-9998 的问题 查看详情 文档,单词错误 查看详情 更新微信版本后,用户提示“运行内存不足,请重新打开该小游戏”的问题 查看详情 微信小程序开发者文档 返回 页面报404 查看详情 SKU查询信息字段错误 查看详情 iOS 手机 wx.login 接口调用失败的问题 查看详情 错误代码:基础信息:图片数量应为1-9的问题 查看详情 社区勿点了关注的帖子无法取消关注的问题 查看详情 社区文章编辑器 切换MD编辑器按钮 显示问题 查看详情 animation动画在IOS6 7没效果,无法正常运行的问题 查看详情 iOS 调用 wx.openDocument 打开一直白屏的问题 查看详情 iphone真机上部分图片无法显示的问题 查看详情 web-view真机报错但开发工具和浏览器正常的问题 查看详情 开发工具升级后的问题 查看详情 需求反馈需求评估中建议视频落地页智能全局推荐视频恢复到原先的横向排列 查看详情 微信草稿预览推送功能增加常用预览者列表的需求 查看详情 设置“先关注再投票”的需求 查看详情 希望添加多个途径点功能 查看详情 公众号后台的多媒体素材库的图片,是否可以增加搜索的选项 查看详情 期望在开发->运维中心 日志查询 新增一个根据小程序版本查询日志 查看详情 project.config.json 修改为 project.config.js 查看详情 账号合并登录的需求 查看详情 公众号定时群发次数增加1天的需求 查看详情 微信团队 2020.09.08
2020-09-09 - 社区每周|公交地铁行业乘车码激励、小程序云开发实战课、活动及问题反馈(8.24-8.28)
各位微信开发者: 以下是微信开放平台公交地铁行业小程序乘车码激励活动及上周我们在社区收到的问题反馈、需求的处理进度,希望同大家一同打造小程序生态。 开放报名:微信开放平台公交地铁行业小程序乘车码激励活动为更好的鼓励服务商开拓公交地铁行业小程序乘车码场景业务,为广大用户提供高效、便捷、贴心的出行体验,微信开放平台推出本激励活动,服务商代所授权的小程序商户报名成功并满足规定的条件后,服务商可获得相应的奖励。 更多详情,欢迎点击《开放报名:微信开放平台公交地铁行业小程序乘车码激励活动》详细了解。 课程推荐:小程序云开发官方实战直播课(第二期) 由官方核心研发团队打造的小程序云开发实战直播课第二期:《Kbone + 云开发快速助力小程序 & Web同构》,可扫码观看。 [图片] (扫描进入直播间) 更多详情,欢迎点击 活动页面 详细了解。 上周问题反馈和处理进度(08.24-08.28) 已修复的问题页面退出后,未注销 IntersectionObserver 将导致内存泄漏的问题 查看详情 小游戏云函数宕机了十几分钟返回 errorCode-601001 的问题 查看详情 小程序助手点击提交没有反应的问题 查看详情 微信公众号注册遇到系统繁忙的问题 查看详情 获取在线客服列表接口调不通 查看详情 小程序访问不到云存储中的图片 查看详情 问答功能打不开的问题 查看详情 修复中的问题cover-view外层有时候 position:fixed 无效的问题 查看详情 swiper组件 指示点被swiper-item覆盖 查看详情 地图自定义 callout 溢出的问题 查看详情 小游戏销毁 camera 再创建时获取不到帧数据的问题 查看详情 image 回调不触发的问题 查看详情 调用接口errcode: -1, system error rid: 5f425ad0-40799 查看详情 choosevideo拍摄完成后,视频为何严重丢帧的问题 查看详情 微信多客服获取在线客服列表接口报错 查看详情 云开发控制台数据加载失败 查看详情 live-player小窗模式,微信高版本不起作用的问题 查看详情 通过微信分享的多个web链接,微信浏览器依次打开后会显示第一篇缓存的内容的问题 查看详情 小程序调用云存储图片:渲染层网络层错误Failed to load image 的问题 查看详情 小游戏真机调试报错 errMsg: "operateWXData:fail:invalid scop 的问题 查看详情 文档说明有误的问题 查看详情 使用投票功能,为什么却票数变少了 查看详情 打开开发工具之后后续开发更改代码并重新编译会卡住,就是tabbar的子页面不显示,为空白 查看详情 需求反馈迭代跟进中swiper 支持双指滑动 查看详情 插件模式下地图支持 subkey 查看详情 wiper 支持双指滑动 查看详情 需求评估中udp的性能优化的需求 查看详情 没有点击浮窗 floating 的事件 api 的需求 查看详情 订阅号助手APP能有问答提醒的需求 查看详情 微信后台很多音乐提示版权过期无法添加保存的需求 查看详情 图文消息删除单张图片功能的需求 查看详情 开发者工具震动模拟阻碍游戏操作 查看详情 公众号平台编辑页面右侧滚动条加宽的需求 查看详情 新的canvas2d api 支持 OffscreenCanvas 的需求 查看详情 页面路径推送放开上限的需求 查看详情 建议优化小程序开发者绑定机制的需求 查看详情 希望可以推出一个会员分组折扣的功能 查看详情 moveToLocation 新增一个不需要授权就能平移的接口的需求 查看详情 微信团队 2020.09.03
2020-09-03 - 小程序wx.getLocation用户拒绝授权后如何再次获取位置信息?
这里微信小程序我就写了一个简单的获取位置信息的代码 <map id="map" longitude="{{longitude}}" latitude="{{latitude}}" scale="14" hasLocation = "{{hasLocation}}" show-location="true" bindcontroltap="controltap" markers="{{markers}}" covers="{{covers}}" bindmarkertap="markertap" bindregionchange="regionchange" style="width: 100%; height: 100%;"> <cover-view class="map-cover" catchtap="goBack"> <cover-image src="/images/thatweizhi.png"></cover-image> </cover-view> </map> 然后是js代码: onLoad: function (options) { this.getLocation(); }, goBack() { console.log("111"); this.getLocation(); }, getLocation(){ let that = this; wx.getLocation({ type: 'gcj02', success:function(res){ console.log(res); let latitude = res.latitude; let longitude = res.longitude; that.setData({ latitude, longitude }) } }) }, 想做成的效果是用户进来时会提示弹框让其选择授权或拒绝,当用户拒绝时不会获取位置进行定位,而当用户点击goBack按钮时又能重新定位。但是这样写不能达到效果,请问该怎么做? [图片] [图片]
2020-07-14 - 社区每周| 小程序后台新增“清空接口调用量”、直播预告、话题获奖名单及问题反馈(7.20-7.24)
各位微信开发者: 以下是小程序管理后台新增“清空接口调用量”功能、小程序云开发实战直播课、近期话题获奖名单及上周我们在社区收到的问题反馈、需求的处理进度,希望同大家一同打造小程序生态。 小程序管理后台新增“清空接口调用量”功能 小程序服务端接口的每日调用次数都具有一定的额度限制,部分小程序服务端接口的日调用次数过高可能会超出限额,影响小程序的正常运行。因此小程序管理后台新增“清空接口调用量”功能,支持开发者对小程序接口当日调用量进行清空。 1. 功能介绍 登录小程序管理后台,选择“开发—接口设置—调用额度”,即可进行清空操作 [图片] 2. 规则描述 清空后,所有服务端接口当日调用量都清零(对有“分钟级或小时级调用量限制”和“累计调用量限制”的接口不适用)每月可操作清空10次暂不支持小游戏账号 直播预告:小程序云开发实战直播课 由官方核心研发团队打造的小程序云开发实战系列直播课即将开课。更多详情,欢迎点击 活动页面 详细了解。 上周问题反馈和处理进度(07.20-07.24)修复中的问题微信小游戏视频播放出现解码失败 查看详情 FileSystemManager.statSync在真机无法使用recursive 查看详情 websocket在手机上发不出消息 查看详情 安卓端微信7.0.16版本运行小游戏问题 查看详情 自定义组件数据监听器的官方示范码上的标准库默认2.6.0,不符合最低要求的2.6.1,导致错误结果 查看详情 上传代码和预览都失败,只提示错误信息 ,没有错误出处,怎么回事 查看详情 上传视频总提示离开网页,然后就无法上传 查看详情 小程序安卓选择图片(原图)后显示图片处理中并且得到的图片被压缩处理过 查看详情 chooseMedia 文档示例代码误导性错误 查看详情 腾讯视频插件播放一部分视频不成功 查看详情 Worker线程PostMessage数据ArraBuffer,在iPhone手机接收异常 查看详情 PC小游戏上shareMessageToFriend接口传入的回调分享后不会触发 查看详情 上传代码失败,但开发工具没有报错 查看详情 小程序发布报错 查看详情 文档标点错误 查看详情 运营者账号和管理员微信扫码都登陆不了,提示没有可登陆账号 查看详情 小程序开发文档 onShareTimeline 错别字 查看详情 订阅号助手出现异常情况 查看详情 iOS 手机上不能播放视频 查看详情 【文档纠错】公众号打开小程序文档笔误 查看详情 消息分析-消息关键词暂无消息,怎么回事 查看详情 readFile api在pc端使用异常 查看详情 2020-07-14 Error:wxss编译错误,错误信息:20200714 15:43:05 查看详情 wx.saveVideoToPhotosAlbum 调用失败 查看详情 连续的input adjust-position不生效 查看详情 小程序注册无法预览服务协议 查看详情 卡劵领取页面深色模式下显示问题 查看详情 BackgroundAudioManager 通过顶部状态栏或浮标操作暂停 播放 停止 不出发事件 查看详情 微信开发者工具 查看详情 1.4版最新版开发者工具更新后调试器不能正常使用 查看详情 小程序真机调试及开发环境提示:HTTP/1.1 404 Not Found 查看详情 开发工具调试器报HTTP/1.1 404 Not Found什么原因 查看详情 需求反馈需求评估中wxBindCanvasTexture 这个 api 什么时候能支持安卓 查看详情 请问在可视化平台有什么办法可以实现玩家之间的简单交互,比如捐赠道具 查看详情 强烈建议:页面模板置顶的三个图片可以直接链接“专辑”的页面 查看详情 微信公众号能不能开发标题修改 查看详情 如何开通标签功能 查看详情 小程序直播 未来会有禁止分享的新功能吗 查看详情 页面模板添加新的文章后,建议能自动排在第一个,而不需要手动排序 查看详情 公众号的文章只能修改文字,不能修改排版吗?内容不变,修改排版不行吗 查看详情 区分未读文章与已读文章 查看详情 能不能在订阅号页删除已群发消息中,已删除的内容 查看详情 上传视频受限 查看详情 视频号的热门评论弹幕能不能让他一句话弹完整 查看详情 小程序什么时候能支持TCP Socket接口 查看详情 source map是否会合并 查看详情 希望可以给web开发者工具后面的收藏夹添加分组+备注 查看详情 微信小程序地图支持聚合显示 查看详情 公众号菜单什么时候才可以跳转专辑链接 查看详情 公众号发出后还发现标题有错能改正吗 查看详情 付费功能里能否增加免费名单 查看详情 可以直接通过房间id跳转到其他小程序的直播间吗 查看详情 小程序直播插件什么时候增加打赏功能 查看详情 能不能开放公众号发布图文消息中内容摘要修改功能 查看详情 公众号文章单篇收藏量如何浏览 查看详情 公众号群发定时时间可以开放至两天以上吗 查看详情 相关阅读功能是否能关闭 查看详情 摘要写错了能修改吗 查看详情 什么时候开通留言功能?留言功能对号主很重要,犹如微博这些平台的评论区,是否可以改成评论区开放 查看详情 能否增加修改文章题目中错别字功能 查看详情 在PC端无法选择Excel等附件上传,后续是否会提供chooseFile的API 查看详情 个人微信公众号删除的已群发图文,能不能在系统里同时删除图标呢?不要只是划一下,仍然占着页面位置 查看详情 自动回复功能上限问题可以进行调整吗 查看详情 社区近期话题获奖作者 话题1:你对于社区文章的深度和可读性有什么喜好偏向?你对于社区文章版块有什么管理建议或期待呢? 获奖用户:杨泉、圣殿骑士、Cooper、Admin²⁰²⁰、吴奕群、Hanks🇨🇳 社区礼品:【微信气泡狗晚安灯】 话题2:2020上半年再见,你有什么值得分享的经历与故事呢? 获奖用户:杨泉、纸玫瑰、子不语、胖丫么胖 社区礼品:【微信黄脸收纳套装】 话题3:关于高考你都有哪些难忘回忆?高考对于你加入当前行业工作产生过哪些影响呢? 获奖用户:圣殿骑士、aholy、citizen four、Cooper、Mixchain 俞哄哄🤳📲 社区礼品:【微信钢印行李牌】 *注:官方正版周边礼物以实物为准;获奖者需在1个月内响应社区运营专员私信沟通发货地址。 [图片] [图片] [图片] 微信团队 2020.07.31
2020-07-31 - 小程序跳转按钮:<wx-open-launch-weapp>怎么不显示?
在使用vue框架页面,按照文档先后配置好了js接口安全域名——1.6.0版本jssdk——配置config opentaglist——使用<script type="text/wxtag-template"></script>包括button,html代码如下 <wx-open-launch-weapp username="gh_ebfb250249c1" @error="launchAppError" @launch="launchAppLaunch" @ready="launchAppReady" > <script type="text/wxtag-template"> <style>.btn { display: flex;align-items: center; }</style> <button class="btn" style="border-radius: 55px;font-size:15px;color:#ffffff;font-weight:700;padding: 0 50px;height:45px;line-height: 45px;background-color: #FF9700;margin: 0 auto;" >立即打开小程序</button> </script> </wx-open-launch-weapp>
2020-07-13 - 求解小程序已开通直播,创建完直播间后,分享直播间码,显示页面不存在,急求解决?
求解小程序已开通直播,创建完直播间后,分享直播间码,显示页面不存在,急求解决!我微信号18754969177
2020-04-28 - 微信提供的直播插件(live-player-plugin)在这里查看插件版本号和appid
第一步进入小程序点击设置 [图片] 第二步点击设置后点击第三方设置 [图片] 第三步点击设置后滑动到插件管理选择【小程序直播组件】点击查看详情 [图片] 第四步进入详情后便可查看插件APPID和版本号,如下图 [图片] 小程序直播能力开启公测之后,除了接受商家申请,目前已支持服务商接入。服务商申请权限之后,可帮助商户快速实现小程序直播功能。 相比于商家需要被邀请接入,服务商可直接申请,申请步骤如下: 在申请后7个工作日内,可登陆微信开放平台查看第三方平台权限集并勾选 “小程序直播” 能力;开通后,即可登陆 “微信开放平台” (open.weixin.qq.com)勾选 “小程序直播” 第三方权限集并全网发布。
2020-03-04 - 小程序中使用three.js
小程序中使用three.js 目前小程序支持了webgl, 同时项目中有相关3D展示的需求,所以考虑将three.js移植到小程序中。 但是小程序里面没有浏览器相关的运行环境如 window,document等。要想在小程序中使用three.js需要使用相应的移植版本。https://github.com/yannliao/three.js实现了一个在 three.js 的基本移植版, 目前测试支持了 包含 BoxBufferGeometry, CircleBufferGeometry, ConeBufferGeometry, CylinderBufferGeometry, DodecahedronBufferGeometry 等基本模型,OrbitControl, GTLFLoader, OBJLoader等。 使用 下载https://github.com/yannliao/three.js项目中build目录下的three.weapp.min.js到小程序相应目录,如: [图片] 在index.wxml中加入canvas组件, 其中需要手动绑定相应的事件,用于手势控制。 [代码]<view style="height: 100%; width: 100%;" bindtouchstart="documentTouchStart" bindtouchmove="documentTouchMove" bindtouchend="documentTouchEnd" > <canvas type="webgl" id="c" style="width: 100%; height:100%;" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd" bindtouchcancel="touchCancel" bindlongtap="longTap" bindtap="tap"></canvas> </view> [代码] 在页面中引用three.js 和相应的Loader: [代码]import * as THREE from '../../libs/three.weapp.min.js' import { OrbitControls } from '../../jsm/loaders/OrbitControls' [代码] 在onLoad中获取canvas对象并注册到[代码]THREE.global[代码]中,[代码]THREE.global.registerCanvas[代码]可以传入id, 用于通过[代码]THREE.global.document.getElementById[代码]找到, 如果不传id默认使用canvas对象中的_canvasID, registerCanvas同时也会将该canvas选为当前使用canvas对象. 同时请在onUnload回调中注销canvas对象. 注意: THREE.global 中最多同时注册 5 个[代码]canvas[代码]对象, 并可以通过id找到. 注册的canvas对象, 会长驻内存, 如果不及时清理可能造成内存问题. [代码]THREE.global[代码]为[代码]three.js[代码] 的运行环境, 类似于浏览器中的window. [代码]Page({ data: { canvasId: '' }, onLoad: function () { wx.createSelectorQuery() .select('#c') .node() .exec((res) => { const canvas = THREE.global.registerCanvas(res[0].node) this.setData({ canvasId: canvas._canvasId }) // const canvas = THREE.global.registerCanvas('id_123', res[0].node) // canvas代码 }) }, onUnload: function () { THREE.global.unregisterCanvas(this.data.canvasId) // THREE.global.unregisterCanvas(res[0].node) // THREE.global.clearCanvas() }, [代码] 注册相关touch事件. 由于小程序架构原因, 需要手动绑定事件到THREE.global.canvas或者THREE.global.document上. 可以使用[代码]THREE.global.touchEventHandlerFactory('canvas', 'touchstart')[代码] 生成小程序的事件回调函数,触发默认canvas对象上的touch事件. [代码] { touchStart(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e) }, touchMove(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e) }, touchEnd(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e) }, } [代码] 编写three.js代码, 小程序运行环境中没有requestAnimationFrame, 目前可以使用canvas.requestAnimationFrame之后会将requestAnimationFrame注入到THREE.global中. [代码]const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000); camera.position.z = 500; const scene = new THREE.Scene(); scene.background = new THREE.Color(0xAAAAAA); const renderer = new THREE.WebGLRenderer({ antialias: true }); const controls = new OrbitControls(camera, renderer.domElement); // controls.enableDamping = true; // controls.dampingFactor = 0.25; // controls.enableZoom = false; camera.position.set(200, 200, 500); controls.update(); const geometry = new THREE.BoxBufferGeometry(200, 200, 200); const texture = new THREE.TextureLoader().load('./pikachu.png'); const material = new THREE.MeshBasicMaterial({ map: texture }); // const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio); // renderer.setSize(canvas.width, canvas.height); function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(canvas.width, canvas.height); } function render() { canvas.requestAnimationFrame(render); // mesh.rotation.x += 0.005; // mesh.rotation.y += 0.01; controls.update(); renderer.render(scene, camera); } render() [代码] 完整示例: index.js [代码]import * as THREE from '../../libs/three.weapp.min.js' import { OrbitControls } from '../../jsm/loaders/OrbitControls' Page({ data: {}, onLoad: function () { wx.createSelectorQuery() .select('#c') .node() .exec((res) => { const canvas = THREE.global.registerCanvas(res[0].node) this.setData({ canvasId: canvas._canvasId }) const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000); camera.position.z = 500; const scene = new THREE.Scene(); scene.background = new THREE.Color(0xAAAAAA); const renderer = new THREE.WebGLRenderer({ antialias: true }); const controls = new OrbitControls(camera, renderer.domElement); // controls.enableDamping = true; // controls.dampingFactor = 0.25; // controls.enableZoom = false; camera.position.set(200, 200, 500); controls.update(); const geometry = new THREE.BoxBufferGeometry(200, 200, 200); const texture = new THREE.TextureLoader().load('./pikachu.png'); const material = new THREE.MeshBasicMaterial({ map: texture }); // const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio); // renderer.setSize(canvas.width, canvas.height); function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(canvas.width, canvas.height); } function render() { canvas.requestAnimationFrame(render); // mesh.rotation.x += 0.005; // mesh.rotation.y += 0.01; controls.update(); renderer.render(scene, camera); } render() }) }, onUnload: function () { THREE.global.unregisterCanvas(this.data.canvasId) }, touchStart(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e) }, touchMove(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e) }, touchEnd(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e) }, touchCancel(e) { // console.log('canvas', e) }, longTap(e) { // console.log('canvas', e) }, tap(e) { // console.log('canvas', e) }, documentTouchStart(e) { // console.log('document',e) }, documentTouchMove(e) { // console.log('document',e) }, documentTouchEnd(e) { // console.log('document',e) }, }) [代码] index.wxml [代码]<view style="height: 100%; width: 100%;" bindtouchstart="documentTouchStart" bindtouchmove="documentTouchMove" bindtouchend="documentTouchEnd" > <canvas type="webgl" id="c" style="width: 100%; height:100%;" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd" bindtouchcancel="touchCancel" bindlongtap="longTap" bindtap="tap"></canvas> </view> [代码] 其他 全部示例在 https://github.com/yannliao/threejs-example three.js 库 https://github.com/yannliao/three.js loader 组件在 threejs-example 中的 jsm 目录中 欢迎提交PR和issue
2019-10-20 - 小程序直播违规有什么处罚方式?
违规了,一般怎么处理,严重的是否会被封?是直播功能被封还是不能创建直播间?又或者影响小程序账号?
2020-03-26 - 小程序内用户帐号登录规范调整和优化建议
为更好地保护用户隐私信息,优化用户体验,平台将会对小程序内的帐号登录功能进行规范。本公告所称“帐号登录功能”是指开发者在小程序内提供帐号登录功能,包括但不限于进行的手机号登录,getuserinfo形式登录、邮箱登录等形式。具体规范要求如下: 1.服务范围开放的小程序 对于用户注册流程是对外开放、无需验证特定范围用户,且注册后即可提供线上服务的小程序,不得在用户清楚知悉、了解小程序的功能之前,要求用户进行帐号登录。 包括但不限于打开小程序后立即跳转提示登录或打开小程序后立即强制弹窗要求登录,都属于违反上述要求的情况; 以下反面示例,在用户打开小程序后立刻弹出授权登录页; [图片] 建议修改为如下正面示例形式:在体验小程序功能后,用户主动点击登录按钮后触发登录流程,且为用户提供暂不登录选项。 [图片] 2.服务范围特定的小程序 对于客观上服务范围特定、未完全开放用户注册,需通过更多方式完成身份验证后才能提供服务的小程序,可以直接引导用户进行帐号登录。例如为学校系统、员工系统、社保卡信息系统等提供服务的小程序; 下图案例为正面示例:校友管理系统,符合规范要求。 [图片] 3.仅提供注册功能小程序 对于线上仅提供注册功能,其他服务均需以其他方式提供的小程序,可在说明要求使用帐号登录功能的原因后,引导用户进行帐号注册或帐号登录。如ETC注册申请、信用卡申请; 如下反面示例,用户在进入时未获取任何信息,首页直接强制弹框要求登录注册ETC,这是不符合规范的。 [图片] 建议修改为如下正面示例所示形式:允许在首页说明注册功能后,提供登录或注册按钮供用户主动选择点击登录。 [图片] 4.提供可取消或拒绝登录选项 任何小程序调用帐号登录功能,应当为用户清晰提供可取消或拒绝的选项按钮,不得以任何方式强制用户进行帐号登录。 如下图所示反面示例,到需要登录环节直接跳转登录页面,用户只能选择点击登录或退出小程序,这不符合登录规范要求。 [图片] 建议修改为下图正面示例形式,在需帐号登录的环节,为用户主动点击登录,并提供可取消按钮,不强制登录。 [图片] 针对以上登录规范要求,平台希望开发者们能相应地调整小程序的帐号登录功能。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。
2019-07-20 - 微信小程序直播资料整理
可以通过此脑图大概了解小程序直播插件发展过程:http://naotu.baidu.com/file/597625fbd8659aa87e54143df1ed7f39?token=5bad276062c05585 [图片] 小程序直播组件是微信给开发者提供的实时视频直播工具,可以帮助开发者快速通过小程序向用户提供优质的直播内容,在小程序内流畅完成购买交易闭环,提升转化率; 小程序直播组件包括观众端、主播端及后台管理端,其中观众端提供拉流、实时互动、订阅提醒、商品购买等能力,主播端提供开播、推流、音视频效果优化等能力,后台管理端则负责直播房间、商品货架以及营销活动配置等。 【开通条件】 https://mp.weixin.qq.com/s/oqNdNEnRblEzwR_61d3vHA 满足以下条件的电商平台、自营商家,即有机会被邀请到小程序直播公测中来: (同时满足以下1、2、3条件,加上4、5、6条件的其中之一即可。) 1. 满足小程序18个开放类目(包括:电商平台、商家自营-百货、食品、初级食用农产品、酒/盐、图书报刊/音像/影视/游戏/动漫、汽车/其他交通工具的配件、服装/鞋/箱包、玩具/母婴用品(不含食品)、家电/数码/手机、美妆/洗护、珠宝/饰品/眼镜/钟表、运动/户外/乐器、鲜花/园艺/工艺品、家居/家饰/家纺、汽车内饰/外饰、办公/文具、机械/电子器件) 2. 主体下小程序近半年没有严重违规 3. 小程序近90天存在支付行为 4. 主体下公众号累计粉丝数大于100 5. 主体下小程序连续7日日活跃用户数大于100 6. 主体在微信生态内近一年广告投放实际消耗金额大于1w 【第三方平台(小程序服务商)】 https://developers.weixin.qq.com/community/develop/doc/000a06014745f00d95f9e03d951401?from=groupmessage&isappinstalled=0 服务商接入指引 具体接入指引请参考《【小程序直播】服务商接入指引》,以下为服务商接入步骤。 1. 权限申请 1) 在问卷《服务商“小程序直播”接入申请》填写相关信息并等待权限开通,发送申请后7个工作日内,可登陆微信开放平台查看第三方平台权限集并勾选 “小程序直播” 能力; 2) 开通后,即可登陆 “微信开放平台” (open.weixin.qq.com)勾选 “小程序直播” 第三方权限集并全网发布; 2.功能开发 小程序直播需要实现【直播组件】与【后台API】两个部分,其中组件部分需要在小程序中进行配置开发。 具体开发文档,请参考《小程序直播组件接入指引》。 【看点直播与小程序直播区别】 看点直播是不是腾讯官方?有什么区别? 小程序直播是腾讯WXG(也就是微信团队)公测推出的能力,为商家提供的经营工具,可在商家自有的小程序中实现直播互动与商品销售闭环。优点是可以直接内嵌到商家小程序,和公众号打通;直播吸引的流量都沉淀在商家自有小程序,不用跳转其他渠道,有利于形成私域流量,转化率高。 腾讯看点直播是腾讯PCG(平台与内容事业群,做QQ、腾讯视频的事业群)推出的直播产品,有App、小程序端、小程序端是基于微信小程序的开发能力开发的,商家必须通服务商才能接入,无法直接在商家自有的小程序内闭环交易。(需要直播服务商提交资料开通,服务费用599半年。) 【2020-05-22更新】 近期已上线新功能:评论管理,支持对单个用户进行禁言,与敏感词设置。 未全面开放新功能:直播间可以直接发放微信代金券,目前内测中,敬请期待! [图片] 【2020-06-02更新】 小程序后台直播功能接口支持进一步提升,可以通过接口添加直播间与商品管理,在没有涉及抽奖情况下及推送商品功能情况下,已经可以完整脱离小程序后台完成直播相关设置,对于一些服务商来说是一个不错的消息,按这个进度,下一步官方应该优先支持推送商品及抽奖活动设置配置接口的开放。 创建直播间文档:https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/live-player-plugin.html 拉到三/9 商品接口文档:https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/commodity-api.html [图片] 【2020-06-09更新】 直播间优惠券功能已经全面开放,很多玩法将会产生。 直播间领取的券可直接插入微信卡包(代表着可以提醒一次)。 如何配置:http://note.youdao.com/noteshare?id=afbff99580c4540cc011e3ed7ab5fbcf&sub=CDE480AAEC07437CB58902E0FF42C329 [图片][图片]
2020-06-09 - 小程序涉及iOS虚拟支付修改指引
近期,开发者社区几乎隔三差五就被“如何整改虚拟支付”霸占。社区君在大家的强烈诉求下,特地整理了大家审核过程中经常遇到的问题。 问题1 虚拟支付到底是什么? 虚拟支付是指购买非实物商品。比如:VIP会员、充值、课程、音视频等虚拟产品。 问题2 为什么关闭了虚拟支付功能,还是不能过审? 部分开发者在提审时,仅在后台关闭了支付接口,但在前端仍保留购买/支付/订阅等功能或按钮,在iOS端的界面也都会有类似“订阅课程、开通VIP”等标识,这类呈现货架价格、提示购买的行为将被禁止。 比如: [图片] 这类展示货架(价格标签)的,将被拒绝 问题3 为什么已经去掉了引导关注公众号,还是不能过审? 请勿试图引导用户到微信公众号、H5、App或是任何外链进行付费。任何为用户提供前往支付流程的路径/文案,都将被拒绝。 比如: [图片] 这类通过【客服消息】-【个人微信号】,完成购买行为,将会被拒绝 [图片] 这类引导到H5,完成购买的行为,将会被拒绝 [图片] 这类引导到公众号,完成购买的行为,将会被拒绝 以下提供3种修改示例,仅供参考。 1、关闭iOS付费通道。 以小程序【腾讯视频】为例,iOS端关闭会员卡开通渠道,原【开通VIP】按钮,现仅显示【你还不是会员】,不再提供会员开通服务。比如: [图片] 2、所有付费内容均更改为免费。 以小程序【有道精品课】为例,iOS端将所有付费内容更改为免费,原【付费解锁】,现已完全免费使用。比如: [图片] 3、前端直接拦截提示不可服务。 以小程序【网易公开课精品】、【腾讯视频VIP】为例,iOS端点击订阅、开通VIP服务,前端直接拦截提示不可服务。但,如果页面呈现货架价格,提示购买行为的(如问题2),将会被拒绝。比如: [图片]
2018-08-17 - 如何获取视频的某一帧
- 需求的场景描述(希望解决的问题) 如何获取视频的某一帧 - 希望提供的能力 获取视频的某一帧
2019-05-31 - 社区每周|云开发原生支持微信支付、开发者工具更新及上周问题反馈(05.04-05.08)
各位微信开发者: 以下是小程序云开发原生支持微信支付、微信开发者工具更新及上周我们在社区收到的问题反馈、需求的处理进度,希望同大家一同打造小程序生态。 小程序云开发原生支持微信支付小程序·云开发新推出原生微信支付能力支持,拥有简单、安全、免签名、免证书、支付结果可回调云函数的特点。开发者在云开发控制台可直接绑定微信支付商户,在绑定完成后可在云开发中原生接入微信支付。 更多详情,欢迎查看原公告 《小程序云开发原生支持微信支付》 详细了解。接入过程如有疑问或建议,欢迎在 #云开发 社区与我们一起交流。 微信开发者工具 1.03.2005141 RC 更新微信开发者工具RC版更新,可查看 更新日志 了解详细更新信息。 关于开发者工具,可在 #微信开发者工具 主页中获取更多信息与交流讨论。 上周问题反馈和处理进度(05.04-05.08)已修复的问题注册时简介内容保存失败的问题 查看详情 修复中的问题小程序只有title和底部button的问题 查看详情 小游戏跳转直接小退的问题 查看详情 开发者指南中体验小程序源码编译报错的问题 查看详情 使用navigateTo 通过 eventChannel 向被打开页面传送数据时被打开页面无法接收到数据的问题 查看详情 Guide文档修正 查看详情 自定义picker省市选择的问题 查看详情 Mac 客户端 textarea Bugnnx 的问题 查看详情 直播订阅组件 subscribe1.0.8 以及以上阻止事件冒泡的问题 查看详情 iOS 7.0.12 cavnas 设置 font-weight 没有生效的问题 查看详情 小程序直播组件,直播间商品货架无法收起(详情请看视频)的问题 查看详情 vivo手机黑屏的问题 查看详情 input组件的type动态切换键盘类型在某些iOS手机上无效的问题 查看详情 iOS跳转闪退问题 查看详情 安卓微信7.0.14部分机型无法显示全屏的问题 查看详情 【业务数据监控】查看开发文档指引链接错误的问题 查看详情 小程序直播 删除商品库中商品接口 报 not found 的问题 查看详情 微信小游戏的开放数据入口文件index出了问题 查看详情 小程序开发文档设计指南中按钮用词错误的问题 查看详情 错别字 查看详情 官方文档 CanvasContext.fillText 有误的问题 查看详情 需求反馈需求评估中帧同步开发中进入房间后更新个人信息的需求 查看详情 商品直播标记的需求 查看详情 开发订阅号被动回复用户消息的个数的需求 查看详情 小程序直播主播开播提醒功能的需求 查看详情 公众号评论接口配额权限的需求 查看详情 movable-view 的 scale 的缩放中心以双指缩放为中心点的需求 查看详情 wx.scanCode 获取非本小程序二维码带参的需求 查看详情 wx.request 请求异常情况是否有具体说明 查看详情 公众号识别图片并自动回复的需求 查看详情 WeUI基础控件库中缺少底部tab图标尺寸规范的需求 查看详情 微信团队 2020.05.15
2020-05-15 - 社区每周 | 上周社区功能优化更新(09.03-09.07)
各位微信的开发者: 大家下午好。 今天主要跟大家介绍社区的能力更新:新增企业主页创建/登录、新增插件板块。 1 企业主页创建/登录 以往社区所有的登录是以个人开发者的微信身份来登录的,我们收到了一些企业的反馈,希望可以在社区有企业开发者的身份在社区进行回答或分享。 于是我们近期在社区增加了企业主页的创建和登录流程,希望可以帮助一些服务商在这里分享,提高自身的影响力。 另外,可提前预告,社区后续会对个人开发者、企业开发者有相应的激励措施。 企业主页创建/登录指引流程,请见: https://developers.weixin.qq.com/community/develop/doc/000286982bc358be39578961f51008 创建成功后,你可以进行如下操作: 1. 编辑企业主页的资料,一个月可以有3次修改机会; 2. 可修改管理员; 3. 添加运营人员,最多可添加10个,运营人员可以企业身份登录企业主页,在社区进行操作、回答; 4. 添加企业的小程序或者插件案例,小程序和插件案例可各添加3个; 5. 以企业主页在社区所有的回答、分享、评论与回复,将记录在该企业上; 6. 在社区获得的点赞也会记录在该企业上。 [图片] 2 社区增加插件板块,可添加插件案例 插件上线以来,我们陆续收到很多开发者的提问: “哪里可以看到全部插件?” “我怎么知道我的行业有哪些好用的插件”, 插件开发者也希望知道怎么让更多的开发者发现我的插件。所以,我们在社区增加插件板块。 插件开发者(个人主页/企业主页)编辑自己的案例,详细介绍插件的适用行业、服务场景。同时,小程序的开发者也能在社区内通过帖子或查找,找到自己需要的插件。 在社区右上角,可以进入插件板块。 1. 可以通过搜索查找插件 2. 可以在页面内发起插件分享(分享的案例可作为个人主页/企业主页的案例) 3. 可以查看别人分享的案例。 [图片] 3 如何添加插件? 方式一:在个人主页/企业主页添加插件案例 [图片] 方式二:在插件页面,点击“发起分享” [图片] 由于我们上周负责值班的开发哥哥生病住院了,关于上周反馈的bug和需求总结暂时delay,我们会在下周一并反馈给大家。感谢大家谅解。 大家关于社区的需求反馈,以及大家期望的社区激励,可以通过评论留言给我们,我们会采纳大家合理的建议,共同营造良好的社区氛围,共建小程序开发者生态。 微信团队 2018.09.13
2018-09-14