
WebDAV client for uni-app
https://github.com/kytrun/webdav-client-uniapp
Fork 自 webdav-client,添加了 uni.request 作为 axios adaper,目前基本可用,没有测试全部功能。需要注意的是坚果云提供的 WebDAV 并非标准的 WebDAV,部分接口有问题可能在其他服务上表现正常。
下载 https://github.com/kytrun/webdav-client-uniapp/blob/master/dist/uniapp/webdav.js 文件在 uni-app 项目中以 ES module 导入使用。
坚果云 WebDAV 示例:
import {
AuthType,
createClient
} from "@/deps/webdav"
const client = createClient(
"https://dav.jianguoyun.com/dav/test/", {
authType: AuthType.Password,
username: `${username}`,
password: `${password}`
})
client.getFileContents("test.txt", {
format: "text"
})
.then(data => console.log(data))
.catch(e => console.log(e))
更多接口见项目文档。
https://github.com/kytrun/webdav-client-uniapp
Fork 自 webdav-client,添加了 uni.request 作为 axios adaper,目前基本可用,没有测试全部功能。需要注意的是坚果云提供的 WebDAV 并非标准的 WebDAV,部分接口有问题可能在其他服务上表现正常。
下载 https://github.com/kytrun/webdav-client-uniapp/blob/master/dist/uniapp/webdav.js 文件在 uni-app 项目中以 ES module 导入使用。
坚果云 WebDAV 示例:
import {
AuthType,
createClient
} from "@/deps/webdav"
const client = createClient(
"https://dav.jianguoyun.com/dav/test/", {
authType: AuthType.Password,
username: `${username}`,
password: `${password}`
})
client.getFileContents("test.txt", {
format: "text"
})
.then(data => console.log(data))
.catch(e => console.log(e))
更多接口见项目文档。
收起阅读 »
语音播报-前台后台离线推送语音播报、到账xx元、收款播报、自定义推送铃(ios)
语音播报-前台后台离线推送语音播报、到账xx元、收款播报、自定义推送铃(ios):https://ext.dcloud.net.cn/plugin?id=8452
语音播报-前台后台离线推送语音播报、到账xx元、收款播报、自定义推送铃(ios):https://ext.dcloud.net.cn/plugin?id=8452

DCloud平台用户信息修改,插件作者未更新
更新用户信息后,为什么发布的插件作者那里没有变,点击之后还提示找不到此用户
更新用户信息后,为什么发布的插件作者那里没有变,点击之后还提示找不到此用户

希望可以在js里面输入未导入的变量时可以自动导入
希望可以在js里面输入未导入的变量时可以自动导入,像webstorm一样,,不然第一次写的都只能去复制,这样可以省去更多的时间;
并且希望可以在scss文件里面也可以使用格式化功能,现在无法使用格式化功能,只能自己手动排版,非常麻烦!
希望可以在js里面输入未导入的变量时可以自动导入,像webstorm一样,,不然第一次写的都只能去复制,这样可以省去更多的时间;
并且希望可以在scss文件里面也可以使用格式化功能,现在无法使用格式化功能,只能自己手动排版,非常麻烦!

迫于新版HX诸多问题,放一个我现在一直在用的3.3.13版本
迫于新版HX诸多问题,看到有不少同学在找历史版本,在这放一个现在一直在用的3.3.13版本吧

图片在容器下自由缩放旋转拖动Demo
先实现如下效果,图片可自由缩放旋转删除移动。通过两个点控制。

