先上效果图

这是小程序上的表现:

这是ios app上的表现:


分析:看到这个需求,我们第一反应是使用uniapp上的camera组件,然后在用cover-image添加一个图片就可以达到要求。但是camera组件有兼容性的问题,它不支持app端。于是参考别人的写法,看到有一个live-pusher直播流组件,用nvue写就可以兼容app。

技术拆分:

1.小程序端使用camera组件。页面内嵌的区域相机组件。注意这不是点击后全屏打开的相机。

2.App端使用直播推流 live-pusher 组件,官方上说:如app平台的vue页面需要支持直播推流,需编写条件编译代码,使用 plus.video.LivePusher业务指南、规范文档。还是推荐直接使用nvue里的live-pusher组件。所以我们使用nvue格式来代替vue格式的页面。

不管是camera组件还是live-pusher组件。他们都是原生组件,所以必须使用cover-image、cover-view来制作覆盖层。

<!--* @Descripttion: 照片OCR取景框页面,2个功能。1.实现相机+取景框的拍照组合。2.裁剪取景框内的元素。拍照:1.APP端使用<live-pusher> 直播流 模拟相机窗口。需要在manifest.json -> APP权限模块里勾选 LivePusher直播流2.小程序端只用<camera>组件模拟相机窗口。裁剪:1.APP端无法使用canvas的API,因为用的是nvue文件,目前不支持官方canvas的API,可以使用官方提供的gcanvas的API。(gcanvas的drawImage不允许临时路径)2.小程序端通过canvas提供的API可实现。关于gcanvas 参考官方示例 https://github.com/dcloudio/NvueCanvasDemo* @version: * @Author: dal* @Date: 2021-12-13 10:59:41* @LastEditors: dal* @LastEditTime: 2021-12-29 14:13:31
-->
<template><view class="live-camera" :style="{ width: `${windowWidth}px`, height:  `${windowHeight}px` }"><view class="preview" :style="{ width:  `${windowWidth}px`, height:`${windowHeight - 90 }px` }"><!-- #ifdef APP-PLUS --><live-pusherv-if="showLive"id="livePusher"ref="livePusher"class="livePusher"mode="FHD"beauty="0"whiteness="0":aspect="aspect"min-bitrate="1000"audio-quality="16KHz"device-position="back":auto-focus="true":muted="true":enable-camera="true":enable-mic="false":zoom="false"@statechange="statechange"@error = "error":style="{ width: `${cameraWidth }px`, height: `${windowHeight - 90 }px` }"></live-pusher><!-- #endif --><!-- #ifdef MP --><camera :style="{ width: `${cameraWidth }px`, height: `${windowHeight - 90 }px` }" :device-position="devicePosition"></camera><!-- #endif --><!--辅助线--><cover-view class="outline-box" :style="{ width: `${windowWidth }px`, height:  `${windowHeight - 90}px` }"><cover-image v-if="type === '0'" class="outline-img" src="../static/images/regist/k-sfz.png" ></cover-image><cover-image v-else-if="type === '1'" class="outline-img" src="../static/images/regist/k-sfzb.png" ></cover-image><cover-image v-else-if="type === '2'" class="outline-img1" src="../static/images/regist/jsz-qjk.png" ></cover-image><cover-image v-else-if="type === '3'" class="outline-img1" src="../static/images/regist/xsz-qjk.png" ></cover-image><cover-image v-else-if="type === '4'" class="outline-img" src="../static/images/regist/k-cyzgz.png" ></cover-image><cover-image v-else-if="type === '5'" class="outline-img" src="../static/images/regist/k-dlysz.png" ></cover-image><ksfz /></cover-view></view><view class="menu"><!--底部菜单区域背景--><cover-image class="menu-mask" src="../static/images/regist/bar.png"></cover-image><!--返回键--><cover-image class="menu-back" @tap="back" src="../static/images/regist/back2.png"></cover-image><!--快门键--><cover-image class="menu-snapshot" @tap="snapshot" src="../static/images/regist/btn.png"></cover-image><!--反转键--><cover-image class="menu-flip" @tap="flip" src="../static/images/regist/flip.png"></cover-image></view><canvas-crop ref="crop"></canvas-crop></view>
</template><script>
import { judgeIosPermission,requestAndroidPermission,gotoAppPermissionSetting } from "@/common/scripts/permission.js"
import { uploadFileOSS } from "@/api/oss.js"
import { errorMsg } from "@/common/scripts/message.js"
import config from "@/config/index.js"export default {data() {return {devicePosition:"back",//前置或后置摄像头,值为front, backpoenCarmeInterval: null, //打开相机的轮询dotype: 'idcardface', //操作类型message: '', //提示aspect: '2:3', //比例cameraWidth: '', //相机画面宽度cameraHeight: '', //相机画面宽度windowWidth: '', //屏幕可用宽度windowHeight: '', //屏幕可用高度camerastate: false, //相机准备好了livePusher: null, //流视频对象snapshotsrc: null ,//快照type:null,showLive:false ,//安卓机需要先判断权限有没有授权imageInfo:{},//取景框内的图片大小context:{},//canvas实例对象rpx2px:"",finder:{//取景框尺寸,驾驶证、行驶证为333*999,其他为640*980width:640,height:980},};},onLoad(e) {this.type = e.typeif(["2","3"].indexOf(this.type)> -1) this.finder = {width:333,height:999}console.log(this.finder)},async onReady() {this.initCamera();// #ifdef APP-NVUEif(plus.os.name === "Android"){await this.checkAndriodCamera()}else{this.showLive = true}// #endif// #ifdef MPthis.showLive = trueif(       !await this.checkCamera() ){ return }// #endif// #ifdef APP-PLUSthis.showLive ? this.$nextTick(() => {this.livePusher = uni.createLivePusherContext('livePusher', this)}) : ""if(plus.os.name === "iOS"){setTimeout(()=>{//开启预览并设置摄像头this.startPreview();},100)}// #endif},methods: {//初始化相机initCamera() {//处理安卓手机异步授权问题uni.getSystemInfo({success: (res) => {console.log("手机信息",res)this.windowWidth = res.windowWidth;this.windowHeight = res.windowHeight;this.cameraWidth = res.windowWidth;this.cameraHeight = res.windowWidth * 1.5;this.rpx2px = 1 / 750 * res.windowWidth;let imgW = parseInt(this.finder.width*this.rpx2px), imgH = parseInt(this.finder.height*this.rpx2px)  // 640和980是css里定义取景框的rpx宽度this.imageInfo = {width : imgW,height: imgH}}});},//检查安卓相机权限async checkAndriodCamera(){let androidPermisson = await requestAndroidPermission("android.permission.CAMERA")if(androidPermisson === 1){this.showLive = truethis.$nextTick(() => {this.livePusher = uni.createLivePusherContext('livePusher', this);})setTimeout(()=>{//开启预览并设置摄像头this.startPreview();this.poenCarme()},100)}else{uni.showModal({content:"请打开摄像头授权功能!",showCancel:false,success: (res) => {if(res.confirm) gotoAppPermissionSetting()}})}console.log("checkAndriodCamera",androidPermisson)},//检查照相机权限checkCamera(){return new Promise( async (resolve) => {// #ifdef APP-PLUSif( (plus.os.name === "iOS" && !judgeIosPermission("camera"))){uni.showModal({content:"请打开摄像头授权功能!",showCancel:false,success: (res) => {if(res.confirm) gotoAppPermissionSetting()}})resolve(false)}else if( plus.os.name === "Android"){let androidPermisson = await requestAndroidPermission("android.permission.CAMERA")console.log(androidPermisson)if(androidPermisson < 1){uni.showModal({content:"请打开摄像头授权功能!",showCancel:false})resolve(false)} else {resolve(true)}}else{resolve(true)}// #endif// #ifdef MPuni.getSetting({success:(sRes) => {console.log(sRes)if(sRes.authSetting["scope.camera"] === false){uni.showModal({content:"请打开摄像头授权功能!",showCancel:false})resolve(false)}else{resolve(true)}},fail:(err) => {console.log(err)}})// #endif})},//轮询打开async poenCarme() {//#ifdef APP-PLUSif (plus.os.name == 'Android') {this.poenCarmeInterval = setInterval(() => {if (!this.camerastate) this.startPreview();}, 1000);}//#endif},//开始预览startPreview() {this.livePusher.startPreview({success: async (a) => {//直播推流默认是前置摄像头,预览成功后给转成后置摄像头if(plus.os.name == "iOS"){this.livePusher.switchCamera();this.camerastate = true;}}});},error(e) {clearInterval(this.poenCarmeInterval)console.log("error:" + JSON.stringify(e));if(e.detail.errCode === 10001){uni.showModal({content:"请打开摄像头授权功能!",showCancel:false,success: (res) => {if(res.confirm) gotoAppPermissionSetting()}})}},//停止预览stopPreview() {this.livePusher.stopPreview({success: a => {this.camerastate = false; //标记相机未启动}});},//状态statechange(e) {//状态改变console.log(e);if (e.detail.code == 1003 || e.detail.code == 1007) { //1007this.camerastate = true;} else if (e.detail.code == -1301) {this.checkCamera()this.camerastate = false;}},//返回back() {uni.navigateBack();},/*** 抓拍,因为APP端用的gcanvas,gcanvas.drawImage不允许临时图片,所以要先传一次图片* **/snapshot() {if(!this.checkCamera()){ return false}//震动uni.vibrateShort();// #ifdef APP-PLUSthis.livePusher.snapshot({success: async e => {this.uploadImage(`file://${e.message.tempImagePath}`)},fail: err => {console.log(err)}});// #endif// #ifdef MPconst ctx = uni.createCameraContext();ctx.takePhoto({quality: 'high',success: (res) => {this.uploadImage(res.tempImagePath)}});// #endif},/*** 获取临时路径的图片宽高大小* **/getImageInfo(path){return new Promise((resolve,reject) => {uni.getImageInfo({src:path,success: (res) => {resolve(res)},fail: (err) => {reject(err)resolve(err)}})})},async uploadImage(path){let info = await this.getImageInfo(path) //获取临时路径的图片宽高大小let width = Math.round( this.rpx2px*this.finder.width/this.cameraWidth*info.width),height = Math.round(this.rpx2px*this.finder.height/(this.windowHeight-90)*info.height)let x = parseInt((info.width-width)/2),y = parseInt((info.height-height)/2)uploadFileOSS(path).then( res => {this.snapshotsrc = res.url + `?x-oss-process=image/crop,x_${x},y_${y},w_${width},h_${height}/rotate,270`console.log(this.snapshotsrc)this.setImage({x,y,width,height});uni.navigateBack({delta:2})}).catch( err => {console.log(err)errorMsg(err)})},//反转flip() {// #ifdef APP-PLUSthis.livePusher.switchCamera();// #endif// #ifdef MPthis.devicePosition = this.devicePosition === 'back' ? 'front' : 'back'// #endif},//设置setImage(x,y,width,height) {let pages = getCurrentPages();let prevPage = pages[pages.length - 3]; //上二个页面//直接调用上二个页面的setImage()方法,把数据存到上二个页面中去prevPage.$vm.setImage({ path: this.snapshotsrc, type: this.type });}}
};
</script><style lang="scss">
.live-camera {.preview {justify-content: center;align-items: center;position: relative;z-index: 1;.canvas{visibility: hidden;position: absolute;top: 0;z-index: -1;}.gcanvas{z-index: -1;position: absolute;}.outline-box {position: absolute;top: 0;left: 0;bottom: 0;z-index: 99;align-items: center;justify-content: center;display: flex;.outline-img {width: 640rpx;height: 980rpx;}.outline-img1{width: 333rpx;height: 999rpx;}}.remind {position: absolute;left: -106px;top: 880rpx;width: 750rpx;z-index: 100;transform: rotate(90deg);align-items: center;justify-content: center;color: #FFFFFF;.remind-text {color: #FFFFFF;font-weight: bold;}}}.menu {position: absolute;left: 0;bottom: 0;width: 750rpx;height: 90px;z-index: 98;align-items: center;justify-content: center;background-color: #000;box-sizing:inherit;.menu-mask {position: absolute;left: 0;bottom: 0;width: 750rpx;height: 180rpx;z-index: 98;}.menu-back {position: absolute;left: 30rpx;bottom: 50rpx;width: 80rpx;height: 80rpx;z-index: 99;align-items: center;justify-content: center;}.menu-snapshot {width: 130rpx;height: 130rpx;z-index: 99;}.menu-flip {position: absolute;right: 30rpx;bottom: 50rpx;width: 80rpx;height: 80rpx;z-index: 99;align-items: center;justify-content: center;}}
}
.back{width: 88rpx;height: 88rpx;margin-left: 20rpx;margin-top: env(safe-area-inset-top);position: absolute;top: 0;left: 0;
}
</style>
/*
* permisson.js
**//*** 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启*/var isIos
// #ifdef APP-PLUS
isIos = (plus.os.name == "iOS")
// #endif// 判断推送权限是否开启
function judgeIosPermissionPush() {var result = false;var UIApplication = plus.ios.import("UIApplication");var app = UIApplication.sharedApplication();var enabledTypes = 0;if (app.currentUserNotificationSettings) {var settings = app.currentUserNotificationSettings();enabledTypes = settings.plusGetAttribute("types");console.log("enabledTypes1:" + enabledTypes);if (enabledTypes == 0) {console.log("推送权限没有开启");} else {result = true;console.log("已经开启推送功能!")}plus.ios.deleteObject(settings);} else {enabledTypes = app.enabledRemoteNotificationTypes();if (enabledTypes == 0) {console.log("推送权限没有开启!");} else {result = true;console.log("已经开启推送功能!")}console.log("enabledTypes2:" + enabledTypes);}plus.ios.deleteObject(app);plus.ios.deleteObject(UIApplication);return result;
}// 判断定位权限是否开启
function judgeIosPermissionLocation() {var result = false;var cllocationManger = plus.ios.import("CLLocationManager");var status = cllocationManger.authorizationStatus();result = (status != 2)console.log("定位权限开启:" + result);// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation/* var enable = cllocationManger.locationServicesEnabled();var status = cllocationManger.authorizationStatus();console.log("enable:" + enable);console.log("status:" + status);if (enable && status != 2) {result = true;console.log("手机定位服务已开启且已授予定位权限");} else {console.log("手机系统的定位没有打开或未给予定位权限");} */plus.ios.deleteObject(cllocationManger);return result;
}// 判断麦克风权限是否开启
function judgeIosPermissionRecord() {var result = false;var avaudiosession = plus.ios.import("AVAudioSession");var avaudio = avaudiosession.sharedInstance();var permissionStatus = avaudio.recordPermission();console.log("permissionStatus:" + permissionStatus);if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {console.log("麦克风权限没有开启");} else {result = true;console.log("麦克风权限已经开启");}plus.ios.deleteObject(avaudiosession);return result;
}// 判断相机权限是否开启
function judgeIosPermissionCamera() {var result = false;var AVCaptureDevice = plus.ios.import("AVCaptureDevice");var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');console.log("authStatus:" + authStatus);if (authStatus == 3) {result = true;console.log("相机权限已经开启");} else {console.log("相机权限没有开启");}plus.ios.deleteObject(AVCaptureDevice);return result;
}// 判断相册权限是否开启
function judgeIosPermissionPhotoLibrary() {var result = false;var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");var authStatus = PHPhotoLibrary.authorizationStatus();console.log("authStatus:" + authStatus);if (authStatus == 3) {result = true;console.log("相册权限已经开启");} else {console.log("相册权限没有开启");}plus.ios.deleteObject(PHPhotoLibrary);return result;
}// 判断通讯录权限是否开启
function judgeIosPermissionContact() {var result = false;var CNContactStore = plus.ios.import("CNContactStore");var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);if (cnAuthStatus == 3) {result = true;console.log("通讯录权限已经开启");} else {console.log("通讯录权限没有开启");}plus.ios.deleteObject(CNContactStore);return result;
}// 判断日历权限是否开启
function judgeIosPermissionCalendar() {var result = false;var EKEventStore = plus.ios.import("EKEventStore");var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);if (ekAuthStatus == 3) {result = true;console.log("日历权限已经开启");} else {console.log("日历权限没有开启");}plus.ios.deleteObject(EKEventStore);return result;
}// 判断备忘录权限是否开启
function judgeIosPermissionMemo() {var result = false;var EKEventStore = plus.ios.import("EKEventStore");var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);if (ekAuthStatus == 3) {result = true;console.log("备忘录权限已经开启");} else {console.log("备忘录权限没有开启");}plus.ios.deleteObject(EKEventStore);return result;
}// Android权限查询
function requestAndroidPermission(permissionID) {return new Promise((resolve, reject) => {plus.android.requestPermissions([permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装function(resultObj) {var result = 0;for (var i = 0; i < resultObj.granted.length; i++) {var grantedPermission = resultObj.granted[i];console.log('已获取的权限:' + grantedPermission);result = 1}for (var i = 0; i < resultObj.deniedPresent.length; i++) {var deniedPresentPermission = resultObj.deniedPresent[i];console.log('拒绝本次申请的权限:' + deniedPresentPermission);result = 0}for (var i = 0; i < resultObj.deniedAlways.length; i++) {var deniedAlwaysPermission = resultObj.deniedAlways[i];console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);result = -1}resolve(result);// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限// if (result != 1) {// gotoAppPermissionSetting()// }},function(error) {console.log('申请权限错误:' + error.code + " = " + error.message);resolve({code: error.code,message: error.message});});});
}// 使用一个方法,根据参数判断权限
function judgeIosPermission(permissionID) {if (permissionID == "location") {return judgeIosPermissionLocation()} else if (permissionID == "camera") {return judgeIosPermissionCamera()} else if (permissionID == "photoLibrary") {return judgeIosPermissionPhotoLibrary()} else if (permissionID == "record") {return judgeIosPermissionRecord()} else if (permissionID == "push") {return judgeIosPermissionPush()} else if (permissionID == "contact") {return judgeIosPermissionContact()} else if (permissionID == "calendar") {return judgeIosPermissionCalendar()} else if (permissionID == "memo") {return judgeIosPermissionMemo()}return false;
}// 跳转到**应用**的权限页面
function gotoAppPermissionSetting() {if (isIos) {var UIApplication = plus.ios.import("UIApplication");var application2 = UIApplication.sharedApplication();var NSURL2 = plus.ios.import("NSURL");// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");     var setting2 = NSURL2.URLWithString("app-settings:");application2.openURL(setting2);plus.ios.deleteObject(setting2);plus.ios.deleteObject(NSURL2);plus.ios.deleteObject(application2);} else {// console.log(plus.device.vendor);var Intent = plus.android.importClass("android.content.Intent");var Settings = plus.android.importClass("android.provider.Settings");var Uri = plus.android.importClass("android.net.Uri");var mainActivity = plus.android.runtimeMainActivity();var intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);intent.setData(uri);mainActivity.startActivity(intent);}
}// 检查系统的设备服务是否开启
// var checkSystemEnableLocation = async function () {
function checkSystemEnableLocation() {if (isIos) {var result = false;var cllocationManger = plus.ios.import("CLLocationManager");var result = cllocationManger.locationServicesEnabled();console.log("系统定位开启:" + result);plus.ios.deleteObject(cllocationManger);return result;} else {var context = plus.android.importClass("android.content.Context");var locationManager = plus.android.importClass("android.location.LocationManager");var main = plus.android.runtimeMainActivity();var mainSvr = main.getSystemService(context.LOCATION_SERVICE);var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);console.log("系统定位开启:" + result);return result}
}module.exports = {judgeIosPermission: judgeIosPermission,requestAndroidPermission: requestAndroidPermission,checkSystemEnableLocation: checkSystemEnableLocation,gotoAppPermissionSetting: gotoAppPermissionSetting
}

Uniapp实现证件照提示框模板(小程序+APP)相关推荐

  1. 省钱兄同城跑腿小程序源码uniapp前端模版源码(小程序+APP+H5)

    开源省钱兄同城跑腿源码,目前只开源用户端V2版本部分核心模块源码提供学习研究 使用uniapp技术,提供学习使用不可商业 适配支持公众号+APP+H5+小程序,使用Hbuilder导入即可运行 #功能 ...

  2. crmeb知识付费uniapp重构 适配小程序 APP 微信H5

    uniapp重构crmeb知识付费 适配小程序 APP wxh5 前端首页 前端首页顶部banner图可在后台[系统设置>首页设置>首页轮播图]中更换:图片尺寸:750*365 px 首页 ...

  3. uni-app(Vue.js)创建运行微信小程序

    uni-app(Vue.js)创建运行微信小程序 1.全局安装 npm install -g @vue/cli 需要安装node,官方网站,否则提示npm不可用 2.创建uni-app 新建文件夹,选 ...

  4. 基于Uni-APP多端「h5+小程序+App」高仿抖音小视频|直播|聊天实例

    uni-ttLive 基于uni-app+uView-ui跨端开发短视频+直播聊天实例. 全新研发的一款多端仿制抖音短视频+直播+聊天项目,基于uniApp+Vue.js+Vuex+Nvue+uVie ...

  5. 模板小程序怎么做?【小程序模板搭建】

    制作小程序除了可以代码开发之外,现在还会有很多模板小程序,是通过小程序模板搭建出来的.模板小程序目前也受到很多公司企业的青睐,毕竟搭建效率高,让我们一起来看看模板小程序怎么做. 一.小程序账号 小程序 ...

  6. 模板小程序或定制小程序有什么区别?

    网络图片侵权删 小程序定制开发服务相对模板来说,价格是高一些的:一般得,要找小程序定制开发,定制开发大概需要以下这些人: 1)产品经理主要负责产品的整体设计和需求的文档,就像是一个东西必须要有重心一样 ...

  7. 微信小程序选择框问题 小程序picker点击显示object range-key=这里写要显示的字段

    微信小程序选择框问题 小程序picker点击显示object range-key="这里写要显示的字段" <picker bindchange="bindClass ...

  8. 微信开放平台之第三方平台开发,模板小程序如何提交?

    大家好,我是悟空码字 12月25日,天气晴朗,阳光普照,今天是圣诞节.因为疫情影响,小羊人的增多,街上放眼望去,人烟稀少.楼下除了几个十一二岁的小男孩在玩耍,也没有像往日老人悠闲打牌.小孩嬉戏那般热闹 ...

  9. 计算机毕业设计Python+uniapp扫码点餐微信小程序(小程序+源码+LW)

    计算机毕业设计Python+uniapp扫码点餐微信小程序(小程序+源码+LW) 该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程 项目运行 环境配置: Pychram社区版+ pyth ...

最新文章

  1. linux centos 网络设置 优先使用ipv4 其次ipv6
  2. Netty 系列一(核心组件和实例).
  3. 关于AUC计算公式推导
  4. python学习-高阶函数(函数传参、返回函数(闭包)、匿名函数lambda)
  5. VC++6.0开发环境之快捷键
  6. 返回一个首尾相接的二维整数数组中最大子数组的和
  7. PHP代码中的情话,php语言编程情话
  8. 建模大师怎么安装到revit中_工程师最爱的REVIT插件,让BIM建模溜到飞起!
  9. 10进制转62进制 java_两种方法实现10进制和62进制互转 | 学步园
  10. 微服务模式下API测试
  11. Hibernate配置文件,映射文件
  12. c语言银行排队系统链表,银行预约排队系统(数据结构问题)
  13. 计算机课程设计心得体会及总结,课程设计心得体会
  14. 六月软件程序大赛WBS图
  15. 谷歌浏览器:快速切换搜索引擎
  16. solidworks启动慢的原因在这里
  17. Xmind 8 pro 软件破解版
  18. 手把手教你用熵值法计算权重
  19. Vue实现web端仿网易云音乐 完成大部分功能
  20. Python毕业设计必备案例:【学生信息管理系统】

热门文章

  1. 向spring大师致敬
  2. python实现单摆的数值模拟
  3. pat-public bikes manager
  4. python实战故障诊断之CWRU数据集(一):数据集初识
  5. 如何使用PDF编辑工具中的便签功能?
  6. 转载:春哥的程序人生
  7. window 配置 OutLook 2016
  8. 如何实现FHSS模型和CST模型之间的相互导入导出!
  9. 基美电子针对下一代用户界面推出高清压电薄膜触觉执行器
  10. windows10 双屏幕扩展后,窗口拖动方向的问题。