实现效果Demo如下:
这是Demo代码,可直接复制到项目中运行看效果。
支持小程序、APP。
一开始考虑使用的movable-area+movable-view。但因为需要先禁用移动,当点击的是右下角旋转按钮时才允许移动,所以需要在touchstart里面disabled改为false,然后再touchend中再次修改为禁止移动。
但movable-view不支持在touchstart中修改disabled立刻生效,只能在下次点击时生效,所以使用view+css实现。
<template>
<view class="page-body">
<view class="move-area">
<view v-if="!isHide" class="move-box" style="transform-origin:center;"
@touchmove="itemMove" @touchstart="itemtouch" @touchend="itemend"
:style="{transform: 'translate(' + itemX + 'px, ' + itemY +'px) rotate('+tmpRotate+'deg) scale('+tmpScale+')'}">
<view class="opt-icon del-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(2)"></view>
<view class="opt-icon edit-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(1)"></view>
<view class="move-body" @touchstart="startMode(0)">这是内容</view>
</view>
</view>
</view>
</template>
<script>
const nearDg = 7; // 用于控制当近似垂直或水平时,视为垂直或水平的容差。
let lastX = 0, lastY = 0;
export default {
data() {
return {
itemX: 0,
itemY: 0,
realCenterX: 0,
realCenterY: 0,
halfWidth: 75, // px
halfHeight: 75, // px
realWidth: 0,
realHeight: 0,
offset: 12.5, // px
touchMode: 0, // 0移动 1缩放旋转 2删除
startX: 0,
startY: 0,
startAngle: 0,
startDist: 0,
tmpRotate: 0,
initRotate: 0,
tmpScale: 1,
initScale: 1,
isHide: false,
}
},
onLoad:function(){
this.itemX = 70;
this.itemY = 100;
},
mounted:function(){
let view = uni.createSelectorQuery().in(this).select(".move-box");
view.boundingClientRect(data => {
this.realWidth = data.width;
this.realHeight = data.height;
this.realCenterX = data.right - data.width / 2;
this.realCenterY = data.bottom - data.height / 2;
}).exec();
},
methods: {
itemMove: function(e){
let curX = e.touches[0].clientX;
let curY = e.touches[0].clientY;
if(this.touchMode === 0){
this.itemX += curX - lastX;
this.itemY += curY - lastY;
this.realCenterX += curX - lastX;
this.realCenterY += curY - lastY;
lastX = curX;
lastY = curY;
}
if(this.touchMode !== 1)return;
let angle = this.angle(this.realCenterX, this.realCenterY, curX, curY);
if(this.startAngle === 0){
this.startX = curX;
this.startY = curY;
this.startAngle = angle;
this.startDist = Math.hypot(this.startX - this.realCenterX, this.startY - this.realCenterY);
}
else{
let rotate = this.initRotate + Math.round(angle - this.startAngle);
let leftDg = Math.abs(rotate % 90);
if(leftDg < nearDg || 90 - leftDg < nearDg){
rotate = Math.round(rotate / 90) * 90;
}
this.tmpRotate = rotate;
let curDist = Math.hypot(curX - this.realCenterX, curY - this.realCenterY);
this.tmpScale = this.initScale * curDist / this.startDist;
}
},
itemtouch: function(e){
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
},
itemend: function(e){
this.startAngle = 0;
this.initRotate = this.tmpRotate;
this.initScale = this.tmpScale;
},
startMode: function(mode){
this.touchMode = mode;
if(this.touchMode === 2){
this.isHide = true;
}
},
angle: function(cx, cy, ex, ey){
var dy = ey - cy;
var dx = ex - cx;
var theta = Math.atan2(dy, dx); // range (-PI, PI]
theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
if (theta < 0) theta = 360 + theta; // range [0, 360)
return theta;
}
}
}
</script>
<style lang="scss">
.page-body{
padding: 5px;
}
.move-area{
width: 365px;
height: 500px;
}
.move-box{
position: relative;
width: 175px;
height: 175px;
padding: 1px;
}
.opt-icon{
position: absolute;
width: 25px;
height: 25px;
border-radius: 50%;
}
.del-icon{
background-color: red;
left: 0;
top: 0;
}
.edit-icon{
background-color: green;
right: 0;
bottom: 0;
}
.move-body{
margin: 12.5px;
width: 150px;
height: 150px;
border: 2px solid green;
}
</style>
先实现如下效果,图片可自由缩放旋转删除移动。通过两个点控制。
实现效果Demo如下:
这是Demo代码,可直接复制到项目中运行看效果。
支持小程序、APP。
一开始考虑使用的movable-area+movable-view。但因为需要先禁用移动,当点击的是右下角旋转按钮时才允许移动,所以需要在touchstart里面disabled改为false,然后再touchend中再次修改为禁止移动。
但movable-view不支持在touchstart中修改disabled立刻生效,只能在下次点击时生效,所以使用view+css实现。
<template>
<view class="page-body">
<view class="move-area">
<view v-if="!isHide" class="move-box" style="transform-origin:center;"
@touchmove="itemMove" @touchstart="itemtouch" @touchend="itemend"
:style="{transform: 'translate(' + itemX + 'px, ' + itemY +'px) rotate('+tmpRotate+'deg) scale('+tmpScale+')'}">
<view class="opt-icon del-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(2)"></view>
<view class="opt-icon edit-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(1)"></view>
<view class="move-body" @touchstart="startMode(0)">这是内容</view>
</view>
</view>
</view>
</template>
<script>
const nearDg = 7; // 用于控制当近似垂直或水平时,视为垂直或水平的容差。
let lastX = 0, lastY = 0;
export default {
data() {
return {
itemX: 0,
itemY: 0,
realCenterX: 0,
realCenterY: 0,
halfWidth: 75, // px
halfHeight: 75, // px
realWidth: 0,
realHeight: 0,
offset: 12.5, // px
touchMode: 0, // 0移动 1缩放旋转 2删除
startX: 0,
startY: 0,
startAngle: 0,
startDist: 0,
tmpRotate: 0,
initRotate: 0,
tmpScale: 1,
initScale: 1,
isHide: false,
}
},
onLoad:function(){
this.itemX = 70;
this.itemY = 100;
},
mounted:function(){
let view = uni.createSelectorQuery().in(this).select(".move-box");
view.boundingClientRect(data => {
this.realWidth = data.width;
this.realHeight = data.height;
this.realCenterX = data.right - data.width / 2;
this.realCenterY = data.bottom - data.height / 2;
}).exec();
},
methods: {
itemMove: function(e){
let curX = e.touches[0].clientX;
let curY = e.touches[0].clientY;
if(this.touchMode === 0){
this.itemX += curX - lastX;
this.itemY += curY - lastY;
this.realCenterX += curX - lastX;
this.realCenterY += curY - lastY;
lastX = curX;
lastY = curY;
}
if(this.touchMode !== 1)return;
let angle = this.angle(this.realCenterX, this.realCenterY, curX, curY);
if(this.startAngle === 0){
this.startX = curX;
this.startY = curY;
this.startAngle = angle;
this.startDist = Math.hypot(this.startX - this.realCenterX, this.startY - this.realCenterY);
}
else{
let rotate = this.initRotate + Math.round(angle - this.startAngle);
let leftDg = Math.abs(rotate % 90);
if(leftDg < nearDg || 90 - leftDg < nearDg){
rotate = Math.round(rotate / 90) * 90;
}
this.tmpRotate = rotate;
let curDist = Math.hypot(curX - this.realCenterX, curY - this.realCenterY);
this.tmpScale = this.initScale * curDist / this.startDist;
}
},
itemtouch: function(e){
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
},
itemend: function(e){
this.startAngle = 0;
this.initRotate = this.tmpRotate;
this.initScale = this.tmpScale;
},
startMode: function(mode){
this.touchMode = mode;
if(this.touchMode === 2){
this.isHide = true;
}
},
angle: function(cx, cy, ex, ey){
var dy = ey - cy;
var dx = ex - cx;
var theta = Math.atan2(dy, dx); // range (-PI, PI]
theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
if (theta < 0) theta = 360 + theta; // range [0, 360)
return theta;
}
}
}
</script>
<style lang="scss">
.page-body{
padding: 5px;
}
.move-area{
width: 365px;
height: 500px;
}
.move-box{
position: relative;
width: 175px;
height: 175px;
padding: 1px;
}
.opt-icon{
position: absolute;
width: 25px;
height: 25px;
border-radius: 50%;
}
.del-icon{
background-color: red;
left: 0;
top: 0;
}
.edit-icon{
background-color: green;
right: 0;
bottom: 0;
}
.move-body{
margin: 12.5px;
width: 150px;
height: 150px;
border: 2px solid green;
}
</style>
收起阅读 »

Native.js 实现 unaipp 离线推送
<template>
<view class="content">
<view class="text-area">
<button type="primary" size="mini" @tap="pushMsg()">推送</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
onLoad() {
// #ifdef APP-PLUS
// 获取客户端标识信息
// var info = plus.push.getClientInfo();
// console.log( JSON.stringify( info ) );
// 添加监听从系统消息中心点击某条消息启动应用事件
plus.push.addEventListener('click', function(msg){
// 分析msg.payload处理业务逻辑
console.log( '点击通知栏: ',msg.payload, msg.content, msg.payload.url )
setTimeout(() => {
uni.navigateTo({
url: msg.payload.url
});
}, 1000);
}, false );
// #endif
},
methods: {
pushMsg(){
plus.nativeUI.toast('推送消息测试')
// createMessage
var options = {
cover:false,
// icon: '',
// title: '1111',
// subtitle: '1111',
};
plus.push.createMessage('你收到一条新消息', {
type: 'LocalMSG',
url: '../test/test'
}, options);
//设置角标
plus.runtime.setBadgeNumber(0);
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
<template>
<view class="content">
<view class="text-area">
<button type="primary" size="mini" @tap="pushMsg()">推送</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
onLoad() {
// #ifdef APP-PLUS
// 获取客户端标识信息
// var info = plus.push.getClientInfo();
// console.log( JSON.stringify( info ) );
// 添加监听从系统消息中心点击某条消息启动应用事件
plus.push.addEventListener('click', function(msg){
// 分析msg.payload处理业务逻辑
console.log( '点击通知栏: ',msg.payload, msg.content, msg.payload.url )
setTimeout(() => {
uni.navigateTo({
url: msg.payload.url
});
}, 1000);
}, false );
// #endif
},
methods: {
pushMsg(){
plus.nativeUI.toast('推送消息测试')
// createMessage
var options = {
cover:false,
// icon: '',
// title: '1111',
// subtitle: '1111',
};
plus.push.createMessage('你收到一条新消息', {
type: 'LocalMSG',
url: '../test/test'
}, options);
//设置角标
plus.runtime.setBadgeNumber(0);
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
收起阅读 »

Native.js 实现 unaipp 截图
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<image :src="capture" ref="img"></image>
<view class="text-area">
<text class="title">{{title}}</text>
</view>
<button @tap="captureWebview"> 截图 </button>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello word !',
capture: '',
}
},
onLoad() {
// #ifdef APP-PLUS
console.log('系统: ', plus.os.name)
// console.log('version:' + plus.runtime.versionCode);
// 监听设备网络状态变化事件
plus.globalEvent.addEventListener('netchange', function(){
console.log('网络')
});
// #endif
},
methods: {
// 截屏绘制
captureWebview() {
var _this = this
var ws=plus.webview.getTopWebview();
var bitmap = new plus.nativeObj.Bitmap('test');
// 将webview内容绘制到Bitmap对象中
ws.draw(bitmap,function(){
console.log('截屏绘制图片成功');
bitmap.save( "_doc/a.jpg",{},
function(e){
console.log('保存图片成功:'+JSON.stringify(e));
//保存到相册
plus.gallery.save(e.target,(e)=>{
plus.nativeUI.toast("保存成功")
})
},
function(e){
console.log('保存图片失败:'+JSON.stringify(e));
});
_this.capture = bitmap.toBase64Data()
// bitmap.save('_doc/b.png',
// { "overwrite": false, "format": "png", "quality": 50 },
// function(e){
// //保存到相册
// plus.gallery.save(e.target,(e)=>{
// plus.nativeUI.toast("保存成功")
// })
// }, function(error){
// plus.nativeUI.toast("保存失败")
// console.log("code:" + error.code + ";msg:" + error.message)
// })
//保存到相册后,回收Bitmap图片内存
bitmap.recycle();
},function(e){
console.log('截屏绘制图片失败:'+JSON.stringify(e));
});
},
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<image :src="capture" ref="img"></image>
<view class="text-area">
<text class="title">{{title}}</text>
</view>
<button @tap="captureWebview"> 截图 </button>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello word !',
capture: '',
}
},
onLoad() {
// #ifdef APP-PLUS
console.log('系统: ', plus.os.name)
// console.log('version:' + plus.runtime.versionCode);
// 监听设备网络状态变化事件
plus.globalEvent.addEventListener('netchange', function(){
console.log('网络')
});
// #endif
},
methods: {
// 截屏绘制
captureWebview() {
var _this = this
var ws=plus.webview.getTopWebview();
var bitmap = new plus.nativeObj.Bitmap('test');
// 将webview内容绘制到Bitmap对象中
ws.draw(bitmap,function(){
console.log('截屏绘制图片成功');
bitmap.save( "_doc/a.jpg",{},
function(e){
console.log('保存图片成功:'+JSON.stringify(e));
//保存到相册
plus.gallery.save(e.target,(e)=>{
plus.nativeUI.toast("保存成功")
})
},
function(e){
console.log('保存图片失败:'+JSON.stringify(e));
});
_this.capture = bitmap.toBase64Data()
// bitmap.save('_doc/b.png',
// { "overwrite": false, "format": "png", "quality": 50 },
// function(e){
// //保存到相册
// plus.gallery.save(e.target,(e)=>{
// plus.nativeUI.toast("保存成功")
// })
// }, function(error){
// plus.nativeUI.toast("保存失败")
// console.log("code:" + error.code + ";msg:" + error.message)
// })
//保存到相册后,回收Bitmap图片内存
bitmap.recycle();
},function(e){
console.log('截屏绘制图片失败:'+JSON.stringify(e));
});
},
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
收起阅读 »

我写了一个基于Typescript跨多平台的nodejs框架
现在支持私有部署同时也支持serverless各个平台的nodejs框架多么?答案是否定的,在2022年越来越多的企业会选择serverless来部署自己的应用,因为它足够轻巧省去了运维的成本,编写一个API可能只需要几行代码,越来越多的平台也推出了自己的云数据库以及云消息队列,我们前端开发编写后端api不再是一个头疼的事情。所以serverless是未来,为此我写了一个框架,它会在web服务器中运行也可以在serverless环境运行,在serverless中我会优先支持unicloud,我将使用这款框架参加2022年的dcloud插件大赛。
简单聊聊这款框架,我为什么要创建一款nodejs框架?
为什么要造轮子
在sword团队中我们使用unicloud构建应用程序,采用了CQRS,我们所有的写操作都由unicloud的云函数完成,但是你会发现在unicloud社区优秀的框架有很多,它们提供了url处理以及逻辑的分发,还有一些特色的框架也提供了诸如上传,和unicloud的部分特性封装,在我看来,这样的框架没有真正解决开发者的问题,比如:
- 我想现在不想用unicloud,我想用传统服务器运行函数
- 我想和云平台解耦,我希望我的云函数的特性和功能实现和某一平台无关
- 我想使用ts开发
- 我想有IDE强力支持
- 我想使用一些开箱即用的方案,比如说hook,又比如HMR
- 我想使用ES开发nodejs程序,使用先进的技术对程序进行捆绑(treeshaking...)
- 如果要满足上面的特性,那么只有midway.js了,midway.js很酷,但是它并没有unicloud的faas插件,而且我希望框架能够让sword团队更好的构建程序,所以我们造一个轮子,来解决这些事情。
简单说说这个框架的技术栈
这款框架是我目前最得意的作品, 但是时间太仓促, 它的功能实现肯定不是最好的, 所以我需要各位的支持测试, 而且我也希望能拿到今年的插件大赛的名次; 去年拿到了三等奖也是我写的一款小程序, 今年带来的这个作品含金量非常高哈哈哈.
整个框架都是ts, 我使用了monorepo架构基于pnpm, 所有的源代码你都可以在packages中找到, 框架核心就是runtime那个文件夹. 整个框架最主要的2个部分就是cli + runtime, cli主要做多端的编译功能, 没错cli不仅做了打包还做了编译, 而且打包也是分环境的, 开发环境我是用的是swc (rust) 进行打包, 生产程序是使用esbuild (go), 所以极速是这个框架的特点, 不仅如此, runtime部分我实现了一个简易的异步依赖调度器使之不同平台, 都可以动态加载依赖, 所以server平台和unicloud平台互相不牵扯.
接下来, 我大概梳理一下我这个框架的特性, 希望你可以喜欢
- 完全拥抱TypeScript
- 支持跨平台: Server/阿里云/腾讯云/Cloudflare/AWS/Unicloud
- 基于文件系统的路由
- 开箱即用的开发套件
- TS运行时类型校验
- 生成API文档,兼容markdown以及openapi3.0
- 完全 Hook 的写法与设计
框架github地址: https://github.com/swordCodePractice/sword-framework
dcloud插件市场求支持求支持求支持: https://ext.dcloud.net.cn/plugin?id=8433
我们的中文文档: https://www.yuque.com/mlgrgm/lrf0ra
我们的官方网站: https://swordcodepractice.github.io/backend-framework-website/
现在支持私有部署同时也支持serverless各个平台的nodejs框架多么?答案是否定的,在2022年越来越多的企业会选择serverless来部署自己的应用,因为它足够轻巧省去了运维的成本,编写一个API可能只需要几行代码,越来越多的平台也推出了自己的云数据库以及云消息队列,我们前端开发编写后端api不再是一个头疼的事情。所以serverless是未来,为此我写了一个框架,它会在web服务器中运行也可以在serverless环境运行,在serverless中我会优先支持unicloud,我将使用这款框架参加2022年的dcloud插件大赛。
简单聊聊这款框架,我为什么要创建一款nodejs框架?
为什么要造轮子
在sword团队中我们使用unicloud构建应用程序,采用了CQRS,我们所有的写操作都由unicloud的云函数完成,但是你会发现在unicloud社区优秀的框架有很多,它们提供了url处理以及逻辑的分发,还有一些特色的框架也提供了诸如上传,和unicloud的部分特性封装,在我看来,这样的框架没有真正解决开发者的问题,比如:
- 我想现在不想用unicloud,我想用传统服务器运行函数
- 我想和云平台解耦,我希望我的云函数的特性和功能实现和某一平台无关
- 我想使用ts开发
- 我想有IDE强力支持
- 我想使用一些开箱即用的方案,比如说hook,又比如HMR
- 我想使用ES开发nodejs程序,使用先进的技术对程序进行捆绑(treeshaking...)
- 如果要满足上面的特性,那么只有midway.js了,midway.js很酷,但是它并没有unicloud的faas插件,而且我希望框架能够让sword团队更好的构建程序,所以我们造一个轮子,来解决这些事情。
简单说说这个框架的技术栈
这款框架是我目前最得意的作品, 但是时间太仓促, 它的功能实现肯定不是最好的, 所以我需要各位的支持测试, 而且我也希望能拿到今年的插件大赛的名次; 去年拿到了三等奖也是我写的一款小程序, 今年带来的这个作品含金量非常高哈哈哈.
整个框架都是ts, 我使用了monorepo架构基于pnpm, 所有的源代码你都可以在packages中找到, 框架核心就是runtime那个文件夹. 整个框架最主要的2个部分就是cli + runtime, cli主要做多端的编译功能, 没错cli不仅做了打包还做了编译, 而且打包也是分环境的, 开发环境我是用的是swc (rust) 进行打包, 生产程序是使用esbuild (go), 所以极速是这个框架的特点, 不仅如此, runtime部分我实现了一个简易的异步依赖调度器使之不同平台, 都可以动态加载依赖, 所以server平台和unicloud平台互相不牵扯.
接下来, 我大概梳理一下我这个框架的特性, 希望你可以喜欢
- 完全拥抱TypeScript
- 支持跨平台: Server/阿里云/腾讯云/Cloudflare/AWS/Unicloud
- 基于文件系统的路由
- 开箱即用的开发套件
- TS运行时类型校验
- 生成API文档,兼容markdown以及openapi3.0
- 完全 Hook 的写法与设计
框架github地址: https://github.com/swordCodePractice/sword-framework
dcloud插件市场求支持求支持求支持: https://ext.dcloud.net.cn/plugin?id=8433
我们的中文文档: https://www.yuque.com/mlgrgm/lrf0ra
我们的官方网站: https://swordcodepractice.github.io/backend-framework-website/

uni-app海外月活过亿,2022出海吧;uni-ad全面支持web和微信小程序;uni统计发布2.0版且全面开源
-
uni-app海外月活过亿,2022出海吧。详见:https://mp.weixin.qq.com/s/to1Zj9t684F6csFn7BJYTw
-
uni-ad新版发布,全面支持web和微信小程序
- 在App端聚合了全网所有主流广告源,包括腾讯优量汇、字节穿山甲、快手、百度、华为、360、Sigmob等十几家广告源以及自有广告客户,并通过优秀比价算法,提供了更高的广告出价
- uni-ad的微信小程序有更低的广告开通门槛,欢迎流量主体验。https://uniapp.dcloud.io/uni-ad.html
-
uni统计2.0版本发布,开源、私有部署、可自由定制。 https://uniapp.dcloud.net.cn/uni-stat-v2.html
-
uni-app海外月活过亿,2022出海吧。详见:https://mp.weixin.qq.com/s/to1Zj9t684F6csFn7BJYTw
-
uni-ad新版发布,全面支持web和微信小程序
- 在App端聚合了全网所有主流广告源,包括腾讯优量汇、字节穿山甲、快手、百度、华为、360、Sigmob等十几家广告源以及自有广告客户,并通过优秀比价算法,提供了更高的广告出价
- uni-ad的微信小程序有更低的广告开通门槛,欢迎流量主体验。https://uniapp.dcloud.io/uni-ad.html
-
uni统计2.0版本发布,开源、私有部署、可自由定制。 https://uniapp.dcloud.net.cn/uni-stat-v2.html

进销存管理系统源码带手机版
进销存管理系统代表企业资源规划,它是组织中用于集成信息流和简化业务流程的软件应用程序。进销存管理系统软件开发可以将应用程序组合到一个网络中,该网络可从任何具有适当安全凭证和权限的计算机访问。进销存管理系统允许公司自动化工作流程、监控库存水平和跟踪销售订单。
进销存管理系统源码:c.csymzs.top
1. 组建进销存管理系统项目团队
首先要做的事情是:独自计划这将是非常痛苦的。创建一个高效且敬业的进销存管理系统实施团队将使您的工作变得更加轻松。
首先,您需要以下“核心”成员。
项目经理:领导。你可能。
应用分析师:负责数据迁移和清洗
应用开发者:负责系统定制
QA 测试工程师:负责系统测试和性能工作
这些将在很大程度上取决于您的主要利益相关者是谁 - 鉴于进销存管理系统的性质,这些将因公司而异,具体取决于您正在实施的功能。
2.制定进销存管理系统实施变更管理计划
这有两个方面,真的。
首先,您需要分步规划您的进销存管理系统实施,并根据专业知识将这些委派给您的实施团队。
我们在这里有一个深入的模板进销存管理系统实施时间表- 尽管您可以使用下面的清单来快速了解需要做什么。在开始实施之前,您应该制定以下计划:
预测实施成本并制定预算
创建进销存管理系统实施计划
将数据迁移到新系统
培训您的进销存管理系统用户群
测试您的进销存管理系统并上线
当天上线活动
评估项目的成功
其次,您需要计划在实施过程中保持公司正常运行的方法。变化总是会带来一定程度的破坏,但可以通过以下方式将其最小化:
任何预期中断的清晰沟通
留出足够的时间进行用户培训
考虑到关键利益相关者的需求(您已经完成了一半,项目团队中包括了代表)。
3. 预测您的进销存管理系统实施成本并起草预算
您应该在实施预算中包括所有这些因素,以避免受到隐藏成本的影响:
硬件/网络升级:如果您正在实施内部部署系统,这些将要高得多
员工加班费:要记住的重要一点 - “我们没有为加班费做预算,抱歉”这句话不会让您的团队感到满意
供应商培训、定制和咨询费用:如果您幸运的话,您的供应商可能已将这些包含在初始软件价格中,但这绝对不是给定的。经常检查
数据备份和存储:这些通常与云进销存管理系统成本捆绑在一起——但同样——总是要检查。
4. 开始迁移您的数据
所以它开始了。
你已经尽可能地计划好了。现在是时候开始着手将您的公司转移到新的进销存管理系统的实际过程了。
数据迁移是第一步——如果你做对了,你将建立一个坚实的基础来继续你的实施工作。弄错了,事情可能会开始崩溃。
您的应用程序分析师应该负责这项工作,因为这是他们的专长。准备好帮助他们:
数据清洗和验证
数据库设置
将旧数据映射到新的数据库字段
数据传输到新系统
遗留数据的测试和验证
新数据输入的测试和验证
5. 开始培训您的进销存管理系统用户群
用户采用是进销存管理系统实施项目成功的关键,而合适的用户培训计划对于实现这一目标至关重要。
无论哪种培训方法最适合您的员工,所有成功培训计划的四个特点包括:
基于角色的培训,以简化流程并让时间紧缺的员工随时待命。
受训人员提供反馈的机会(以及实施团队采取行动的机会)
与供应商支持团队建立清晰的沟通渠道
为了获得最佳效果,为什么不尝试将训练计划的部分内容游戏化呢?调查显示,89% 的受访者表示,基于积分的系统会提高他们的参与度,而 62% 的人表示,排行榜和与同事竞争的机会会激励他们学习。使用它对您有利。
为按时完成培训任务的每个人提供的小额津贴(免费食物,额外的午餐时间),以及为您的表现最好的人提供的较大的补助将使参与培训对忙碌的员工更具吸引力。
6. 计划并启动您的进销存管理系统上线活动
如果您认为结婚需要进行大量艰巨的提前计划,那么您会对进销存管理系统实施的“上线”阶段感到不快。
计划的事情包括:
系统测试(上线前和上线后)
人员安排,包括所需的加班或临时人员
确定项目评估的指标
为系统停机制定沟通策略
网络速度和可靠性检查
数据备份流程
7. 评估您的进销存管理系统实施项目的成功与否
您可以结合使用以下指标:
投资回报率。您在进销存管理系统上花费的资金是否有回报?
减少人为错误:实施良好的进销存管理系统应减少由于流程自动化导致的数据错误。如果您的培训计划奏效,员工应该充分利用这些功能
提高生产力水平:这是不言而喻的。进销存管理系统是否完全实现了它的目标?
提高客户满意度:周转时间增加了吗?您是否更快地处理订单?
进销存管理系统代表企业资源规划,它是组织中用于集成信息流和简化业务流程的软件应用程序。进销存管理系统软件开发可以将应用程序组合到一个网络中,该网络可从任何具有适当安全凭证和权限的计算机访问。进销存管理系统允许公司自动化工作流程、监控库存水平和跟踪销售订单。
进销存管理系统源码:c.csymzs.top
1. 组建进销存管理系统项目团队
首先要做的事情是:独自计划这将是非常痛苦的。创建一个高效且敬业的进销存管理系统实施团队将使您的工作变得更加轻松。
首先,您需要以下“核心”成员。
项目经理:领导。你可能。
应用分析师:负责数据迁移和清洗
应用开发者:负责系统定制
QA 测试工程师:负责系统测试和性能工作
这些将在很大程度上取决于您的主要利益相关者是谁 - 鉴于进销存管理系统的性质,这些将因公司而异,具体取决于您正在实施的功能。
2.制定进销存管理系统实施变更管理计划
这有两个方面,真的。
首先,您需要分步规划您的进销存管理系统实施,并根据专业知识将这些委派给您的实施团队。
我们在这里有一个深入的模板进销存管理系统实施时间表- 尽管您可以使用下面的清单来快速了解需要做什么。在开始实施之前,您应该制定以下计划:
预测实施成本并制定预算
创建进销存管理系统实施计划
将数据迁移到新系统
培训您的进销存管理系统用户群
测试您的进销存管理系统并上线
当天上线活动
评估项目的成功
其次,您需要计划在实施过程中保持公司正常运行的方法。变化总是会带来一定程度的破坏,但可以通过以下方式将其最小化:
任何预期中断的清晰沟通
留出足够的时间进行用户培训
考虑到关键利益相关者的需求(您已经完成了一半,项目团队中包括了代表)。
3. 预测您的进销存管理系统实施成本并起草预算
您应该在实施预算中包括所有这些因素,以避免受到隐藏成本的影响:
硬件/网络升级:如果您正在实施内部部署系统,这些将要高得多
员工加班费:要记住的重要一点 - “我们没有为加班费做预算,抱歉”这句话不会让您的团队感到满意
供应商培训、定制和咨询费用:如果您幸运的话,您的供应商可能已将这些包含在初始软件价格中,但这绝对不是给定的。经常检查
数据备份和存储:这些通常与云进销存管理系统成本捆绑在一起——但同样——总是要检查。
4. 开始迁移您的数据
所以它开始了。
你已经尽可能地计划好了。现在是时候开始着手将您的公司转移到新的进销存管理系统的实际过程了。
数据迁移是第一步——如果你做对了,你将建立一个坚实的基础来继续你的实施工作。弄错了,事情可能会开始崩溃。
您的应用程序分析师应该负责这项工作,因为这是他们的专长。准备好帮助他们:
数据清洗和验证
数据库设置
将旧数据映射到新的数据库字段
数据传输到新系统
遗留数据的测试和验证
新数据输入的测试和验证
5. 开始培训您的进销存管理系统用户群
用户采用是进销存管理系统实施项目成功的关键,而合适的用户培训计划对于实现这一目标至关重要。
无论哪种培训方法最适合您的员工,所有成功培训计划的四个特点包括:
基于角色的培训,以简化流程并让时间紧缺的员工随时待命。
受训人员提供反馈的机会(以及实施团队采取行动的机会)
与供应商支持团队建立清晰的沟通渠道
为了获得最佳效果,为什么不尝试将训练计划的部分内容游戏化呢?调查显示,89% 的受访者表示,基于积分的系统会提高他们的参与度,而 62% 的人表示,排行榜和与同事竞争的机会会激励他们学习。使用它对您有利。
为按时完成培训任务的每个人提供的小额津贴(免费食物,额外的午餐时间),以及为您的表现最好的人提供的较大的补助将使参与培训对忙碌的员工更具吸引力。
6. 计划并启动您的进销存管理系统上线活动
如果您认为结婚需要进行大量艰巨的提前计划,那么您会对进销存管理系统实施的“上线”阶段感到不快。
计划的事情包括:
系统测试(上线前和上线后)
人员安排,包括所需的加班或临时人员
确定项目评估的指标
为系统停机制定沟通策略
网络速度和可靠性检查
数据备份流程
7. 评估您的进销存管理系统实施项目的成功与否
您可以结合使用以下指标:
投资回报率。您在进销存管理系统上花费的资金是否有回报?
减少人为错误:实施良好的进销存管理系统应减少由于流程自动化导致的数据错误。如果您的培训计划奏效,员工应该充分利用这些功能
提高生产力水平:这是不言而喻的。进销存管理系统是否完全实现了它的目标?
提高客户满意度:周转时间增加了吗?您是否更快地处理订单?