Compare commits
21 Commits
16d431aa63
...
main
Author | SHA1 | Date | |
---|---|---|---|
56f05e7eec | |||
dd752d0478 | |||
c63089790d | |||
6e64044802 | |||
2772db349a | |||
6d119e26bc | |||
af352419b9 | |||
89464f3696 | |||
6f376c07bd | |||
cabd3c0987 | |||
ea51dc5d15 | |||
8e6e0a8a59 | |||
3dfdd5d4c1 | |||
a56e1fd43f | |||
cc0ad19e2d | |||
413c495880 | |||
4a0798f47d | |||
f94efa54de | |||
ceeb4772ca | |||
cf3ed73568 | |||
6ada4657fb |
47
App.vue
@ -2,7 +2,7 @@
|
||||
export default {
|
||||
|
||||
onLaunch: function() {
|
||||
|
||||
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
@ -15,27 +15,52 @@
|
||||
|
||||
<style lang="scss">
|
||||
@import 'vk-uview-ui/index.scss';
|
||||
uni-slider .uni-slider-handle-wrapper{
|
||||
|
||||
uni-slider .uni-slider-handle-wrapper {
|
||||
border-radius: 20rpx;
|
||||
|
||||
|
||||
}
|
||||
uni-slider .uni-slider-thumb{
|
||||
|
||||
uni-slider .uni-slider-thumb {
|
||||
width: 66rpx !important;
|
||||
height:80rpx !important;
|
||||
height: 80rpx !important;
|
||||
margin-top: -40rpx !important;
|
||||
border-radius: 16rpx !important;
|
||||
margin-left: -72rpx !important;
|
||||
|
||||
|
||||
}
|
||||
uni-slider .uni-slider-handle-wrapper{
|
||||
|
||||
uni-slider .uni-slider-handle-wrapper {
|
||||
height: 88rpx;
|
||||
position: relative;
|
||||
}
|
||||
uni-slider .uni-slider-value{
|
||||
|
||||
uni-slider .uni-slider-value {
|
||||
position: absolute;
|
||||
color: rgba(255, 255, 255,0.8);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 32rpx;
|
||||
right:2.5%;
|
||||
top:-60rpx
|
||||
right: 2.5%;
|
||||
top: -60rpx
|
||||
}
|
||||
|
||||
.custom-file-picker .file-picker__box-content {
|
||||
background: rgba(26, 26, 26, 1);
|
||||
border: none !important;
|
||||
border-radius: 45rpx;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
}
|
||||
|
||||
.uni-file-picker.custom-file-picker {
|
||||
overflow: inherit !important;
|
||||
}
|
||||
|
||||
.custom-file-picker .icon-add {
|
||||
height: 5rpx !important;
|
||||
width: 70rpx !important;
|
||||
}
|
||||
|
||||
.custom-file-picker .file-picker__box-content {
|
||||
// display: none
|
||||
}
|
||||
</style>
|
671
api/6155/BlueHelper.js
Normal file
@ -0,0 +1,671 @@
|
||||
export default {
|
||||
featrueValueCallback: null,//蓝牙特征变化回调
|
||||
BleChangeCallback:null,//蓝牙状态变化回调
|
||||
//引导用户打开蓝牙
|
||||
showBluetoothGuide: function(showTip) {
|
||||
let platform = process.env.UNI_PLATFORM;
|
||||
|
||||
|
||||
var openBlueSetting = function() {
|
||||
// 判断平台类型
|
||||
if (platform === 'mp-weixin') {
|
||||
uni.openSetting();
|
||||
} else if (platform === 'app-plus' || platform === 'app') {
|
||||
//----------------------------------------------------------------
|
||||
const osName = plus.os.name;
|
||||
|
||||
if (osName === 'iOS') {
|
||||
// iOS 平台打开蓝牙设置
|
||||
plus.runtime.openURL('App-Prefs:root=Bluetooth', function() {
|
||||
console.log('成功打开蓝牙设置');
|
||||
}, function(err) {
|
||||
console.error('打开蓝牙设置失败:' + err.message);
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '无法自动打开蓝牙设置,请手动前往设置 > 蓝牙 进行操作。',
|
||||
showCancel: false
|
||||
});
|
||||
});
|
||||
} else if (osName === 'Android') {
|
||||
// Android 平台打开蓝牙设置
|
||||
try {
|
||||
const Intent = plus.android.importClass('android.content.Intent');
|
||||
const Settings = plus.android.importClass('android.provider.Settings');
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
|
||||
main.startActivity(intent);
|
||||
} catch (e) {
|
||||
console.error('打开蓝牙设置失败:' + e.message);
|
||||
// 尝试使用通用设置页面作为备选方案
|
||||
plus.runtime.openURL('settings://', function() {
|
||||
console.log('打开系统设置成功,请手动找到蓝牙选项');
|
||||
}, function() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '无法自动打开蓝牙设置,请手动前往设置页面开启蓝牙。',
|
||||
showCancel: false
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前系统不支持自动打开蓝牙设置,请手动操作。',
|
||||
showCancel: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
} else if (platform === 'mp-alipay') {
|
||||
uni.openSetting();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (showTip !== undefined) {
|
||||
openBlueSetting();
|
||||
return;
|
||||
}
|
||||
if (platform === 'mp-weixin' || platform === 'app-plus' || platform === 'mp-alipay' || platform === 'app') {
|
||||
uni.showModal({
|
||||
title: '蓝牙未开启',
|
||||
content: '请在系统设置中打开蓝牙以使用此功能',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
openBlueSetting();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("当前平台不支持打开系统设置" + platform);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
//获取蓝牙适配器状态
|
||||
CheckBlue: function(callback) {
|
||||
|
||||
uni.getBluetoothAdapterState({
|
||||
success(res1) {
|
||||
|
||||
console.log("当前蓝牙适配器状态:" + JSON.stringify(res1))
|
||||
if (callback) {
|
||||
callback(res1);
|
||||
}
|
||||
|
||||
},
|
||||
fail(ex1) {
|
||||
console.log("检查蓝牙状态异常:" + JSON.stringify(ex1));
|
||||
if (callback) {
|
||||
if (ex1.code == 10000) {
|
||||
console.log("未初始化蓝牙适配器");
|
||||
}
|
||||
let res1 = {
|
||||
available: false,
|
||||
discovering: false
|
||||
}
|
||||
callback(res1);
|
||||
}
|
||||
},
|
||||
complete() {
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
//初始化蓝牙模块
|
||||
OpenBlue: function(isCheckState, callback, availCallback) {
|
||||
|
||||
var these = this;
|
||||
|
||||
var init = function() {
|
||||
uni.openBluetoothAdapter({
|
||||
success: (res) => {
|
||||
console.log("蓝牙初始化成功:" + JSON.stringify(res));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
uni.onBluetoothAdapterStateChange(function(state) {
|
||||
console.log('蓝牙状态发生变化:' + JSON.stringify(state));
|
||||
if(this.BleChangeCallback){
|
||||
this.BleChangeCallback()
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: function(ex2) {
|
||||
console.log("蓝牙初始化失败:" + JSON.stringify(ex2))
|
||||
if (ex2.code == '10001') {
|
||||
console.log("手机蓝牙未打开或设备不支持蓝牙");
|
||||
|
||||
|
||||
if (availCallback) {
|
||||
availCallback();
|
||||
} else {
|
||||
these.showBluetoothGuide();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isCheckState) {
|
||||
this.CheckBlue(function(res1) {
|
||||
if (res1.available) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
init();
|
||||
})
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
//关闭蓝牙模块,停止搜索、断开所有连接
|
||||
CloseBlue: function(callback) {
|
||||
|
||||
this.StopSearch();
|
||||
|
||||
this.disconnectDevice();
|
||||
|
||||
uni.closeBluetoothAdapter({
|
||||
success: () => {
|
||||
console.log("蓝牙模块已关闭");
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
//开始搜索新设备
|
||||
StartSearch: function(callback) {
|
||||
|
||||
var these = this;
|
||||
|
||||
//发现新设备
|
||||
var onDeviceFound = function() {
|
||||
uni.onBluetoothDeviceFound(function(res) {
|
||||
console.log("发现新设备:" + JSON.stringify(res));
|
||||
if (callback) {
|
||||
callback(res);
|
||||
}
|
||||
})
|
||||
}
|
||||
//开始搜索
|
||||
var Search = function() {
|
||||
uni.startBluetoothDevicesDiscovery({
|
||||
services: ["0xFFE0"],
|
||||
allowDuplicatesKey: false,
|
||||
success: (res) => {
|
||||
console.log('开始搜索蓝牙设备成功');
|
||||
onDeviceFound();
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(`搜索蓝牙设备失败: ${err.errMsg}`);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
//先检查蓝牙状态是可用
|
||||
this.CheckBlue(function(res1) {
|
||||
if (res1.available) {
|
||||
if (!res1.discovering) {
|
||||
Search();
|
||||
} else {
|
||||
console.log("当前蓝牙正在搜索设备")
|
||||
}
|
||||
|
||||
} else {
|
||||
these.OpenBlue(false, Search, () => {
|
||||
these.showBluetoothGuide();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
},
|
||||
//停止搜索
|
||||
StopSearch: function() {
|
||||
uni.stopBluetoothDevicesDiscovery({
|
||||
success: (res) => {
|
||||
console.log("停止搜索蓝牙设备成功")
|
||||
},
|
||||
fail() {
|
||||
console.log("无法停止蓝牙搜索")
|
||||
}
|
||||
});
|
||||
},
|
||||
//获取已连接的设备
|
||||
getLinkBlue: function(callback) {
|
||||
uni.getConnectedBluetoothDevices({
|
||||
services: ["0xFFE0"],
|
||||
success: (res) => {
|
||||
if (callback) {
|
||||
callback(res);
|
||||
|
||||
}
|
||||
},
|
||||
fail: function(ex) {
|
||||
console.log("获取已连接设备异常");
|
||||
if (callback) {
|
||||
callback({
|
||||
devices: []
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//连接某个设备
|
||||
LinkBlue: function(deviceId, callback, error) {
|
||||
|
||||
this.StopSearch();
|
||||
var these = this;
|
||||
let key = "linkedDevices";
|
||||
var store = uni.getStorageInfoSync();
|
||||
var f = store.keys.find(function(v) {
|
||||
return v == key;
|
||||
});
|
||||
var linkedDevices = [];
|
||||
if (f) {
|
||||
var str = uni.getStorageSync(key);
|
||||
if (str) {
|
||||
linkedDevices = JSON.parse(str);
|
||||
}else{
|
||||
linkedDevices=[];
|
||||
}
|
||||
|
||||
}
|
||||
//连接成功的回调
|
||||
var lindedCallback = function () {
|
||||
|
||||
let c = linkedDevices.find(function (v) {
|
||||
return v.deviceId == deviceId;
|
||||
});
|
||||
|
||||
if (c) {
|
||||
console.log("连接成功开始监听特征变化")
|
||||
//监听设备的特征变化
|
||||
uni.notifyBLECharacteristicValueChange({
|
||||
deviceId: deviceId,
|
||||
serviceId: c.notifyServiceid,
|
||||
characteristicId: c.notifyCharactId,
|
||||
state: true,
|
||||
success: function (res) {
|
||||
console.log("开始监听成功。。。。")
|
||||
if(res.errCode=='0'){
|
||||
//订阅特征值
|
||||
uni.onBLECharacteristicValueChange(function(data){
|
||||
// data.characteristicId
|
||||
// data.deviceId
|
||||
// data.serviceId
|
||||
// data.value
|
||||
console.log("监听到特征值:"+JSON.stringify(data));
|
||||
|
||||
if(these.featrueValueCallback){
|
||||
these.featrueValueCallback(data);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
var linkState = function(res) {
|
||||
console.log("获取已连接的设备回调" + JSON.stringify(res))
|
||||
let flag = res.devices.find(function(v) {
|
||||
if (v.deviceId == deviceId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (flag) {
|
||||
console.log("设备状态已连接");
|
||||
|
||||
lindedCallback(deviceId);
|
||||
|
||||
return;
|
||||
} else {
|
||||
console.log("设备未连接");
|
||||
linkDevice(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
var linkDevice = function(id) {
|
||||
console.log("正在连接"+id);
|
||||
uni.createBLEConnection({
|
||||
deviceId: id,
|
||||
timeout: 30000,
|
||||
success: function(info) {
|
||||
|
||||
console.log("连接成功");
|
||||
|
||||
uni.setBLEMTU({
|
||||
deviceId: id,
|
||||
mtu: 512,
|
||||
success: () => {
|
||||
console.log("mtu设置成功");
|
||||
if(linkedDevices){
|
||||
console.log("11111"+JSON.stringify(linkedDevices));
|
||||
f = linkedDevices.find(function (v) {
|
||||
return v.deviceId == id;
|
||||
});
|
||||
}else{
|
||||
console.log("22222")
|
||||
f=null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!f) {
|
||||
console.log("缓存中没有找到该设备")
|
||||
|
||||
these.getLinkBlue(function (res) {
|
||||
if (res.devices && res.devices.length) {
|
||||
let f = res.devices.find(function (v) {
|
||||
return v.deviceId == id;
|
||||
});
|
||||
linkedDevices.push(f);
|
||||
uni.setStorageSync(key, JSON.stringify(linkedDevices));
|
||||
|
||||
getService(id);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
console.log("缓存中已连接过");
|
||||
if (!f.services) {
|
||||
getService(id);
|
||||
} else {
|
||||
|
||||
lindedCallback(id);
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: function() {
|
||||
console.log("mtu设置失败")
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
fail: function(ex) {
|
||||
if (error) {
|
||||
console.log("蓝牙连接失败" + JSON.stringify(error));
|
||||
error(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//获取服务
|
||||
var getService = function(id) {
|
||||
|
||||
var repeatCnt = 0;
|
||||
var startgetService = function() {
|
||||
uni.getBLEDeviceServices({
|
||||
deviceId: id,
|
||||
success: function(res) {
|
||||
if (res.services && res.services.length > 0) {
|
||||
console.log("获取到服务:" + JSON.stringify(res));
|
||||
|
||||
linkedDevices.find(function(v) {
|
||||
if (v.deviceId == id) {
|
||||
v.services = res.services;
|
||||
}
|
||||
});
|
||||
uni.setStorageSync(key, JSON.stringify(linkedDevices));
|
||||
var promises = [];
|
||||
for (var i = 0; i < res.services.length; i++) {
|
||||
let service = res.services[i];
|
||||
promises.push(getFeatrus(id, service.uuid));
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
.then(results => {
|
||||
console.log('所有操作成功完成', results);
|
||||
|
||||
lindedCallback(id);
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('至少一个操作失败', error);
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
repeatCnt++;
|
||||
if (repeatCnt > 5) {
|
||||
|
||||
lindedCallback(id);
|
||||
|
||||
return;
|
||||
}
|
||||
setTimeout(function() {
|
||||
startgetService(id);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
startgetService(id);
|
||||
}, 1000);
|
||||
}
|
||||
//获取特性
|
||||
var getFeatrus = function(id, serviceId) {
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
uni.getBLEDeviceCharacteristics({
|
||||
deviceId: id,
|
||||
serviceId: serviceId,
|
||||
success: (res) => {
|
||||
console.log("获取到特征:" + JSON.stringify(res));
|
||||
|
||||
//写特征
|
||||
let writeChar = res.characteristics.find(char =>
|
||||
char.uuid.indexOf("FFE1") > -1
|
||||
);
|
||||
//通知特征
|
||||
let notiChar = res.characteristics.find(char =>
|
||||
char.uuid.indexOf("FFE2") > -1
|
||||
);
|
||||
|
||||
linkedDevices.find(function(v) {
|
||||
if (v.deviceId == id) {
|
||||
if (!v.Characteristics) {
|
||||
v.Characteristics = [];
|
||||
}
|
||||
v.Characteristics = v.Characteristics.concat(res
|
||||
.characteristics);
|
||||
|
||||
if (writeChar) {
|
||||
v.writeServiceId = serviceId;
|
||||
v.wirteCharactId = writeChar.uuid;
|
||||
}
|
||||
|
||||
if (notiChar) {
|
||||
v.notifyServiceid = serviceId;
|
||||
v.notifyCharactId = notiChar.uuid;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
uni.setStorageSync(key, JSON.stringify(linkedDevices));
|
||||
resolve(res);
|
||||
},
|
||||
fail: (ex) => {
|
||||
console.log("获取特征出现异常:" + JSON.stringify(ex));
|
||||
resolve(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
//监测蓝牙状态变化
|
||||
uni.onBLEConnectionStateChange(function(res) {
|
||||
if (!res.connected) {
|
||||
console.log("蓝牙断开连接" + res.deviceId + "");
|
||||
// lindDevice(res.deviceId);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("正在获取蓝牙适配器状态")
|
||||
this.CheckBlue((res) => {
|
||||
console.log("蓝牙状态:" + JSON.stringify(res));
|
||||
if (res.available) {
|
||||
this.getLinkBlue(linkState);
|
||||
} else {
|
||||
console.log("蓝牙适配器不可用,正在初始化");
|
||||
this.OpenBlue(false, () => {
|
||||
this.getLinkBlue(linkState);
|
||||
}, () => {
|
||||
console.log("请引导用户打开蓝牙");
|
||||
these.showBluetoothGuide();
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
//断开连接
|
||||
disconnectDevice: function(deviceId) {
|
||||
|
||||
var disconnect = function(id) {
|
||||
uni.closeBLEConnection({
|
||||
deviceId: id,
|
||||
success: (res) => {
|
||||
console.log("蓝牙连接已断开");
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
if (deviceId) {
|
||||
disconnect(deviceId);
|
||||
return;
|
||||
}
|
||||
//断开所有已连接的设备
|
||||
this.getLinkBlue(function(res) {
|
||||
if (res.devices && res.devices.length > 0) {
|
||||
for (var i = 0; i < res.devices.length; i++) {
|
||||
let item = res.devices[i];
|
||||
disconnect(item.deviceId);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log("无连接设备");
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
//发送二进制数据
|
||||
sendData: function(deviceid, buffer) {
|
||||
|
||||
console.log("准备向设备发送数据,deviceid=" + deviceid);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!deviceid) {
|
||||
reject(`deviceid为空,请输入要发送的设备`);
|
||||
return;
|
||||
}
|
||||
console.log("准备发送数据包");
|
||||
let key = "linkedDevices";
|
||||
var store = uni.getStorageInfoSync();
|
||||
var f = store.keys.find(function(v) {
|
||||
return v == key;
|
||||
});
|
||||
console.log("倒计时:5");
|
||||
var linkedDevices = [];
|
||||
if (f) {
|
||||
var str = uni.getStorageSync(key);
|
||||
if (str) {
|
||||
linkedDevices = JSON.parse(str);
|
||||
}
|
||||
|
||||
}
|
||||
console.log("倒计时:4");
|
||||
if (linkedDevices && linkedDevices.length && linkedDevices.length > 0) {
|
||||
console.log("倒计时:3");
|
||||
f = linkedDevices.find(function(v) {
|
||||
return v.deviceId == deviceid;
|
||||
|
||||
});
|
||||
console.log("f=" + JSON.stringify(f));
|
||||
// console.log("deviceid=" + deviceid);
|
||||
console.log("倒计时:2");
|
||||
if (f) {
|
||||
console.log("倒计时:1");
|
||||
uni.writeBLECharacteristicValue({
|
||||
deviceId: f.deviceId,
|
||||
serviceId: f.writeServiceId,
|
||||
characteristicId: f.wirteCharactId,
|
||||
value: buffer,
|
||||
success: () => {
|
||||
console.log("发送数据成功");
|
||||
resolve();
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log("发送数据失败" + JSON.stringify(err));
|
||||
reject(`发送数据失败: ${err.errMsg}`);
|
||||
},
|
||||
complete: function() {
|
||||
console.log("发送数据complete");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(`已连接设备中无法找到此设备`);
|
||||
// console.log("警报:已连接设备中无法找到此设备")
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log("检测到未与设备建立连接");
|
||||
reject(`检测到未与设备建立连接`);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
},
|
||||
sendDataNew: function(deviceid, serviceId, characteristicId, buffer) {
|
||||
|
||||
console.log("准备向设备发送数据,deviceid=" + deviceid);
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.writeBLECharacteristicValue({
|
||||
deviceId: deviceid,
|
||||
serviceId: serviceId,
|
||||
characteristicId: characteristicId,
|
||||
value: buffer,
|
||||
success: () => {
|
||||
console.log("发送数据成功");
|
||||
resolve();
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log("发送数据失败" + JSON.stringify(err));
|
||||
reject(`发送数据失败: ${err.errMsg}`);
|
||||
},
|
||||
complete: function() {
|
||||
console.log("发送数据complete");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,10 @@ export function deviceTypeList(params) {
|
||||
}
|
||||
|
||||
// 删除设备列表接口
|
||||
export function deviceUnbind(data) {
|
||||
export function deviceUnbind(id) {
|
||||
return request({
|
||||
url: '/app/device/unBind',
|
||||
url: `/app/device/unBind?id=${id}`,
|
||||
method: 'DELETE',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
// 重命名设备
|
||||
|
19
api/common/operatingInstruct/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
import qs from 'qs'
|
||||
// 查询列表
|
||||
export function fileInfo(params) {
|
||||
return request({
|
||||
url: '/app/file/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 删除
|
||||
export function fileDelete(ids) {
|
||||
return request({
|
||||
url: `/app/file/delete/${ids}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
32
api/common/operationVideo/index.js
Normal file
@ -0,0 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
// 视频列表
|
||||
export function listOperationVideos(params) {
|
||||
return request({
|
||||
url: '/app/operationVideo/listOperationVideos',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
// 添加
|
||||
export function addOperationVideo(data) {
|
||||
return request({
|
||||
url: '/app/operationVideo/addOperationVideo',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// 编辑
|
||||
export function editOperationVideo(data) {
|
||||
return request({
|
||||
url: '/app/operationVideo/editOperationVideo',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// 删除视频列表
|
||||
export function deleteOperationVideo(ids) {
|
||||
return request({
|
||||
url: `/app/operationVideo/deleteOperationVideo/${ids}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
258
components/BottomSlideMenuPlus/BottomSlideMenuPlus.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<view class="message-popup" :class="{ 'show': config.show }">
|
||||
|
||||
<view class="mask" @click="closeMenu" :class="{ 'show': config.show }"
|
||||
:style="{backgroundColor:config.maskBgColor,display:(config.showMask?'':'none')}"
|
||||
>
|
||||
|
||||
</view>
|
||||
<view class="bottom-slide-menu" :style="{ backgroundColor: config.bgColor }" :class="{ 'show': config.show }"
|
||||
@touchmove.stop.prevent>
|
||||
<view class="menu-header" :class="config.showHeader?'':'displayNone'">
|
||||
<view class="title" :style="{ color: config.textColor}">{{ config.title }}</view>
|
||||
<view class="close-icon" @click="closeMenu"
|
||||
:style="{display:(config.showClose?'':'none')}"
|
||||
>
|
||||
<image src="/static/Images/public/close.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="menu-items" v-if="config.menuItems && config.menuItems.length>0">
|
||||
<view class="menu-item" v-for="(item, index) in config.menuItems" :key="index" :style="{
|
||||
color: config.textColor,
|
||||
textAlign: config.textAlign,
|
||||
height:config.itemHeight,
|
||||
lineHeight:config.itemHeight,
|
||||
paddingLeft: config.dividerMargin,
|
||||
paddingRight: config.dividerMargin
|
||||
}" @click="handleItemClick(item, index)">
|
||||
|
||||
<view class="p100" :style="{backgroundColor:config.activeIndex==index?config.itemBgColor:'',
|
||||
justifyContent:config.textAlign
|
||||
}">
|
||||
<image v-if="item.icon" :src="item.icon" mode="aspectFit"></image>
|
||||
<text>{{ item.text }}</text>
|
||||
</view>
|
||||
|
||||
|
||||
<view v-if="index<config.menuItems.length-1" class="menu-line"
|
||||
:style="{paddingLeft: config.dividerMargin,paddingRight: config.dividerMargin}">
|
||||
<view class="menu-divider"
|
||||
:style="{borderTopWidth: config.dividerThickness,borderTopColor: config.dividerColor}">
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<slot v-else></slot>
|
||||
<view @click="btnClick" class="bottom-btn" :class="config.showBtn?'':'displayNone'"
|
||||
:style="{ backgroundColor: config.btnBgColor ,color:config.btnTextColor}">
|
||||
{{config.btnText}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BottomSlideMenuPlus',
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
show: false,//是否显示
|
||||
showHeader: false,//是否显示标头
|
||||
showMask:true,//是否显示mask
|
||||
showDivider: false,//是否在两个项之间显示分隔线
|
||||
showBtn: false,//是否显示底部按钮
|
||||
showClose:false,//是否显示右上角关闭按钮
|
||||
maskBgColor:'',//mask的颜色
|
||||
menuItems: [],//菜单项 包含icon text
|
||||
activeIndex: -1,//当前已选中的项编号
|
||||
bgColor: '#2a2a2a',//主体背景
|
||||
itemBgColor: '#3a3a3a',//各项被选中后的背景
|
||||
textColor: '#ffffffde',//各项的文字颜色
|
||||
textAlign: 'flex-start',//各项的文字居中方式
|
||||
title: '',//header的文字
|
||||
dividerColor: '#00000000',//分隔线颜色
|
||||
dividerThickness: '0rpx',//分隔线宽度
|
||||
dividerMargin: '10rpx',//分隔线距离两边的宽度
|
||||
itemHeight: '80rpx',//各项的调试
|
||||
btnBgColor: "#bbe600",//按钮颜色
|
||||
btnText: "确定",//按钮文字
|
||||
btnTextColor: "#000000",//按钮文字颜色
|
||||
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
closeMenu() {
|
||||
|
||||
this.$emit('close');
|
||||
},
|
||||
handleItemClick(item, index) {
|
||||
|
||||
|
||||
this.$emit('itemClick', item, index);
|
||||
},
|
||||
btnClick() {
|
||||
|
||||
let item = null;
|
||||
let index = null;
|
||||
if (this.config.activeIndex > -1) {
|
||||
item = this.config.menuItems[this.config.activeIndex];
|
||||
}
|
||||
index = this.config.activeIndex;
|
||||
this.$emit('btnClick', item, index);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-popup {
|
||||
position: relative;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-popup.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.p100 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0rpx 20rpx;
|
||||
}
|
||||
|
||||
.displayNone {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.bottom-slide-menu {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
transition: transform 0.3s ease-out;
|
||||
transform: translateY(100%);
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9998;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.bottom-slide-menu.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 30rpx;
|
||||
/* border-bottom: 0.03125rem solid #eeeeee; */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
|
||||
}
|
||||
|
||||
.close-icon image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menu-items {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
box-sizing: border-box;
|
||||
font-size: 28rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-item image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
/* 分隔线样式 */
|
||||
|
||||
.menu-line {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.menu-divider {
|
||||
border-top-style: solid;
|
||||
width: 100%;
|
||||
height: 0px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.bottom-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
text-align: center;
|
||||
color: rgba(35, 35, 35, 0.87);
|
||||
font-family: PingFang SC;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
line-height: 90rpx;
|
||||
letter-spacing: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
381
components/MessagePopup/MessagePopup.vue
Normal file
@ -0,0 +1,381 @@
|
||||
<template>
|
||||
<view class="message-popup" v-if="visible">
|
||||
<view class="popup-mask" @click="handleMaskClick"></view>
|
||||
<view
|
||||
class="popup-content"
|
||||
:style="{
|
||||
backgroundColor: bgColor || getTypeStyle('bgColor'),
|
||||
borderColor: borderColor || getTypeStyle('borderColor'),
|
||||
color: textColor || getTypeStyle('textColor')
|
||||
}"
|
||||
>
|
||||
<view v-if="showHeader&&headerTxt" class="header"
|
||||
|
||||
>{{headerTxt}}</view>
|
||||
|
||||
<view v-if="visibleClose" class="rightClose"
|
||||
:style="{color:textColor || getTypeStyle('textColor')}"
|
||||
@click="closeClick"
|
||||
>x</view>
|
||||
<view v-if="!visiblePrompt">
|
||||
<image
|
||||
v-if="iconUrl"
|
||||
:src="iconUrl"
|
||||
mode="aspectFit"
|
||||
class="popup-icon"
|
||||
:style="{ tintColor: textColor || getTypeStyle('textColor') }"
|
||||
></image>
|
||||
<view class="popup-message">{{ message }}</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view v-else class="popup-prompt">
|
||||
<text class="popup-prompt-title">{{ promptTitle || '请输入信息' }}</text>
|
||||
<input
|
||||
class="popup-prompt-input"
|
||||
:placeholder="promptPlaceholder"
|
||||
:value="modelValue"
|
||||
@input="handleInput"
|
||||
@confirm="handleButtonClick"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="popBtnContent">
|
||||
|
||||
<view
|
||||
class="popup-button-cancel"
|
||||
:style="{display:showCancel?'block':'none'}"
|
||||
@click="handleCancelClick"
|
||||
>{{ buttonCancelText?buttonCancelText:'取消' }}</view>
|
||||
|
||||
<view
|
||||
class="popup-button"
|
||||
:style="{
|
||||
backgroundColor: buttonBgColor || getTypeStyle('buttonBgColor'),
|
||||
color: buttonTextColor || getTypeStyle('buttonTextColor')
|
||||
}"
|
||||
@click="handleButtonClick"
|
||||
>{{ buttonText }}</view>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MessagePopup',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
visibleClose:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
visiblePrompt: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
promptTitle: String,
|
||||
promptPlaceholder: {
|
||||
type: String,
|
||||
default: "请输入"
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: value => ['success', 'error', 'info','custom'].includes(value)
|
||||
},
|
||||
bgColor: String,
|
||||
borderColor: String,
|
||||
textColor: String,
|
||||
buttonBgColor: String,
|
||||
buttonTextColor: String,
|
||||
iconUrl: String,
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
buttonCancelText:{
|
||||
type:String,
|
||||
default:'取消'
|
||||
},
|
||||
showCancel:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
showHeader:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
headerTxt:{
|
||||
type:String,
|
||||
default:""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: this.modelValue
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听外部值变化
|
||||
modelValue(newVal) {
|
||||
this.inputValue = newVal
|
||||
},
|
||||
// 监听模式切换
|
||||
visiblePrompt(newVal) {
|
||||
console.log('[MessagePopup] visiblePrompt changed to:', newVal)
|
||||
// 重置输入值
|
||||
if (newVal) {
|
||||
this.inputValue = this.modelValue
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
getTypeStyle(styleType) {
|
||||
const styles = {
|
||||
success: {
|
||||
bgColor: '#f0fff0',
|
||||
borderColor: '#52c41a',
|
||||
textColor: '#389e0d',
|
||||
buttonBgColor: '#52c41a',
|
||||
buttonTextColor: '#FFFFFF'
|
||||
},
|
||||
error: {
|
||||
bgColor: '#fff0f0',
|
||||
borderColor: '#ff4d4f',
|
||||
textColor: '#cf1322',
|
||||
buttonBgColor: '#ff4d4f',
|
||||
buttonTextColor: '#FFFFFF'
|
||||
},
|
||||
info: {
|
||||
bgColor: '#e6f7ff',
|
||||
borderColor: '#1890ff',
|
||||
textColor: '#0050b3',
|
||||
buttonBgColor: '#1890ff',
|
||||
buttonTextColor: '#FFFFFF'
|
||||
}
|
||||
}
|
||||
return styles[this.type][styleType]
|
||||
},
|
||||
handleButtonClick() {
|
||||
console.log('[MessagePopup] Button clicked with value:', this.inputValue)
|
||||
this.$emit('buttonClick', this.inputValue)
|
||||
},
|
||||
handleMaskClick() {
|
||||
console.log('[MessagePopup] Mask clicked')
|
||||
this.$emit('maskClick')
|
||||
},
|
||||
closeClick(){
|
||||
this.$emit('closePop')
|
||||
},
|
||||
handleCancelClick(){
|
||||
this.$emit('cancelPop');
|
||||
},
|
||||
handleInput(e) {
|
||||
|
||||
this.inputValue = e.detail.value
|
||||
this.$emit('update:modelValue', this.inputValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
max-width: 400px;
|
||||
border-radius: 12px;
|
||||
padding: 30rpx;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
border-width: 2rpx;
|
||||
border-style: solid;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
}
|
||||
.header{
|
||||
width:100%;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
font-family: PingFang SC;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
letter-spacing: 0.07px;
|
||||
text-align: center;
|
||||
}
|
||||
.rightClose{
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 20rpx;
|
||||
color: #FFFFFF;
|
||||
font-size: 36rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
line-height: 24rpx;
|
||||
}
|
||||
.popup-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
margin: 20rpx auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.popup-message {
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
padding: 20rpx 0rpx 30rpx 0rpx;
|
||||
|
||||
|
||||
font-weight: 400;
|
||||
|
||||
letter-spacing: 0.07px;
|
||||
}
|
||||
|
||||
.popup-prompt {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.popup-prompt-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.popup-prompt-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.popBtnContent{
|
||||
width:100%;
|
||||
height: 60rpx;
|
||||
margin-top: 20rpx;
|
||||
display: flex
|
||||
;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
.popup-button {
|
||||
width: 30%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 44rpx;
|
||||
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
letter-spacing: 4rpx;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.popup-button-cancel{
|
||||
width: 30%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 44rpx;
|
||||
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
letter-spacing: 4rpx;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
|
||||
background-color: #00000000;
|
||||
|
||||
color: #FFFFFFde;
|
||||
}
|
||||
|
||||
.popup-prompt{
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding-top: 40rpx;
|
||||
}
|
||||
|
||||
|
||||
.popup-prompt-title{
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
text-align: right;
|
||||
width: 30%;
|
||||
float: left;
|
||||
box-sizing: border-box;
|
||||
white-space:nowrap;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
.popup-prompt-input{
|
||||
float: left;
|
||||
width: 70%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
color: rgba(255, 255, 255, 0.87) ;
|
||||
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 8rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
129
components/Progress/Progress.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view class="message-popup" :class="{ 'show': config.show }">
|
||||
<view class="mask" @click="closeProgress" :class="{ 'show': config.show }"
|
||||
:style="{backgroundColor:config.maskBgColor,display:(config.showMask?'':'none')}">
|
||||
</view>
|
||||
|
||||
<view class="progress-content" :style="{backgroundColor:config.contentBgColor}">
|
||||
<text
|
||||
:style="{color:config.txtColor,display:(config.showText?'':'none')}">{{config.curr}}/{{config.total}}</text>
|
||||
<view class="jdWai"
|
||||
:style="{borderRadius:config.borderRadius, backgroundColor:config.proBgColor,border:config.proBorder,height:config.height}">
|
||||
<view class="jdNei"
|
||||
:style="{borderRadius:config.borderRadius,height:config.height,backgroundColor:config.currBgColor,border:config.currBorder,width:(config.curr/config.total*100)+'%'}">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Progress",
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
show: false, //是否显示
|
||||
showMask: true, //是否显示mask
|
||||
height:'20px',
|
||||
maskBgColor: '#00000000', //mask背景颜色
|
||||
contentBgColor: '#121212', //总背景颜色
|
||||
showText: true, //是否显示当前进度的文字
|
||||
txtColor: '#ffffffde', //文字的颜色
|
||||
curr: 0, //当前进度
|
||||
total: 100, //总进度
|
||||
proBgColor: '#2a2a2a', //进度条底色,
|
||||
proBorder: '', //进度条border
|
||||
currBgColor: '#bbe600', //当前进度底色
|
||||
currBorder: '', //当前进度border
|
||||
borderRadius:'10px'
|
||||
})
|
||||
}
|
||||
},methods: {
|
||||
closeProgress() {
|
||||
|
||||
this.$emit('closeProgress');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message-popup.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.p100 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0rpx 20rpx;
|
||||
}
|
||||
|
||||
.displayNone {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.progress-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.jdWai {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jdNei {
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
</style>
|
@ -22,7 +22,8 @@
|
||||
"Barcode" : {},
|
||||
"Camera" : {},
|
||||
"Maps" : {},
|
||||
"OAuth" : {}
|
||||
"OAuth" : {},
|
||||
"Geolocation" : {}
|
||||
},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
@ -58,6 +59,7 @@
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {
|
||||
"appid" : "uni.app.UNIA21EF43",
|
||||
"privacyDescription" : {
|
||||
"NSBluetoothPeripheralUsageDescription" : "需要蓝牙访问权限,用于设备通信",
|
||||
"NSBluetoothAlwaysUsageDescription" : "需要蓝牙访问权限,用于设备通信"
|
||||
@ -66,8 +68,21 @@
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {
|
||||
"geolocation" : {},
|
||||
"maps" : {},
|
||||
"geolocation" : {
|
||||
"amap" : {
|
||||
"name" : "amapHG8nIFW5",
|
||||
"__platform__" : [ "ios", "android" ],
|
||||
"appkey_ios" : "065c43f02c7b627a74ad7dd23b16bb4f",
|
||||
"appkey_android" : "d7d852dbda2b95f6f796fb9a711a9fee"
|
||||
}
|
||||
},
|
||||
"maps" : {
|
||||
"amap" : {
|
||||
"name" : "amapHG8nIFW5",
|
||||
"appkey_ios" : "065c43f02c7b627a74ad7dd23b16bb4f",
|
||||
"appkey_android" : "d7d852dbda2b95f6f796fb9a711a9fee"
|
||||
}
|
||||
},
|
||||
"oauth" : {},
|
||||
"push" : {}
|
||||
},
|
||||
|
56
pages.json
@ -1,11 +1,16 @@
|
||||
{
|
||||
"pages": [
|
||||
|
||||
{
|
||||
"path": "pages/common/login/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"path": "pages/common/index/index",
|
||||
"style": {
|
||||
@ -61,43 +66,68 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/6170/operationVideo/index",
|
||||
"path": "pages/common/operationVideo/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/6170/addvideo/index",
|
||||
"path": "pages/common/addvideo/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "添加"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
// 操作说明
|
||||
{
|
||||
"path": "pages/6170/editVideo/index",
|
||||
"path": "pages/common/operatingInstruct/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑视频"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
// 产品说明
|
||||
{
|
||||
"path": "pages/6170/operatingInstruct/index",
|
||||
"path": "pages/common/productDes/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/6155/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "6155"
|
||||
"path" : "pages/common/addBLE/addEquip",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "添加设备"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/6155/bluetooth/bluetooth",
|
||||
"path" : "pages/common/addBLE/LinkBle",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "扫描到的设备"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/6155/deviceDetail",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "HBY 6155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/6155/ImgCrop",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "图像裁剪",
|
||||
"navigationStyle": "custom",
|
||||
"fullscreen": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/common/map/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTitleText": "地图"
|
||||
}
|
||||
}
|
||||
|
||||
],
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#fff",
|
||||
"selectedColor": "#BBE600",
|
||||
|
82
pages/6155/ImgCrop.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<view>
|
||||
<canvas id="splashCanvas" canvas-id="splashCanvas" style="width: 160px; height: 80px; z-index: 9999;position: fixed; visibility: hidden;"></canvas>
|
||||
|
||||
|
||||
<qf-image-cropper :src="src" :showAngle="false" :width="1600" :height="800" fileType="jpg"
|
||||
@crop="handleCrop" :gpu="true">
|
||||
</qf-image-cropper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QfImageCropper from '@/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue';
|
||||
export default {
|
||||
components: {
|
||||
QfImageCropper
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
src: "",
|
||||
Statu: false
|
||||
|
||||
}
|
||||
},
|
||||
onLoad: function(option) {
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
var these = this;
|
||||
eventChannel.on('checkImg', function(data) {
|
||||
console.log("我收到你的消息了,消息内容是:" + JSON.stringify(data));
|
||||
these.src = data.data;
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleCrop(e) {
|
||||
var these = this;
|
||||
this.Statu = true;
|
||||
console.log("裁剪完成");
|
||||
console.log(e.tempFilePath);
|
||||
|
||||
const ctx = uni.createCanvasContext('splashCanvas', this);
|
||||
ctx.drawImage(
|
||||
e.tempFilePath,
|
||||
0,0,160,80
|
||||
);
|
||||
setTimeout(() => {
|
||||
ctx.draw(false, ()=>{
|
||||
console.log("abcdefg")
|
||||
uni.canvasGetImageData({
|
||||
canvasId: 'splashCanvas',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 160,
|
||||
height: 80,
|
||||
success: (res) => {
|
||||
// 处理像素数据并发送
|
||||
console.log("res.data.length="+res.data.length);
|
||||
// this.processAndSendImageData(res.data).then(
|
||||
// resolve).catch(reject);
|
||||
const eventChannel = these.getOpenerEventChannel();
|
||||
eventChannel.emit('ImgCutOver',res.data);
|
||||
uni.navigateBack();
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(`获取canvas数据失败: ${err.errMsg}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 500);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.canvas {
|
||||
width: 1600px;
|
||||
height: 800px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
</style>
|
@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<custom-navbar :showBack="true"></custom-navbar>
|
||||
<view class="pageContent" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<view class="scanner">
|
||||
<view class="pulse"></view>
|
||||
<view class="device-name">
|
||||
<!-- <image src="/static/images/svgg.png" class="bluth"></image> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="device">我的设备</view>
|
||||
<scroll-view class="device-list" scroll-y>
|
||||
<block v-for="(item, index) in deviceList" :key="index" :ref="'swipeItem_' + index">
|
||||
<!-- 设备卡片内容保持不变 -->
|
||||
<view @click.stop="handleFile(item)" class="device-card">
|
||||
<view class="device-header">
|
||||
<view class="deviceIMG">
|
||||
<!-- <image src="/static/images/bip.png" mode="" class="IMG"></image> -->
|
||||
</view>
|
||||
<view class="device-name1">
|
||||
<view>设备:001-00</view>
|
||||
<view class="ID">
|
||||
<view>ID:0222222</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
<view class="device">其他设备</view>
|
||||
<scroll-view class="device-list" scroll-y>
|
||||
<block v-for="(item, index) in 3" :key="index" :ref="'swipeItem_' + index">
|
||||
<!-- 设备卡片内容保持不变 -->
|
||||
<view @click.stop="handleFile(item)" class="device-card">
|
||||
<view class="device-header">
|
||||
<view class="deviceIMG">
|
||||
<!-- <image src="/static/images/bip.png" mode="" class="IMG"></image> -->
|
||||
</view>
|
||||
<view class="device-name1">
|
||||
<view>设备:001-00</view>
|
||||
<view class="ID">
|
||||
<view>ID:0222222</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 这里可以添加数据绑定
|
||||
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
|
||||
deviceList:[{}]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 扫描
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pageContent {
|
||||
min-height: 100vh;
|
||||
background-color: rgb(18, 18, 18);
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.scanner {
|
||||
position: relative;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 设备名称 */
|
||||
.device-name {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
background: rgba(187, 230, 0, 1);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bluth {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
/* 脉冲波纹效果 */
|
||||
.pulse {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(206, 242, 49, 0.05);
|
||||
background: rgba(206, 242, 49, 0.05);
|
||||
animation: pulse 4s infinite cubic-bezier(0.2, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 通过伪元素创建4层波纹 */
|
||||
.pulse::before,
|
||||
.pulse::after,
|
||||
.pulse .ripple-1,
|
||||
.pulse .ripple-2 {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(187, 230, 0, 0.4);
|
||||
border: 1px solid rgba(206, 242, 49, 0.04);
|
||||
animation: inherit;
|
||||
}
|
||||
|
||||
.pulse::before {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.pulse::after {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.pulse .ripple-1 {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.pulse .ripple-2 {
|
||||
animation-delay: 1.5s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
70% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.device {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 设备卡片 */
|
||||
.device-card {
|
||||
background-color: rgb(26, 26, 26);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding: 30rpx 0 10rpx 30rpx;
|
||||
}
|
||||
|
||||
.device-name1 {
|
||||
font-size: 32rpx;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
margin-left:40rpx;
|
||||
line-height: 50rpx;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.ID {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.device-status {
|
||||
width: 122rpx;
|
||||
height: 52rpx;
|
||||
font-size: 26rpx;
|
||||
border-radius: 0px 8px 0px 8px;
|
||||
background-color: rgb(42, 42, 42);
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
right: 0rpx;
|
||||
text-align: center;
|
||||
line-height: 52rpx;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width:8rpx;
|
||||
height: 40rpx;
|
||||
position: absolute;
|
||||
right:25rpx;
|
||||
top:85rpx;
|
||||
}
|
||||
|
||||
.online {
|
||||
color: rgb(187, 230, 0);
|
||||
}
|
||||
|
||||
.device-id {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
position: relative;
|
||||
padding: 0rpx 0rpx 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.deviceIMG {
|
||||
/* width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
background-color: rgba(42, 42, 42, 0.6);
|
||||
display: flex;
|
||||
align-items: center; */
|
||||
}
|
||||
|
||||
.IMG {
|
||||
width:38rpx;
|
||||
height: 80rpx;
|
||||
margin-left: 17%;
|
||||
}
|
||||
</style>
|
1334
pages/6155/deviceDetail.vue
Normal file
@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>6155</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
@ -2,7 +2,7 @@
|
||||
<view>
|
||||
<!-- 使用自定义导航栏 -->
|
||||
<custom-navbar :title="navTitle" :showBack="true" color="#FFFFFF" rightIcon="/static/images/path.png"
|
||||
@right-click="uploadStartup"></custom-navbar>
|
||||
@right-click="uploadStartup"></custom-navbar>
|
||||
<view class="device-detail-container" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<!-- 设备电量信息 -->
|
||||
<view class="battery-section">
|
||||
@ -79,7 +79,7 @@
|
||||
<image src="/static/images/jg.png" class="setIMG"></image>
|
||||
<view>
|
||||
<view class="battery-v2">激光模式</view>
|
||||
<!-- <view class="mode-v3">{{currentSecondaryMode}}</view> -->
|
||||
<!--<view class="mode-v3">{{currentSecondaryMode}}</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -231,7 +231,8 @@
|
||||
lightModeB: false,
|
||||
lightModeC: false, //激光提示框
|
||||
items: [],
|
||||
isFormExpanded: true // 默认展开
|
||||
isFormExpanded: true, // 默认展开
|
||||
deviceID: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -314,17 +315,19 @@
|
||||
// 操纵说明
|
||||
operatingInst() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/6170/operatingInstruct/index'
|
||||
url: `/pages/common/operatingInstruct/index?id=${this.deviceID}`
|
||||
})
|
||||
},
|
||||
// 产品参数
|
||||
productparams() {
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/common/productDes/index?id=${this.deviceID}`
|
||||
})
|
||||
},
|
||||
// 操作视频
|
||||
operatingVideo() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/6170/operationVideo/index'
|
||||
url: `/pages/common/operationVideo/index?id=${this.deviceID}`
|
||||
})
|
||||
|
||||
},
|
||||
@ -338,6 +341,10 @@
|
||||
handleDisagree() {
|
||||
this.lightModeC = false
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
console.log(options.id) // 输出: 123
|
||||
this.deviceID = options.id
|
||||
}
|
||||
|
||||
}
|
||||
@ -439,6 +446,12 @@
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.mode-v3 {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 26rpx;
|
||||
@ -549,7 +562,8 @@
|
||||
transform: translate(-17%, -100%);
|
||||
|
||||
}
|
||||
.uni-file-picker__container{
|
||||
|
||||
.uni-file-picker__container {
|
||||
width: 200rpx !important;
|
||||
border: 1px solid rgb(58, 58, 58);
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<view>
|
||||
<text>编辑视频</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
@ -1,350 +0,0 @@
|
||||
<template>
|
||||
<!-- 使用自定义导航栏 -->
|
||||
<view>
|
||||
<custom-navbar :title="navTitle" :showBack="true" backgroundColor="#202020" color="#FFFFFF" right-text="编辑"
|
||||
right-color="rgba(255, 255, 255, 0.87)" @right-click="handleEdit"></custom-navbar>
|
||||
<view class="device-page" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<view class="pathContent">
|
||||
<image src="/static/images/path3.png" mode="" class="path3"></image>
|
||||
</view>
|
||||
</view>
|
||||
<!--上传文件弹框 -->
|
||||
<view class="agreement-mask" v-if="opeartShow">
|
||||
<!-- 上传画面弹窗 -->
|
||||
<view class="agreement-popup">
|
||||
<!-- 标题 -->
|
||||
<view class="popup-title">编辑</view>
|
||||
<view class="popup-content">
|
||||
<view v-for="(item, index) in items" :key="index">
|
||||
<view class="item" :class="{'selected': index === selectedItemIndex}"
|
||||
@click="handleItemAction(index)"> <!-- 修改点击事件 -->
|
||||
<image :src="item.image" class="setIMG"></image>
|
||||
{{item.text}}
|
||||
<!-- 隐藏的文件输入(仅用于上传模式) -->
|
||||
<!-- <input v-if="index === 0" type="file" ref="fileInput" style="display: none"
|
||||
@change="onFileSelect"> -->
|
||||
<uni-file-picker v-if="index === 0" limit="1" file-mediatype="all"></uni-file-picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="agree" @click="handleupload">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="agreement-mask" v-if="opeartSuccess">
|
||||
<view class="agreement-popupC">
|
||||
<view class="popup-content">
|
||||
<image src="/static/images/path5.png" mode="" class="path2"></image>
|
||||
<view class="popup-Title">
|
||||
<view>上传成功!</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="btn agreeBtn" @click="handleBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- =============删除文件,列表文件=============== -->
|
||||
<view class="agreement-mask" v-if="opeartDelect">
|
||||
<!-- 上传画面弹窗 -->
|
||||
<view class="agreement-popup">
|
||||
<view class="popup-title">删除文件</view>
|
||||
<view class="popup-content">
|
||||
<!-- 设备列表 -->
|
||||
<scroll-view class="device-list" scroll-y>
|
||||
<view class="device-card" v-for="(item, index) in delectList" :key="index"
|
||||
@click="toggleSelect(index)">
|
||||
<!-- 复选框 -->
|
||||
<view class="checkbox" :class="{ checked: item.checked }">
|
||||
<uni-icons v-if="item.checked" type="checkmarkempty" size="18"
|
||||
color="rgb(0, 0, 0)"></uni-icons>
|
||||
</view>
|
||||
<!-- 设备信息 -->
|
||||
<view class="device-content">
|
||||
<view class="device-header">
|
||||
<view class="device-name">
|
||||
<view>设备:001-00</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="agree" @click="handleopearBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
navTitle: '操作说明',
|
||||
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
|
||||
opeartShow: false,
|
||||
items: [{
|
||||
text: '点击选择上传文件',
|
||||
selected: true,
|
||||
image: '/static/images/upload.png'
|
||||
},
|
||||
{
|
||||
text: '删除文件',
|
||||
selected: false,
|
||||
image: '/static/images/delete.png'
|
||||
}
|
||||
],
|
||||
selectedItemIndex: 0, // 默认选中第一个
|
||||
fileInput: false,
|
||||
opeartSuccess: false,
|
||||
opeartDelect: false,
|
||||
delectList: Array(6).fill().map((_, i) => ({
|
||||
id: `1321615${i}`,
|
||||
checked: false,
|
||||
showConfirm: false
|
||||
})),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 编辑
|
||||
handleEdit() {
|
||||
this.opeartShow = true
|
||||
},
|
||||
handleItemAction(index) {
|
||||
this.selectedItemIndex = index;
|
||||
if (index === 0) {
|
||||
// 调用 Uni-app 文件选择 API
|
||||
// uni.chooseFile({
|
||||
// count: 1, // 最多选择1个文件
|
||||
// success: (res) => {
|
||||
// const file = res.tempFiles[0];
|
||||
// this.items[0].text = `已选择: ${file.name}`;
|
||||
// this.selectedFile = file; // 存储文件对象
|
||||
// }
|
||||
// });
|
||||
} else {
|
||||
// 这里是走删除弹框列表
|
||||
this.opeartDelect = true //显示批量删除弹框列表
|
||||
this.opeartShow = false //隐藏编辑弹框
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onFileSelect(e) {
|
||||
const files = e.target.files;
|
||||
if (files.length > 0) {
|
||||
// 这里拿到文件对象,可以立即上传或显示文件名
|
||||
console.log("选择的文件:", files[0].name);
|
||||
// 示例:显示文件名在item.text中
|
||||
this.items[0].text = `已选择: ${files[0].name}`;
|
||||
}
|
||||
},
|
||||
// 确认
|
||||
handleupload() {
|
||||
this.opeartSuccess = true
|
||||
//this.opeartShow = false;
|
||||
},
|
||||
// 上传成功后确实弹框提示确认
|
||||
handleBtn() {
|
||||
this.opeartSuccess = false
|
||||
},
|
||||
toggleSelect(index) {
|
||||
this.delectList[index].checked = !this.delectList[index].checked
|
||||
this.$forceUpdate()
|
||||
},
|
||||
// 批量删除文件确认按钮
|
||||
handleopearBtn() {
|
||||
this.opeartDelect = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: rgb(18, 18, 18);
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.pathContent {
|
||||
background-color: rgb(52, 52, 52);
|
||||
width: 100%;
|
||||
min-height: 87vh;
|
||||
}
|
||||
|
||||
.files-button uni-button {
|
||||
background: red !important;
|
||||
position: absolute;
|
||||
left: 50rpx;
|
||||
top: 50rpx
|
||||
}
|
||||
|
||||
.path2 {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.popup-Title {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
padding: 20rpx 0rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.path3 {
|
||||
width: 172rpx;
|
||||
height: 114rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.agreement-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* 弹窗主体 */
|
||||
.agreement-popup {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
background-color: rgb(42, 42, 42);
|
||||
border-radius: 60rpx 60rpx 0rpx 0rpx;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
}
|
||||
|
||||
.agreement-popupC {
|
||||
width: 70%;
|
||||
background-color: rgb(42, 42, 42);
|
||||
border-radius: 40rpx;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid rgba(187, 230, 0, 0.3);
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agree {
|
||||
background-color: rgb(187, 230, 0);
|
||||
color: #232323;
|
||||
border: none;
|
||||
font-size: 24rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
font-size: 32rpx;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.setIMG {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.item.selected {
|
||||
background-color: rgb(58, 58, 58);
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
margin-top: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 通用按钮样式 */
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 32rpx;
|
||||
margin: 10rpx auto;
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
/* 同意按钮 */
|
||||
.agreeBtn {
|
||||
background-color: rgb(187, 230, 0);
|
||||
color: #232323;
|
||||
border: none;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 删除文件,弹框列表样式文件
|
||||
*/
|
||||
.device-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.device-content {
|
||||
border-radius: 8rpx;
|
||||
background: rgba(58, 58, 58, 1);
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
width: 93%;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||
margin-right: 20rpx;
|
||||
border-radius: 4rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -25rpx;
|
||||
}
|
||||
|
||||
.checkbox.checked {
|
||||
background-color: rgb(187, 230, 0);
|
||||
border-color: rgb(187, 230, 0);
|
||||
}
|
||||
</style>
|
22
pages/common/addBLE/LinkBle.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
596
pages/common/addBLE/addEquip.vue
Normal file
@ -0,0 +1,596 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="topAnimate">
|
||||
<view class="animate center">
|
||||
<view class="animateContent">
|
||||
<view class="circle"></view>
|
||||
<view class="circle"></view>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
|
||||
<view class="imgContent center">
|
||||
<view class="img center">
|
||||
<image src="/static/images/bluetooth.png" class="titleIco" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mainContent">
|
||||
<view class="p100">
|
||||
|
||||
<view class="lblTitle">配对设备</view>
|
||||
<view class="list" style="margin-bottom: 30rpx;">
|
||||
|
||||
<view class="item " v-for="item, index in PairEquip" v-show="PairEquip.length>0">
|
||||
<view class="leftImg ">
|
||||
<image src="/static/images/BLEAdd/device.png" class="titleIco" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
<view class="centertxt ">
|
||||
<view class="name" v-text="item.name"></view>
|
||||
<view class="id" v-text="item.deviceId"></view>
|
||||
</view>
|
||||
<view class="rightIco center">
|
||||
<image src="/static/images/BLEAdd/linked.png" class="img" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
</view>
|
||||
<view v-show="PairEquip.length==0" class="item center">
|
||||
<view class="noLink">无已配对设备</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="lblTitle">搜索设备</view>
|
||||
<view class="list searchList">
|
||||
<view class="item" v-on:click="Link(item,index)" v-for="item, index in EquipMents"
|
||||
v-show="!item['linkStatu']">
|
||||
<view class="leftImg ">
|
||||
<image src="/static/images/BLEAdd/device.png" class="titleIco" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
<view class="centertxt ">
|
||||
<view class="name" v-text="item.name?item.name:'Unnamed'"></view>
|
||||
<view class="id" v-text="item.deviceId"></view>
|
||||
</view>
|
||||
<view class="rightIco center">
|
||||
<image :src="isItemLink(item,index)" class="img" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<BottomSlideMenuPlus :config="Status.BottomMenu">
|
||||
<view class="openBlue">
|
||||
<view class="txt">
|
||||
当前手机蓝牙关闭,是否打开以扫描附近的蓝牙设备?
|
||||
</view>
|
||||
<view class="btns">
|
||||
<view class="btn cancel" @click="Status.BottomMenu.show=false">
|
||||
<view>取消</view>
|
||||
</view>
|
||||
<view class="ok btn" @click="gotoSetting">
|
||||
<view>开启</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</BottomSlideMenuPlus>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ble from '../../../api/6155/BlueHelper.js';
|
||||
import request from '../../../utils/request.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
Status: {
|
||||
BottomMenu: {
|
||||
show: false,
|
||||
showHeader: false,
|
||||
menuItems: [],
|
||||
activeIndex: -1,
|
||||
bgColor: '#2a2a2a',
|
||||
itemBgColor: '#3a3a3a',
|
||||
textColor: '#ffffffde',
|
||||
textAlign: 'flex-start',
|
||||
title: '主灯模式',
|
||||
showDivider: false,
|
||||
dividerColor: '#00000000',
|
||||
dividerThickness: '0rpx',
|
||||
dividerMargin: '10rpx',
|
||||
itemHeight: '80rpx',
|
||||
type: '',
|
||||
showBtn: false,
|
||||
btnBgColor: "#bbe600",
|
||||
btnText: "确定",
|
||||
btnTextColor: "#232323de",
|
||||
showMask: true,
|
||||
maskBgColor: '#00000066',
|
||||
showClose: false
|
||||
}
|
||||
|
||||
},
|
||||
PairEquip: [], //已配对设备
|
||||
EquipMents: [] //搜索出来的设备
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
onHide: function() {
|
||||
ble.StopSearch();
|
||||
},
|
||||
onBackPress: (e) => {
|
||||
ble.StopSearch();
|
||||
ble.disconnectDevice();
|
||||
},
|
||||
|
||||
onShow: function() {
|
||||
// return;
|
||||
var these = this;
|
||||
uni.getStorage({
|
||||
key: "linkedDevices",
|
||||
success: (res) => {
|
||||
this.PairEquip = JSON.parse(res.data);
|
||||
},
|
||||
fail: (ex) => {
|
||||
this.PairEquip = [];
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.UNI_PLATFORM == 'mp-weixin' ||
|
||||
process.env.UNI_PLATFORM == 'mp-alipay' ||
|
||||
process.env.UNI_PLATFORM == 'app-plus' ||
|
||||
process.env.UNI_PLATFORM == 'app'
|
||||
|
||||
) {
|
||||
//打开蓝牙开始搜索设备
|
||||
ble.OpenBlue(true, () => {
|
||||
ble.StartSearch(function(arr) {
|
||||
|
||||
arr = arr.devices;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
|
||||
arr[i].linkStatu = false;
|
||||
let f = these.EquipMents.find(function(v) {
|
||||
return v.deviceId == arr[i].deviceId;
|
||||
});
|
||||
|
||||
if (!f) {
|
||||
these.EquipMents.push(arr[i]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
console.log("设备列表:" + JSON.stringify(these.EquipMents));
|
||||
});
|
||||
},
|
||||
() => {
|
||||
these.showOpenSetting();
|
||||
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
console.log('当前环境:' + process.env.UNI_PLATFORM + '不支持蓝牙');
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
isItemLink: function(item, index) {
|
||||
let src = '/static/images/BLEAdd/noLink.png';
|
||||
if (this.PairEquip && this.PairEquip instanceof Array) {
|
||||
if (this.PairEquip.length > 0) {
|
||||
let f = this.PairEquip.find(function(v) {
|
||||
return v.deviceId == item.deviceId;
|
||||
});
|
||||
if (f) {
|
||||
src = '/static/images/BLEAdd/linked.png';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return src;
|
||||
},
|
||||
alert: function(title, content, callback) {
|
||||
if (!title) {
|
||||
title = '提示'
|
||||
}
|
||||
if (!content) {
|
||||
content = title;
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: title,
|
||||
content: content,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
console.log('用户点击确定');
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(res);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showOpenSetting: function() {
|
||||
this.Status.BottomMenu.show = true;
|
||||
},
|
||||
gotoSetting: function() {
|
||||
this.Status.BottomMenu.show = false;
|
||||
ble.showBluetoothGuide(false);
|
||||
},
|
||||
Link: function(item, index) {
|
||||
var these = this;
|
||||
if (process.env.UNI_PLATFORM == 'mp-weixin' ||
|
||||
process.env.UNI_PLATFORM == 'mp-alipay' ||
|
||||
process.env.UNI_PLATFORM == 'app-plus' ||
|
||||
process.env.UNI_PLATFORM == 'app'
|
||||
) {
|
||||
|
||||
uni.showLoading({
|
||||
title: "正在连接",
|
||||
mask: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
|
||||
|
||||
ble.LinkBlue(item.deviceId, function() {
|
||||
let c = these.PairEquip.find(function(v) {
|
||||
return v.deviceId == item.deviceId;
|
||||
});
|
||||
if (!c) {
|
||||
these.PairEquip.push(item);
|
||||
uni.setStorage({
|
||||
key: 'linkedDevices',
|
||||
data: JSON.stringify(these.PairEquip),
|
||||
success: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 调用绑定设备接口
|
||||
let promise = request({
|
||||
url: '/app/device/bind',
|
||||
method: 'POST',
|
||||
data: {
|
||||
deviceImei: '',
|
||||
deviceMac: item.deviceId,
|
||||
communicationMode: '1', //0是4g,1是蓝牙
|
||||
}
|
||||
});
|
||||
promise.then((res) => {
|
||||
console.log("1111" + JSON.stringify(res));
|
||||
if (res.code == 0) {
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: res.data,
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
});
|
||||
}
|
||||
}).catch((ex) => {
|
||||
|
||||
uni.showToast({
|
||||
title: '出现了未知的异常,操作失败',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}, (ex) => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
}, 0);
|
||||
|
||||
} else {
|
||||
these.alert("提示", "当前平台不支持蓝牙");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.noLink {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: #1d1d1d;
|
||||
color: #ffffffde;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.p100 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fleft {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fright {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.topAnimate {
|
||||
position: absolute;
|
||||
left: 0rpx;
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
top: 20px;
|
||||
/* 距离视口顶部 20px 时固定 */
|
||||
z-index: 100;
|
||||
/* 确保元素显示在最上层 */
|
||||
}
|
||||
|
||||
.animate {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.animate .animateContent {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.animate .imgContent {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.animate .imgContent .img {
|
||||
height: 100rpx;
|
||||
width: 100rpx;
|
||||
background: #bbe600;
|
||||
border-radius: 50%;
|
||||
|
||||
}
|
||||
|
||||
.animate .titleIco {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: #bbe60033;
|
||||
animation: expand 3s infinite ease-out;
|
||||
}
|
||||
|
||||
.circle:nth-child(2) {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.circle:nth-child(3) {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
@keyframes expand {
|
||||
0% {
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0.8;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 18.75rem;
|
||||
height: 18.75rem;
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: calc(100% - 400rpx);
|
||||
overflow-y: scroll;
|
||||
position: absolute;
|
||||
top: 400rpx;
|
||||
left: 0rpx;
|
||||
}
|
||||
|
||||
|
||||
.mainContent .p100 {}
|
||||
|
||||
.mainContent .lblTitle {
|
||||
color: #ffffffde;
|
||||
font-family: PingFang SC;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
height: 28rpx;
|
||||
line-height: 28rpx;
|
||||
}
|
||||
|
||||
.list {
|
||||
min-height: 120rpx;
|
||||
}
|
||||
|
||||
.searchList {
|
||||
width: 100%;
|
||||
height: calc(100% - 186rpx);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.list .item {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 30rpx;
|
||||
border-radius: 8px;
|
||||
background: #1a1a1a;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.list .item .leftImg {
|
||||
width: 100rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.list .item .centertxt {
|
||||
width: calc(100% - 150rpx);
|
||||
height: 100%;
|
||||
padding-left: 10rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-evenly;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
|
||||
}
|
||||
|
||||
.list .item .rightIco {
|
||||
width: 50rpx;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.list .item .leftImg .titleIco {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list .item .name {
|
||||
color: #ffffffde;
|
||||
font-family: PingFang SC;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
line-height: 50rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.list .item .id {
|
||||
color: #ffffff99;
|
||||
font-family: PingFang SC;
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
line-height: 30rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.list .item .rightIco .img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
.openBlue {
|
||||
padding: 30rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.openBlue .txt {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
font-family: PingFang SC;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.14rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.openBlue .btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
margin-top: 50rpx;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.openBlue .btn {
|
||||
border-radius: 91rpx;
|
||||
box-sizing: border-box;
|
||||
width: 25%;
|
||||
height: 60rpx;
|
||||
text-align: center;
|
||||
font-family: PingFang SC;
|
||||
font-size: 28rpx;
|
||||
letter-spacing: 12rpx;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
letter-spacing: 0.375rem;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
.openBlue .cancel {
|
||||
border: 1px solid rgba(255, 255, 255, 1);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
.openBlue .ok {
|
||||
background-color: #BBE600;
|
||||
color: #232323;
|
||||
|
||||
}
|
||||
</style>
|
@ -1,50 +1,60 @@
|
||||
<template>
|
||||
<view class="device-page">
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-content">
|
||||
<!-- 名称输入框 -->
|
||||
<view class="input-group">
|
||||
<text class="label">名称</text>
|
||||
<input class="input" type="text" placeholder="请输入名称" v-model="name" />
|
||||
<view>
|
||||
<custom-navbar :title="navTitle" :showBack="true" backgroundColor="#202020" color="#FFFFFF"></custom-navbar>
|
||||
<view class="device-page" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-content">
|
||||
<!-- 名称输入框 -->
|
||||
<view class="input-group">
|
||||
<text class="label">名称</text>
|
||||
<input class="input" type="text" placeholder="请输入名称" v-model="name" />
|
||||
</view>
|
||||
|
||||
<!-- 视频链接输入框 -->
|
||||
<view class="input-group">
|
||||
<text class="label">视频链接</text>
|
||||
<input class="input" type="text" placeholder="请输入视频链接" v-model="videoLink" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 视频链接输入框 -->
|
||||
<view class="input-group">
|
||||
<text class="label">视频链接</text>
|
||||
<input class="input" type="text" placeholder="请输入视频链接" v-model="videoLink" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 保存按钮 -->
|
||||
<button class="save-button" @click="saveData">保 存</button>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<button class="save-button" @click="saveData">保 存</button>
|
||||
|
||||
<!--===================== 保存成功提示框================== -->
|
||||
<view class="agreement-mask" v-if="showModal">
|
||||
<!-- 上传画面弹窗 -->
|
||||
<view class="agreement-popupC">
|
||||
<view class="popup-content">
|
||||
<image src="/static/images/path2.png" mode="" class="path2"></image>
|
||||
<view class="popup-Title">
|
||||
<view>保存成功!</view>
|
||||
<!--===================== 保存成功提示框================== -->
|
||||
<view class="agreement-mask" v-if="showModal">
|
||||
<!-- 上传画面弹窗 -->
|
||||
<view class="agreement-popupC">
|
||||
<view class="popup-content">
|
||||
<image src="/static/images/path2.png" mode="" class="path2"></image>
|
||||
<view class="popup-Title">
|
||||
<view>保存成功!</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="btn agreeBtn" @click="handleBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="btn agreeBtn" @click="handleBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import {
|
||||
addOperationVideo,
|
||||
editOperationVideo
|
||||
} from '@/api/common/operationVideo/index.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
|
||||
navTitle: '添加',
|
||||
name: '',
|
||||
videoLink: '',
|
||||
showModal: false
|
||||
showModal: false,
|
||||
deviceID: "",
|
||||
id: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@ -59,20 +69,63 @@
|
||||
})
|
||||
return false
|
||||
}
|
||||
if (this.videoLink == '') {
|
||||
if (this.videoLink == '') {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '视频链接不为空'
|
||||
})
|
||||
return false
|
||||
}
|
||||
this.showModal = true
|
||||
uni.navigateBack()
|
||||
let data = {
|
||||
videoName: this.name,
|
||||
videoUrl: this.videoLink,
|
||||
deviceId: this.deviceID,
|
||||
id: this.id
|
||||
}
|
||||
// 添加编辑
|
||||
const apiMethod = this.editData ? editOperationVideo : addOperationVideo;
|
||||
console.log('apiMethod:', apiMethod);
|
||||
apiMethod(data).then(res => {
|
||||
if (res.code == 200) {
|
||||
this.showModal = true;
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
// 确定提示框
|
||||
handleBtn() {
|
||||
this.showModal = false
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.item) {
|
||||
// 解析传递过来的item对象
|
||||
this.editData = JSON.parse(decodeURIComponent(options.item));
|
||||
// 回显数据
|
||||
this.name = this.editData.videoName || '';
|
||||
this.videoLink = this.editData.videoUrl || '';
|
||||
this.deviceID = this.editData.deviceId || '';
|
||||
this.id = this.editData.id
|
||||
// 修改导航标题
|
||||
this.navTitle = '编辑';
|
||||
} else if (options.id) {
|
||||
this.deviceID = options.id;
|
||||
this.navTitle = '添加';
|
||||
this.editData = null; // 明确设置为 null
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -14,10 +14,9 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="sendFlex">
|
||||
<view>
|
||||
<image src="/static/images/gprs.png" class="gprs"></image>
|
||||
</view>
|
||||
|
||||
<view class="callpolice">报警</view>
|
||||
<view class="Sendmessage" @click="location">位置</view>
|
||||
<view class="Sendmessage" @click="handleSend">发送信息</view>
|
||||
</view>
|
||||
|
||||
@ -25,9 +24,8 @@
|
||||
style="height:80vh;">
|
||||
<uni-swipe-action ref="swipeAction">
|
||||
<block v-for="(item, index) in deviceList" :key="index" :ref="'swipeItem_' + index">
|
||||
<uni-swipe-action-item :right-options="Options" @click="handleSwipeClick($event, item, index)"
|
||||
class="device-card">
|
||||
<!-- 设备卡片内容保持不变 -->
|
||||
<uni-swipe-action-item :right-options="Options"
|
||||
@click="handleSwipeClick($event, item, index)" class="device-card">
|
||||
<view @click.stop="handleFile(item)">
|
||||
<view class="device-header">
|
||||
<view class="deviceIMG">
|
||||
@ -35,17 +33,20 @@
|
||||
</view>
|
||||
<view class="device-name">
|
||||
<view>设备:{{item.deviceName}}</view>
|
||||
<view class="ID" v-if="item.communicationMode==0">ID:{{item.deviceImei}}</view>
|
||||
<view class="ID" v-else>ID:{{item.deviceMac}}</view>
|
||||
<view class="ID">
|
||||
<view class="ID" v-if="item.communicationMode==0">ID:{{item.deviceImei}}
|
||||
</view>
|
||||
<view class="ID" v-else>ID:{{item.deviceMac}}</view>
|
||||
<view class="onlines" v-if="item.communicationMode==0">在线</view>
|
||||
<view>电量:90%</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="device-status online">已连接</view> -->
|
||||
<view class="device-status unline">未连接</view>
|
||||
<view class="device-info" v-if="item.communicationMode==0">
|
||||
<text class="onlines">在线</text>
|
||||
<text class="line"></text>
|
||||
<text>电量:90%</text>
|
||||
<view class="" v-if="item.communicationMode==1">
|
||||
<view class="device-status online">已连接</view>
|
||||
<view class="device-status unline">未连接</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<image src="/static/images/cires.png" class="circle"></image>
|
||||
</uni-swipe-action-item>
|
||||
@ -152,7 +153,7 @@
|
||||
}
|
||||
],
|
||||
page: 1, // 当前页码
|
||||
size:10, // 每页条数
|
||||
size: 10, // 每页条数
|
||||
total: 0, // 总数据量
|
||||
loading: false,
|
||||
finished: false,
|
||||
@ -165,6 +166,7 @@
|
||||
getTab() {
|
||||
deviceTypeList({}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
console.log("deviceTypeList=" + JSON.stringify(res.data));
|
||||
this.tabs = [{
|
||||
id: '',
|
||||
name: '全部设备',
|
||||
@ -237,19 +239,26 @@
|
||||
});
|
||||
break;
|
||||
case 'bluetooth':
|
||||
// uni.navigateTo({
|
||||
// url: 'pages/common/addBLE/AddDevice',
|
||||
// success:(res)=>{
|
||||
// res.eventChannel.emit('key', { data: 'data from starter page' })
|
||||
// },fail: (ex) => {
|
||||
// console.log("跳转失败了",JSON.stringify(ex));
|
||||
// }
|
||||
// });
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages/6155/bluetooth/bluetooth'
|
||||
});
|
||||
url: "/pages/common/addBLE/addEquip"
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
// 右滑点击事件处理
|
||||
handleSwipeClick(e, item, index) {
|
||||
const {
|
||||
content
|
||||
} = e
|
||||
const {content} = e
|
||||
console.log(e, 'eeeee');
|
||||
switch (content.text) {
|
||||
switch (e.content.text) {
|
||||
case '删除':
|
||||
this.handleDeleteDevice(item, index)
|
||||
break
|
||||
@ -270,7 +279,7 @@
|
||||
let data = {
|
||||
id: this.deviceId.id
|
||||
}
|
||||
deviceUnbind(data).then((res) => {
|
||||
deviceUnbind(this.deviceId.id).then((res) => {
|
||||
if (res.code == 200) {
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
@ -329,32 +338,53 @@
|
||||
url: '/pages/common/send/index'
|
||||
})
|
||||
},
|
||||
// 位置
|
||||
location(){
|
||||
uni.navigateTo({
|
||||
url:'/pages/common/map/index'
|
||||
})
|
||||
},
|
||||
handleFile(item) {
|
||||
console.log(item, 'item');
|
||||
console.log('item' + JSON.stringify(item));
|
||||
// communicationMode 0是4G 1是蓝牙
|
||||
if (item.communicationMode == 0) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/6170/deviceControl/index'
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: '/pages/6155/index'
|
||||
})
|
||||
}
|
||||
url: `/pages/6170/deviceControl/index?id=${item.id}`
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.typeName == '6155') {
|
||||
uni.navigateTo({
|
||||
url: "/pages/6155/deviceDetail",
|
||||
events: {
|
||||
ack: function(data) {
|
||||
|
||||
}
|
||||
},
|
||||
success: (res) => {
|
||||
res.eventChannel.emit('detailData', {
|
||||
data: item
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
onIntall() {
|
||||
this.page = 1;
|
||||
this.finished = false;
|
||||
this.getData(); // 重新加载第一页数据
|
||||
},
|
||||
},
|
||||
onIntall() {
|
||||
this.page = 1;
|
||||
this.finished = false;
|
||||
this.getData(); // 重新加载第一页数据
|
||||
onShow() {
|
||||
this.getTab()
|
||||
this.onIntall()
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.getTab()
|
||||
this.onIntall()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -381,15 +411,17 @@
|
||||
/* justify-content: space-around; */
|
||||
cursor: pointer;
|
||||
margin-bottom: 40rpx;
|
||||
/* min-width: 100%; */
|
||||
/* min-width: 100%; */
|
||||
/* 最小宽度 */
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
font-size: 28rpx;
|
||||
/* min-width: 120rpx; */
|
||||
padding: 0 30rpx; /* 左右内边距 */
|
||||
text-align: center; /* 文字居中 */
|
||||
/* min-width: 120rpx; */
|
||||
padding: 0 30rpx;
|
||||
/* 左右内边距 */
|
||||
text-align: center;
|
||||
/* 文字居中 */
|
||||
/* 设置最小宽度 */
|
||||
}
|
||||
|
||||
@ -407,45 +439,38 @@
|
||||
margin-bottom: 30rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.gprs{
|
||||
width:28rpx;
|
||||
height:35rpx;
|
||||
|
||||
.gprs {
|
||||
width: 28rpx;
|
||||
height: 35rpx;
|
||||
position: absolute;
|
||||
left:50rpx
|
||||
left: 50rpx
|
||||
}
|
||||
|
||||
.Sendmessage {
|
||||
margin-left: 50rpx;
|
||||
border: 1px solid rgba(187, 230, 0, 0.4);
|
||||
border-radius: 20px;
|
||||
padding: 7rpx 20rpx;
|
||||
color: rgba(187, 230, 0, 1);
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
.callpolice{
|
||||
border: 1px solid rgba(224, 52, 52, 0.4);
|
||||
border-radius: 20px;
|
||||
|
||||
.callpolice {
|
||||
color: rgba(224, 52, 52, 1);
|
||||
width: 150rpx;
|
||||
text-align: center;
|
||||
padding: 5rpx 10rpx;
|
||||
}
|
||||
|
||||
/* 设备卡片 */
|
||||
.device-card {
|
||||
background-color: rgb(26, 26, 26);
|
||||
border-radius: 16rpx;
|
||||
/* padding: 30rpx; */
|
||||
margin-bottom: 20rpx;
|
||||
/* box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); */
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
/* .device-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding: 30rpx 0 10rpx 30rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
@ -477,8 +502,8 @@
|
||||
width: 8rpx;
|
||||
height: 40rpx;
|
||||
position: absolute;
|
||||
right: 40rpx;
|
||||
top: 80rpx;
|
||||
right: 18rpx;
|
||||
top: 65rpx;
|
||||
}
|
||||
|
||||
.online {
|
||||
@ -498,11 +523,15 @@
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
position: relative;
|
||||
padding: 0rpx 0rpx 30rpx 30rpx;
|
||||
width: 100%;
|
||||
}
|
||||
.device-flex{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.deviceIMG {
|
||||
@ -547,19 +576,123 @@
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
padding: 20rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.device-uplod {
|
||||
background-color: rgb(26, 26, 26);
|
||||
border-radius: 16rpx;
|
||||
} */
|
||||
.device-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: 202rpx;
|
||||
line-height: 200rpx;
|
||||
margin-bottom: 15rpx;
|
||||
padding: 30rpx 0 10rpx 30rpx;
|
||||
}
|
||||
|
||||
.uni-swipe-action-item__options {
|
||||
transition: width 0.3s ease;
|
||||
.unline {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 32rpx;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
margin-left: 24rpx;
|
||||
line-height: 50rpx;
|
||||
width: 75%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ID {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.device-status {
|
||||
width: 122rpx;
|
||||
height: 52rpx;
|
||||
font-size: 26rpx;
|
||||
border-radius: 0px 8px 0px 8px;
|
||||
background-color: rgb(42, 42, 42);
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
right: 0rpx;
|
||||
text-align: center;
|
||||
line-height: 52rpx;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 8rpx;
|
||||
height: 40rpx;
|
||||
position: absolute;
|
||||
right: 25rpx;
|
||||
top: 60rpx;
|
||||
}
|
||||
|
||||
.online {
|
||||
color: rgb(187, 230, 0);
|
||||
}
|
||||
|
||||
.device-id {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
position: relative;
|
||||
padding: 0rpx 0rpx 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.deviceIMG {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
background-color: rgba(42, 42, 42, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.IMG {
|
||||
width: 68rpx;
|
||||
height: 50rpx;
|
||||
margin-left: 17%;
|
||||
}
|
||||
|
||||
.onlines {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.onlines::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 15rpx;
|
||||
height: 15rpx;
|
||||
background: rgb(0, 171, 103);
|
||||
border-radius: 50%;
|
||||
top: 20rpx;
|
||||
left: -20rpx
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 2rpx;
|
||||
height: 24rpx;
|
||||
background: linear-gradient(90deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgb(255, 255, 255) 50%,
|
||||
rgba(255, 255, 255, 0) 100%);
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.loading-status {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
padding: 20rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
|
@ -19,11 +19,11 @@
|
||||
<view class="checkbox-icon" :class="{ checked: isChecked }">
|
||||
<uni-icons v-if="isChecked" type="checkmarkempty" size="8" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="checkbox-label">我已认真阅读,理解并同意<a class="agreeT">《用户服务协议》</a>及<a
|
||||
class="agreeT">《隐私政策》</a></text>
|
||||
<text class="checkbox-label">我已认真阅读,理解并同意<a class="agreeT"
|
||||
@click.stop="goToPage('agreement')">《用户服务协议》</a>及<a class="agreeT"
|
||||
@click.stop="goToPage('privacy')">《隐私政策》</a></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button class="login-btn" @click="handleLogin">
|
||||
登录
|
||||
@ -62,12 +62,12 @@
|
||||
return {
|
||||
showView: false,
|
||||
//codebtn: '获取验证码',
|
||||
phone: '', //手机号码
|
||||
code: "", //验证码
|
||||
phone: '13058067817', //手机号码
|
||||
code: "123456", //验证码
|
||||
agreed: false,
|
||||
isCounting: false,
|
||||
countdown: 0,
|
||||
isChecked: false,
|
||||
isChecked: true,
|
||||
showAgreement: false, // 控制弹窗显示
|
||||
}
|
||||
},
|
||||
@ -114,7 +114,6 @@
|
||||
},
|
||||
// 登录
|
||||
async handleLogin() {
|
||||
console.log('33333');
|
||||
if (this.phone == '') {
|
||||
uni.showToast({
|
||||
title: '手机号不能为空',
|
||||
@ -146,7 +145,6 @@
|
||||
smsCode: this.code,
|
||||
tenantId: '894078' //租户ID
|
||||
})
|
||||
console.log(res, 'res44444');
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading()
|
||||
uni.setStorageSync('token', res.data.access_token) // 缓存token
|
||||
@ -174,19 +172,15 @@
|
||||
}
|
||||
|
||||
},
|
||||
// 打开具体协议
|
||||
openProtocol(type) {
|
||||
if (type === 'user') {
|
||||
// 跳转到用户协议页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/protocol/user'
|
||||
})
|
||||
} else {
|
||||
// 跳转到隐私政策页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/protocol/privacy'
|
||||
})
|
||||
}
|
||||
// 跳转到协议页面
|
||||
goToPage(type) {
|
||||
const url = type === 'agreement' ?
|
||||
'/pages/common/userAgreement/index' // 服务协议
|
||||
:
|
||||
'/pages/common/privacyAgreement/index'; // 隐私政策
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
});
|
||||
},
|
||||
|
||||
// 不同意
|
||||
|
37
pages/common/map/index.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<view class="page-section page-section-gap">
|
||||
<map class="map" :latitude="latitude" :longitude="longitude":show-location="true"
|
||||
:markers="covers">
|
||||
</map>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id: 0, // 使用 marker点击事件 需要填写id
|
||||
title: 'map',
|
||||
latitude: 39.909,
|
||||
longitude: 116.39742,
|
||||
covers: [{
|
||||
latitude: 39.909,
|
||||
longitude: 116.39742,
|
||||
|
||||
}],
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
.map {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
404
pages/common/operatingInstruct/index.vue
Normal file
@ -0,0 +1,404 @@
|
||||
<template>
|
||||
<!-- 使用自定义导航栏 -->
|
||||
<view>
|
||||
<custom-navbar title="操作说明" :showBack="true" backgroundColor="#202020" color="#FFFFFF"
|
||||
:right-text="isDeleteMode ? '完成' : '删除'" right-color="rgba(255, 255, 255, 0.87)"
|
||||
@right-click="toggleDeleteMode">
|
||||
</custom-navbar>
|
||||
|
||||
<view class="device-page" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<view class="example-body">
|
||||
<!-- 图片列表 -->
|
||||
<view class="image-list">
|
||||
<view class="image-item" v-for="(image, index) in instructionImages" :key="index"
|
||||
@click="handleImageClick(index,image)">
|
||||
<image :src="image.fileUrl" mode="aspectFit" class="instruction-image"
|
||||
:class="{ 'image-selected': isDeleteMode && selectedImages.includes(index) }"></image>
|
||||
|
||||
<!-- 多选模式下的选中标记 -->
|
||||
<view class="checkmark" v-if="isDeleteMode && selectedImages.includes(index)">
|
||||
<image src="/static/images/delete-icon.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮(非删除模式显示) -->
|
||||
<view class="upload-btn" v-if="!isDeleteMode">
|
||||
<uni-file-picker ref="filePicker" v-model="fileList" @select="selectFile" limit="9"
|
||||
class="custom-file-picker"></uni-file-picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部删除按钮(多选模式显示) -->
|
||||
<view class="delete-footer" v-if="isDeleteMode && selectedImages.length > 0">
|
||||
<button @click="deleteSelectedImages">删除({{selectedImages.length}})</button>
|
||||
</view>
|
||||
<!-- 删除弹框 -->
|
||||
<view class="agreement-mask" v-if="deleteShow">
|
||||
<view class="agreement-popupC">
|
||||
<view class="popup-content">
|
||||
<image src="/static/images/dell.png" mode="" class="svg"></image>
|
||||
<uni-icon class="trash"></uni-icon>
|
||||
<view>
|
||||
<view class="popup-Title">确定删除所选设备!</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="btn agreeBtn" @click="handleBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
baseURL,
|
||||
getToken,
|
||||
clientid
|
||||
} from '@/utils/request'
|
||||
import {
|
||||
fileInfo,
|
||||
fileDelete
|
||||
} from '@/api/common/operatingInstruct/index.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
|
||||
fileList: [],
|
||||
deviceID: "",
|
||||
instructionImages: [],
|
||||
isDeleteMode: false, // 是否删除模式
|
||||
selectedImages: [], // 选中的图片索引
|
||||
deleteShow: false,
|
||||
ImageData: [],
|
||||
filePicker: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 选择文件
|
||||
selectFile(e) {
|
||||
console.log('选择文件:', e)
|
||||
// const file = e.tempFiles[0].file
|
||||
const files = e.tempFiles.map(item => item.file)
|
||||
console.log('files', files);
|
||||
this.uploadFile(files)
|
||||
},
|
||||
// 上传图片
|
||||
uploadFile(files) {
|
||||
this.$refs.filePicker.clearFiles() // 通过ref操作uni-file-picker组件
|
||||
uni.showLoading({
|
||||
title: '上传中...'
|
||||
})
|
||||
files.forEach(file => {
|
||||
uni.uploadFile({
|
||||
url: baseURL + '/app/file/upload',
|
||||
filePath: file.path || file.url,
|
||||
name: 'files',
|
||||
formData: {
|
||||
deviceId: this.deviceID,
|
||||
fileType: '1' //文件类型(1:操作说明,2:产品参数)
|
||||
},
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getToken(),
|
||||
'clientid': clientid(),
|
||||
},
|
||||
complete: (res) => {
|
||||
console.log(res, 'resss');
|
||||
try {
|
||||
const responseData = JSON.parse(res.data);
|
||||
if (responseData.code === 200) {
|
||||
uni.showToast({
|
||||
title: responseData.msg,
|
||||
icon: 'success'
|
||||
});
|
||||
this.$refs.filePicker.clearFiles(); // 清空已选文件
|
||||
this.callOtherApi();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: responseData.msg,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取图片列表
|
||||
callOtherApi() {
|
||||
let data = {
|
||||
deviceId: this.deviceID,
|
||||
fileType: '1' //文件类型(1:操作说明,2:产品参数)
|
||||
}
|
||||
fileInfo(data).then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.instructionImages = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 切换删除模式
|
||||
toggleDeleteMode() {
|
||||
this.isDeleteMode = !this.isDeleteMode
|
||||
if (!this.isDeleteMode) {
|
||||
this.selectedImages = []
|
||||
}
|
||||
},
|
||||
// 图片点击处理
|
||||
handleImageClick(index, item) {
|
||||
if (this.isDeleteMode) {
|
||||
// 删除模式下处理多选
|
||||
const selectedIndex = this.selectedImages.indexOf(index)
|
||||
if (selectedIndex > -1) {
|
||||
this.selectedImages.splice(selectedIndex, 1)
|
||||
this.ImageData.splice(selectedIndex, 1) // 保持同步删除
|
||||
} else {
|
||||
this.selectedImages.push(
|
||||
index
|
||||
|
||||
)
|
||||
this.ImageData.push(item) // 保持同步添加
|
||||
}
|
||||
} else {
|
||||
// 正常模式下预览图片
|
||||
uni.previewImage({
|
||||
current: this.instructionImages[index].fileUrl,
|
||||
urls: this.instructionImages.map(item => item.fileUrl)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 删除选中的图片
|
||||
deleteSelectedImages() {
|
||||
if (this.selectedImages.length === 0) return
|
||||
this.deleteShow = true
|
||||
},
|
||||
async handleBtn() {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '删除中...',
|
||||
mask: true
|
||||
})
|
||||
// 构造删除请求数据
|
||||
const ids = this.ImageData.map(item => item.id).join(',')
|
||||
// 调用删除接口
|
||||
const res = await fileDelete(ids)
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 从后往前删除,避免索引变化
|
||||
this.selectedImages
|
||||
.sort((a, b) => b.index - a.index)
|
||||
.forEach(item => {
|
||||
this.instructionImages.splice(item.index, 1)
|
||||
})
|
||||
this.callOtherApi()
|
||||
// 重置状态
|
||||
this.selectedImages = []
|
||||
this.isDeleteMode = false
|
||||
this.deleteShow = false
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'none'
|
||||
})
|
||||
this.deleteShow = false
|
||||
}
|
||||
} catch (error) {} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.deviceID = options.id
|
||||
this.callOtherApi()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: rgb(18, 18, 18);
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.example-body {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
/* 图片列表 */
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: -10rpx;
|
||||
}
|
||||
|
||||
/* 图片项 */
|
||||
.image-item {
|
||||
width: 33.33%;
|
||||
padding: 10rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 图片样式 */
|
||||
.instruction-image {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
/* 选中效果 */
|
||||
.image-selected {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 选中标记 */
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
right: 40rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkmark image {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
/* 上传按钮 */
|
||||
.upload-btn {
|
||||
width: 33.33%;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
/* 底部删除按钮 */
|
||||
.delete-footer {
|
||||
position: fixed;
|
||||
bottom: 20rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.delete-footer button {
|
||||
width: 90%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: rgba(187, 230, 0, 1);
|
||||
background: rgba(187, 230, 0, 1);
|
||||
border-radius: 90px;
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.agreement-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* 弹窗主体 */
|
||||
.agreement-popup {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
background-color: rgb(42, 42, 42);
|
||||
border-radius: 60rpx 60rpx 0rpx 0rpx;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
}
|
||||
|
||||
.popup-Title {
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
text-align: center;
|
||||
padding: 30rpx 0rpx;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agreement-popupC {
|
||||
width: 60%;
|
||||
background-color: rgb(42, 42, 42);
|
||||
border-radius: 40rpx;
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 200, 78, 0.3);
|
||||
}
|
||||
|
||||
.popup-flex {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
height: 50rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.popup-input {
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 12rpx;
|
||||
margin-left: 15rpx;
|
||||
padding: 10rpx 0rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.svg {
|
||||
width: 58rpx;
|
||||
height: 62rpx;
|
||||
}
|
||||
|
||||
/* 通用按钮样式 */
|
||||
.btn {
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
margin: 10rpx auto;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
/* 同意按钮 */
|
||||
.agreeBtn {
|
||||
background: #FFC84E;
|
||||
color: #232323;
|
||||
border: none;
|
||||
width: 170rpx !important;
|
||||
}
|
||||
</style>
|
@ -1,21 +1,20 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 使用自定义导航栏 -->
|
||||
<custom-navbar :title="navTitle" :showBack="true" backgroundColor="#202020" color="#FFFFFF" right-text="编辑"
|
||||
right-color="rgba(255, 255, 255, 0.87)" @right-click="handleEdit"></custom-navbar>
|
||||
<custom-navbar title="操作视频" :showBack="true" backgroundColor="#202020" color="#FFFFFF"></custom-navbar>
|
||||
<view class="device-page" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<scroll-view scroll-y>
|
||||
<uni-swipe-action >
|
||||
<uni-swipe-action>
|
||||
<block v-for="(item, index) in videoList" :key="index">
|
||||
<uni-swipe-action-item :right-options="item.showConfirm ? confirmOptions : deleteOptions"
|
||||
@click="handleSwipeClick($event, index)" class="content">
|
||||
<view class="image-box" @click="openVideoUrl(item.url)">
|
||||
@click="handleSwipeClick($event, index,item)" class="content">
|
||||
<view class="image-box" @click="openVideoUrl(item.videoUrl)">
|
||||
<view class="deviceIMG">
|
||||
<image src="/static/images/video.png" mode="" class="video"></image>
|
||||
</view>
|
||||
<view class="">
|
||||
<view class="file-title">{{item.title}}</view>
|
||||
<view class="file-baidu">{{item.url}}</view>
|
||||
<view class="file-title">{{item.videoName}}</view>
|
||||
<view class="file-baidu">{{item.videoUrl}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<image src="/static/images/cires.png" class="circle"></image>
|
||||
@ -31,42 +30,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listOperationVideos,
|
||||
deleteOperationVideo
|
||||
} from '@/api/common/operationVideo/index.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
navTitle: '操作视频',
|
||||
|
||||
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
|
||||
videoList: [{
|
||||
id: 1,
|
||||
title: '使用教程1',
|
||||
url: 'https://www.bilibili.com/variety/?spm_id_from=333.1007.0.0',
|
||||
showConfirm: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '使用教程2',
|
||||
url: 'www.baidu.com',
|
||||
showConfirm: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '使用教程3',
|
||||
url: 'www.baidu.com',
|
||||
showConfirm: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '使用教程4',
|
||||
url: 'www.baidu.com',
|
||||
showConfirm: false
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '使用教程5',
|
||||
url: 'www.baidu.com',
|
||||
showConfirm: false
|
||||
}
|
||||
],
|
||||
videoList: [],
|
||||
deviceID: '',
|
||||
deleteOptions: [{
|
||||
text: '编辑',
|
||||
style: {
|
||||
@ -96,38 +70,49 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 导航栏编辑按钮
|
||||
handleEdit() {
|
||||
uni.showToast({
|
||||
title: '点击了导航栏编辑',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
// 添加视频
|
||||
addvideo() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/6170/addvideo/index'
|
||||
url: `/pages/common/addvideo/index??id=${this.deviceID}`
|
||||
})
|
||||
},
|
||||
|
||||
// 处理滑动按钮点击
|
||||
handleSwipeClick(e, index) {
|
||||
handleSwipeClick(e, index, item) {
|
||||
console.log(item, 'eeeee');
|
||||
const {
|
||||
content
|
||||
} = e;
|
||||
// 如果是确认删除操作
|
||||
if (content.text === '确认删除') {
|
||||
this.videoList.splice(index, 1);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
return;
|
||||
let ids = item.id
|
||||
let data ={
|
||||
ids:item.id
|
||||
}
|
||||
deleteOperationVideo(ids).then((res) => {
|
||||
if (res.code == 200) {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'success'
|
||||
});
|
||||
this.getData()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// return;
|
||||
}
|
||||
// 编辑操作
|
||||
if (content.text === '编辑') {
|
||||
this.editVideo(this.videoList[index].id);
|
||||
console.log('编辑');
|
||||
uni.navigateTo({
|
||||
url: `/pages/common/addvideo/index?item=${encodeURIComponent(JSON.stringify(item))}`
|
||||
});
|
||||
}
|
||||
// 删除操作
|
||||
else if (content.text === '删除') {
|
||||
@ -142,12 +127,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
// 编辑视频
|
||||
editVideo(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/6170/editVideo/index?id=${id}`
|
||||
});
|
||||
},
|
||||
// 打开视频URL
|
||||
openVideoUrl(url) {
|
||||
console.log(url, 'url333');
|
||||
@ -166,6 +145,29 @@
|
||||
});
|
||||
}
|
||||
},
|
||||
getData() {
|
||||
let data = {
|
||||
deviceId: this.deviceID
|
||||
}
|
||||
listOperationVideos(data).then((res) => {
|
||||
console.log(res, 'resss');
|
||||
if (res.code == 200) {
|
||||
this.videoList = res.data
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.getData()
|
||||
},
|
||||
onLoad(options) {
|
||||
this.deviceID = options.id
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
404
pages/common/productDes/index.vue
Normal file
@ -0,0 +1,404 @@
|
||||
<template>
|
||||
<!-- 使用自定义导航栏 -->
|
||||
<view>
|
||||
<custom-navbar title="产品说明" :showBack="true" backgroundColor="#202020" color="#FFFFFF"
|
||||
:right-text="isDeleteMode ? '完成' : '删除'" right-color="rgba(255, 255, 255, 0.87)"
|
||||
@right-click="toggleDeleteMode">
|
||||
</custom-navbar>
|
||||
|
||||
<view class="device-page" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<view class="example-body">
|
||||
<!-- 图片列表 -->
|
||||
<view class="image-list">
|
||||
<view class="image-item" v-for="(image, index) in instructionImages" :key="index"
|
||||
@click="handleImageClick(index,image)">
|
||||
<image :src="image.fileUrl" mode="aspectFit" class="instruction-image"
|
||||
:class="{ 'image-selected': isDeleteMode && selectedImages.includes(index) }"></image>
|
||||
|
||||
<!-- 多选模式下的选中标记 -->
|
||||
<view class="checkmark" v-if="isDeleteMode && selectedImages.includes(index)">
|
||||
<image src="/static/images/delete-icon.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮(非删除模式显示) -->
|
||||
<view class="upload-btn" v-if="!isDeleteMode">
|
||||
<uni-file-picker ref="filePicker" v-model="fileList" @select="selectFile" limit="9"
|
||||
class="custom-file-picker"></uni-file-picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部删除按钮(多选模式显示) -->
|
||||
<view class="delete-footer" v-if="isDeleteMode && selectedImages.length > 0">
|
||||
<button @click="deleteSelectedImages">删除({{selectedImages.length}})</button>
|
||||
</view>
|
||||
<!-- 删除弹框 -->
|
||||
<view class="agreement-mask" v-if="deleteShow">
|
||||
<view class="agreement-popupC">
|
||||
<view class="popup-content">
|
||||
<image src="/static/images/dell.png" mode="" class="svg"></image>
|
||||
<uni-icon class="trash"></uni-icon>
|
||||
<view>
|
||||
<view class="popup-Title">确定删除所选设备!</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 按钮组 -->
|
||||
<view class="popup-buttons">
|
||||
<button class="btn agreeBtn" @click="handleBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
baseURL,
|
||||
getToken,
|
||||
clientid
|
||||
} from '@/utils/request'
|
||||
import {
|
||||
fileInfo,
|
||||
fileDelete
|
||||
} from '@/api/common/operatingInstruct/index.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
|
||||
fileList: [],
|
||||
deviceID: "",
|
||||
instructionImages: [],
|
||||
isDeleteMode: false, // 是否删除模式
|
||||
selectedImages: [], // 选中的图片索引
|
||||
deleteShow: false,
|
||||
ImageData: [],
|
||||
filePicker: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 选择文件
|
||||
selectFile(e) {
|
||||
console.log('选择文件:', e)
|
||||
// const file = e.tempFiles[0].file
|
||||
const files = e.tempFiles.map(item => item.file)
|
||||
console.log('files', files);
|
||||
this.uploadFile(files)
|
||||
},
|
||||
// 上传图片
|
||||
uploadFile(files) {
|
||||
this.$refs.filePicker.clearFiles() // 通过ref操作uni-file-picker组件
|
||||
uni.showLoading({
|
||||
title: '上传中...'
|
||||
})
|
||||
files.forEach(file => {
|
||||
uni.uploadFile({
|
||||
url: baseURL + '/app/file/upload',
|
||||
filePath: file.path || file.url,
|
||||
name: 'files',
|
||||
formData: {
|
||||
deviceId: this.deviceID,
|
||||
fileType: '2' //文件类型(1:操作说明,2:产品参数)
|
||||
},
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getToken(),
|
||||
'clientid': clientid(),
|
||||
},
|
||||
complete: (res) => {
|
||||
console.log(res, 'resss');
|
||||
try {
|
||||
const responseData = JSON.parse(res.data);
|
||||
if (responseData.code === 200) {
|
||||
uni.showToast({
|
||||
title: responseData.msg,
|
||||
icon: 'success'
|
||||
});
|
||||
this.$refs.filePicker.clearFiles(); // 清空已选文件
|
||||
this.callOtherApi();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: responseData.msg,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取图片列表
|
||||
callOtherApi() {
|
||||
let data = {
|
||||
deviceId: this.deviceID,
|
||||
fileType: '2' //文件类型(1:操作说明,2:产品参数)
|
||||
}
|
||||
fileInfo(data).then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.instructionImages = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 切换删除模式
|
||||
toggleDeleteMode() {
|
||||
this.isDeleteMode = !this.isDeleteMode
|
||||
if (!this.isDeleteMode) {
|
||||
this.selectedImages = []
|
||||
}
|
||||
},
|
||||
// 图片点击处理
|
||||
handleImageClick(index, item) {
|
||||
if (this.isDeleteMode) {
|
||||
// 删除模式下处理多选
|
||||
const selectedIndex = this.selectedImages.indexOf(index)
|
||||
if (selectedIndex > -1) {
|
||||
this.selectedImages.splice(selectedIndex, 1)
|
||||
this.ImageData.splice(selectedIndex, 1) // 保持同步删除
|
||||
} else {
|
||||
this.selectedImages.push(
|
||||
index
|
||||
|
||||
)
|
||||
this.ImageData.push(item) // 保持同步添加
|
||||
}
|
||||
} else {
|
||||
// 正常模式下预览图片
|
||||
uni.previewImage({
|
||||
current: this.instructionImages[index].fileUrl,
|
||||
urls: this.instructionImages.map(item => item.fileUrl)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 删除选中的图片
|
||||
deleteSelectedImages() {
|
||||
if (this.selectedImages.length === 0) return
|
||||
this.deleteShow = true
|
||||
},
|
||||
async handleBtn() {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '删除中...',
|
||||
mask: true
|
||||
})
|
||||
// 构造删除请求数据
|
||||
const ids = this.ImageData.map(item => item.id).join(',')
|
||||
// 调用删除接口
|
||||
const res = await fileDelete(ids)
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 从后往前删除,避免索引变化
|
||||
this.selectedImages
|
||||
.sort((a, b) => b.index - a.index)
|
||||
.forEach(item => {
|
||||
this.instructionImages.splice(item.index, 1)
|
||||
})
|
||||
this.callOtherApi()
|
||||
// 重置状态
|
||||
this.selectedImages = []
|
||||
this.isDeleteMode = false
|
||||
this.deleteShow = false
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'none'
|
||||
})
|
||||
this.deleteShow = false
|
||||
}
|
||||
} catch (error) {} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.deviceID = options.id
|
||||
this.callOtherApi()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: rgb(18, 18, 18);
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.example-body {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
/* 图片列表 */
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: -10rpx;
|
||||
}
|
||||
|
||||
/* 图片项 */
|
||||
.image-item {
|
||||
width: 33.33%;
|
||||
padding: 10rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 图片样式 */
|
||||
.instruction-image {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
/* 选中效果 */
|
||||
.image-selected {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 选中标记 */
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
right: 40rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkmark image {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
/* 上传按钮 */
|
||||
.upload-btn {
|
||||
width: 33.33%;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
/* 底部删除按钮 */
|
||||
.delete-footer {
|
||||
position: fixed;
|
||||
bottom: 20rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.delete-footer button {
|
||||
width: 90%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: rgba(187, 230, 0, 1);
|
||||
background: rgba(187, 230, 0, 1);
|
||||
border-radius: 90px;
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.agreement-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* 弹窗主体 */
|
||||
.agreement-popup {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
background-color: rgb(42, 42, 42);
|
||||
border-radius: 60rpx 60rpx 0rpx 0rpx;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
}
|
||||
|
||||
.popup-Title {
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
text-align: center;
|
||||
padding: 30rpx 0rpx;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agreement-popupC {
|
||||
width: 60%;
|
||||
background-color: rgb(42, 42, 42);
|
||||
border-radius: 40rpx;
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 200, 78, 0.3);
|
||||
}
|
||||
|
||||
.popup-flex {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
height: 50rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.popup-input {
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 12rpx;
|
||||
margin-left: 15rpx;
|
||||
padding: 10rpx 0rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.svg {
|
||||
width: 58rpx;
|
||||
height: 62rpx;
|
||||
}
|
||||
|
||||
/* 通用按钮样式 */
|
||||
.btn {
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
margin: 10rpx auto;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
/* 同意按钮 */
|
||||
.agreeBtn {
|
||||
background: #FFC84E;
|
||||
color: #232323;
|
||||
border: none;
|
||||
width: 170rpx !important;
|
||||
}
|
||||
</style>
|
BIN
static/images/6155/DeviceDetail/add.png
Normal file
After Width: | Height: | Size: 145 B |
BIN
static/images/6155/DeviceDetail/battry.png
Normal file
After Width: | Height: | Size: 268 B |
BIN
static/images/6155/DeviceDetail/equip.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
static/images/6155/DeviceDetail/fan.png
Normal file
After Width: | Height: | Size: 275 B |
BIN
static/images/6155/DeviceDetail/fuLamp.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
static/images/6155/DeviceDetail/mainLamp.png
Normal file
After Width: | Height: | Size: 413 B |
BIN
static/images/6155/DeviceDetail/open.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
static/images/6155/DeviceDetail/param.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
static/images/6155/DeviceDetail/qiang.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
static/images/6155/DeviceDetail/remark.png
Normal file
After Width: | Height: | Size: 269 B |
BIN
static/images/6155/DeviceDetail/ruo.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
static/images/6155/DeviceDetail/sendSucc.png
Normal file
After Width: | Height: | Size: 500 B |
BIN
static/images/6155/DeviceDetail/shan.png
Normal file
After Width: | Height: | Size: 304 B |
BIN
static/images/6155/DeviceDetail/slideToggle.png
Normal file
After Width: | Height: | Size: 194 B |
BIN
static/images/6155/DeviceDetail/time.png
Normal file
After Width: | Height: | Size: 321 B |
BIN
static/images/6155/DeviceDetail/uploadSuccess.png
Normal file
After Width: | Height: | Size: 282 B |
BIN
static/images/6155/DeviceDetail/video.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
static/images/BLEAdd/device.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
static/images/BLEAdd/linked.png
Normal file
After Width: | Height: | Size: 793 B |
BIN
static/images/BLEAdd/noLink.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
static/images/BLEAdd/wifi.png
Normal file
After Width: | Height: | Size: 522 B |
BIN
static/images/delete-icon.png
Normal file
After Width: | Height: | Size: 605 B |
@ -20,7 +20,7 @@ let connected = false
|
||||
//测试
|
||||
|
||||
let service_uuid = "0000AE30-0000-1000-8000-00805F9B34FB"
|
||||
let write_characteristic = "0000AE03-0000-1000-8000-00805F9B34FB"
|
||||
let write_characteristic = "0000AE03-0000-1000-8000-00805F9B34FB"
|
||||
let notify_characteristic = "0000AE03-0000-1000-8000-00805F9B34FB"
|
||||
let notify_characteristic_2 = "0000AE02-0000-1000-8000-00805F9B34FB"//获取设备电量和设备mac地址
|
||||
|
||||
@ -123,15 +123,18 @@ String.prototype.transFloat = function() {
|
||||
return str.indexOf('.') != -1 ? parseFloat(str).toFixed(1) : str
|
||||
}
|
||||
|
||||
// //uni
|
||||
uni.onBluetoothAdapterStateChange(function(res) {
|
||||
if (!res.available) {
|
||||
isOpen = false
|
||||
isBleOn = false
|
||||
} else {
|
||||
isBleOn = true
|
||||
}
|
||||
})
|
||||
//
|
||||
|
||||
if (uni.getSystemInfoSync().platform){
|
||||
uni.onBluetoothAdapterStateChange(function(res) {
|
||||
if (!res.available) {
|
||||
isOpen = false
|
||||
isBleOn = false
|
||||
} else {
|
||||
isBleOn = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
67
uni_modules/qf-image-cropper/changelog.md
Normal file
@ -0,0 +1,67 @@
|
||||
## 2.2.4(2024-06-21)
|
||||
* 新增 reverseRotatable 属性,是否支持逆向翻转
|
||||
* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题
|
||||
## 2.2.3(2024-06-21)
|
||||
* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题
|
||||
* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题
|
||||
|
||||
## 2.2.2(2024-06-21)
|
||||
* 优化 组件实例 chooseImage 方法支持传参
|
||||
* 修复 组件使用 `v-if` 时组件无非正常渲染的问题
|
||||
|
||||
## 2.2.1(2024-06-15)
|
||||
* 修复 H5平台不支持手势拖动图片的问题
|
||||
|
||||
## 2.2.0(2024-05-31)
|
||||
* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题
|
||||
|
||||
## 2.1.9(2024-05-29)
|
||||
* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译,导致运行了H5平台代码报错的问题
|
||||
|
||||
## 2.1.8(2024-05-29)
|
||||
* 新增 zIndex 属性,调整组件层级
|
||||
* 新增 组件内容插槽
|
||||
* 优化 微信小程序平台动态修改元素style时的多余内容
|
||||
|
||||
## 2.1.7(2024-05-28)
|
||||
* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界
|
||||
* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制
|
||||
* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块
|
||||
* 优化 动态修改图片宽高但没有传入src时,尺寸适应问题
|
||||
* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题
|
||||
|
||||
## 2.1.6(2023-04-16)
|
||||
* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题
|
||||
|
||||
## 2.1.5(2023-04-15)
|
||||
* 新增 兼容APP平台
|
||||
|
||||
## 2.1.4(2023-03-13)
|
||||
* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png'
|
||||
* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间
|
||||
* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制(1365*1365)则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸
|
||||
* 优化 旋转图标指示方向与实际旋转方向不符
|
||||
|
||||
## 2.1.3(2023-02-06)
|
||||
* 优化 vue3支持
|
||||
|
||||
## 2.1.2(2023-02-03)
|
||||
* 新增 navigation 属性,H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差
|
||||
* 修复 H5平台部分设备(已知iPhone11以下机型)拍照的图片缩放时会闪动的问题
|
||||
|
||||
## 2.1.1(2022-12-06)
|
||||
* 修复 横屏适配问题
|
||||
|
||||
## 2.1.0(2022-12-06)
|
||||
* 新增 兼容H5平台,使用 renderjs 响应手势事件
|
||||
|
||||
## 2.0.0(2022-12-05)
|
||||
* 重构 插件,使用 WXS 响应手势事件
|
||||
* 新增 图片翻转
|
||||
* 新增 拉伸裁剪框放大图片
|
||||
* 新增 监听PC鼠标滚轮触发缩放
|
||||
* 新增 圆形、圆角矩形的图片裁剪
|
||||
* 优化 图片缩放,移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||
* 优化 裁剪框样式
|
||||
* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||
* 优化 生成图片使用新版 Canvas 2D 接口
|
@ -0,0 +1,738 @@
|
||||
/**
|
||||
* 图片编辑器-手势监听
|
||||
* 1. 支持编译到app-vue(uni-app 2.5.5及以上版本)、H5上
|
||||
*/
|
||||
/** 图片偏移量 */
|
||||
var offset = { x: 0, y: 0 };
|
||||
/** 图片缩放比例 */
|
||||
var scale = 1;
|
||||
/** 图片最小缩放比例 */
|
||||
var minScale = 1;
|
||||
/** 图片旋转角度 */
|
||||
var rotate = 0;
|
||||
/** 触摸点 */
|
||||
var touches = [];
|
||||
/** 图片布局信息 */
|
||||
var img = {};
|
||||
/** 系统信息 */
|
||||
var sys = {};
|
||||
/** 裁剪区域布局信息 */
|
||||
var area = {};
|
||||
/** 触摸行为类型 */
|
||||
var touchType = '';
|
||||
/** 操作角的位置 */
|
||||
var activeAngle = 0;
|
||||
/** 裁剪区域布局信息偏移量 */
|
||||
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
/** 元素ID */
|
||||
var elIds = {
|
||||
'imageStyles': 'crop-image',
|
||||
'maskStylesList': 'crop-mask-block',
|
||||
'borderStyles': 'crop-border',
|
||||
'circleBoxStyles': 'crop-circle-box',
|
||||
'circleStyles': 'crop-circle',
|
||||
'gridStylesList': 'crop-grid',
|
||||
'angleStylesList': 'crop-angle',
|
||||
}
|
||||
/** 记录上次初始化时间戳,排除APP重复更新 */
|
||||
var timestamp = 0;
|
||||
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
|
||||
// #ifdef H5
|
||||
var platform = 'H5';
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
var platform = 'APP';
|
||||
// #endif
|
||||
/**
|
||||
* 样式对象转字符串
|
||||
* @param {Object} style 样式对象
|
||||
*/
|
||||
function styleToString(style) {
|
||||
if(typeof style === 'string') return style;
|
||||
var str = '';
|
||||
for (let k in style) {
|
||||
str += k + ':' + style[k] + ';';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Object} instance 页面实例对象
|
||||
* @param {Object} key 要修改样式的key
|
||||
* @param {Object|Array} style 样式
|
||||
*/
|
||||
function setStyle(instance, key, style) {
|
||||
// console.log('setStyle', instance, key, JSON.stringify(style))
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP') {
|
||||
if(Object.prototype.toString.call(style) === '[object Array]') {
|
||||
for (var i = 0, len = style.length; i < len; i++) {
|
||||
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
|
||||
el && (el.style = styleToString(style[i]));
|
||||
}
|
||||
} else {
|
||||
var el = window.document.getElementById(elIds[key]);
|
||||
el && (el.style = styleToString(style));
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5') instance[key] = style;
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 触发页面实例指定方法
|
||||
* @param {Object} instance 页面实例对象
|
||||
* @param {Object} name 方法名称
|
||||
* @param {Object} obj 传递参数
|
||||
*/
|
||||
function callMethod(instance, name, obj) {
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP') instance.callMethod(name, obj);
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5') instance[name](obj);
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 计算两点间距
|
||||
* @param {Object} touches 触摸点信息
|
||||
*/
|
||||
function getDistanceByTouches(touches) {
|
||||
// 根据勾股定理求两点间距离
|
||||
var a = touches[1].pageX - touches[0].pageX;
|
||||
var b = touches[1].pageY - touches[0].pageY;
|
||||
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||
// 求两点间的中点坐标
|
||||
// 1. a、b可能为负值
|
||||
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||
var x = touches[1].pageX - a / 2;
|
||||
var y = touches[1].pageY - b / 2;
|
||||
return { c, x, y };
|
||||
};
|
||||
|
||||
/**
|
||||
* 修正取值
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @param {Object} c
|
||||
* @param {Object} reverse 是否反向
|
||||
*/
|
||||
function correctValue(a, b, c, reverse) {
|
||||
return reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
*/
|
||||
function checkRange(e) {
|
||||
var r = rotate / 90 % 2;
|
||||
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||
var o = (img.height - img.width) / 2; // 宽高差值一半
|
||||
return {
|
||||
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, img.height < area.height),
|
||||
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, img.width < area.width)
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 变更图片布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeImageRect(e) {
|
||||
// console.log('changeImageRect', e)
|
||||
offset.x += e.x || 0;
|
||||
offset.y += e.y || 0;
|
||||
if(e.check && area.checkRange) { // 检查边界
|
||||
var point = checkRange(offset);
|
||||
if(offset.x !== point.x || offset.y !== point.y) {
|
||||
offset = point;
|
||||
}
|
||||
}
|
||||
|
||||
// 因频繁修改 width/height 会造成大量的内存消耗,改为scale
|
||||
// e.instance.imageStyles = {
|
||||
// width: img.width + 'px',
|
||||
// height: img.height + 'px',
|
||||
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
|
||||
// };
|
||||
var ox = (img.width - img.oldWidth) / 2;
|
||||
var oy = (img.height - img.oldHeight) / 2;
|
||||
// e.instance.imageStyles = {
|
||||
// width: img.oldWidth + 'px',
|
||||
// height: img.oldHeight + 'px',
|
||||
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
// };
|
||||
setStyle(e.instance, 'imageStyles', {
|
||||
width: img.oldWidth + 'px',
|
||||
height: img.oldHeight + 'px',
|
||||
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
});
|
||||
callMethod(e.instance, 'dataChange', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
rotate: rotate
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 变更裁剪区域布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeAreaRect(e) {
|
||||
// console.log('changeAreaRect', e)
|
||||
// 变更蒙版样式
|
||||
setStyle(e.instance, 'maskStylesList', [
|
||||
{
|
||||
left: 0,
|
||||
width: (area.left + areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.right + areaOffset.right) + 'px',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
height: (area.top + areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
}
|
||||
]);
|
||||
// 变更边框样式
|
||||
if(area.showBorder) {
|
||||
setStyle(e.instance, 'borderStyles', {
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
});
|
||||
}
|
||||
|
||||
// 变更参考线样式
|
||||
if(area.showGrid) {
|
||||
setStyle(e.instance, 'gridStylesList', [
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// 变更四个伸缩角样式
|
||||
if(area.showAngle) {
|
||||
setStyle(e.instance, 'angleStylesList', [
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// 变更圆角样式
|
||||
if(area.radius > 0) {
|
||||
var radius = area.radius;
|
||||
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||
radius = (area.width / 2);
|
||||
} else { // 圆角矩形
|
||||
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||
}
|
||||
}
|
||||
setStyle(e.instance, 'circleBoxStyles', {
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
});
|
||||
setStyle(e.instance, 'circleStyles', {
|
||||
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||
'border-radius': radius + 'px'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 缩放图片
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function scaleImage(e) {
|
||||
// console.log('scaleImage', e)
|
||||
var last = scale;
|
||||
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||
if(last !== scale) {
|
||||
img.width = img.oldWidth * scale;
|
||||
img.height = img.oldHeight * scale;
|
||||
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||
e.x = (e.x - offset.x) * (1 - scale / last);
|
||||
e.y = (e.y - offset.y) * (1 - scale / last);
|
||||
changeImageRect(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 获取触摸点在哪个角
|
||||
* @param {number} x 触摸点x轴坐标
|
||||
* @param {number} y 触摸点y轴坐标
|
||||
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||
*/
|
||||
function getToucheAngle(x, y) {
|
||||
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||
var oy = sys.navigation ? 0 : sys.windowTop;
|
||||
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 1; // 左上角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 2; // 右上角
|
||||
}
|
||||
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 3; // 左下角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 4; // 右下角
|
||||
}
|
||||
}
|
||||
return 0; // 无触摸到角
|
||||
};
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
function resetData() {
|
||||
offset = { x: 0, y: 0 };
|
||||
scale = 1;
|
||||
minScale = img.minScale;
|
||||
rotate = 0;
|
||||
};
|
||||
function getTouchs(touches) {
|
||||
var result = [];
|
||||
var len = touches ? touches.length : 0
|
||||
for (var i = 0; i < len; i++) {
|
||||
result[i] = {
|
||||
pageX: touches[i].pageX,
|
||||
// h5无标题栏时,窗口顶部距离仍为标题栏高度,且触摸点y轴坐标还是有标题栏的值,即减去标题栏高度的值
|
||||
pageY: touches[i].pageY + sys.windowTop
|
||||
};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var mouseEvent = false;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imageStyles: {},
|
||||
maskStylesList: [{}, {}, {}, {}],
|
||||
borderStyles: {},
|
||||
gridStylesList: [{}, {}, {}, {}],
|
||||
angleStylesList: [{}, {}, {}, {}],
|
||||
circleBoxStyles: {},
|
||||
circleStyles: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听 PC 端鼠标滚轮
|
||||
// #ifdef H5
|
||||
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
|
||||
var touchs = getTouchs([e])
|
||||
img.src && scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
check: true,
|
||||
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||
scale: e.deltaY > 0 ? -0.05 : 0.05,
|
||||
x: touchs[0].pageX,
|
||||
y: touchs[0].pageY
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
// #ifdef H5
|
||||
mounted() {
|
||||
platform === 'H5' && this.initH5Events();
|
||||
},
|
||||
// #endif
|
||||
setPlatform(p) {
|
||||
platform = p;
|
||||
},
|
||||
methods: {
|
||||
// #ifdef H5
|
||||
getTouchEvent(e) {
|
||||
e.touches = [
|
||||
{ pageX: e.pageX, pageY: e.pageY }
|
||||
];
|
||||
return e;
|
||||
},
|
||||
initH5Events() {
|
||||
const preview = document.getElementById('pic-preview');
|
||||
preview?.addEventListener('mousedown', (e, ev) => {
|
||||
mouseEvent = true;
|
||||
this.touchstart(this.getTouchEvent(e));
|
||||
});
|
||||
preview?.addEventListener('mousemove', (e) => {
|
||||
if (!mouseEvent) return;
|
||||
this.touchmove(this.getTouchEvent(e));
|
||||
});
|
||||
preview?.addEventListener('mouseup', (e) => {
|
||||
mouseEvent = false;
|
||||
this.touchend(this.getTouchEvent(e))
|
||||
});
|
||||
preview?.addEventListener('mouseleave', (e) => {
|
||||
mouseEvent = false;
|
||||
this.touchend(this.getTouchEvent(e))
|
||||
});
|
||||
},
|
||||
// #endif
|
||||
async getInstance() {
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP')
|
||||
return this.$ownerInstance
|
||||
? Promise.resolve(this.$ownerInstance)
|
||||
: new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
resolve(await this.getInstance());
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5')
|
||||
return Promise.resolve(this);
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* 初始化:观察数据变更
|
||||
* @param {Object} newVal 新数据
|
||||
* @param {Object} oldVal 旧数据
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
initObserver: async function(newVal, oldVal, o, i) {
|
||||
// console.log('initObserver', newVal, oldVal, o, i)
|
||||
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
|
||||
timestamp = newVal.timestamp;
|
||||
img = newVal.img;
|
||||
sys = newVal.sys;
|
||||
area = newVal.area;
|
||||
minScale = img.minScale;
|
||||
resetData();
|
||||
const instance = await this.getInstance()
|
||||
img.src && changeImageRect({
|
||||
instance,
|
||||
x: (sys.windowWidth - img.width) / 2,
|
||||
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
|
||||
});
|
||||
changeAreaRect({
|
||||
instance
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 鼠标滚轮滚动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
mousewheel: function(e, o) {
|
||||
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
|
||||
},
|
||||
/**
|
||||
* 触摸开始
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchstart: function(e, o) {
|
||||
if(!img.src) return;
|
||||
touches = getTouchs(e.touches);
|
||||
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||
if(touches.length === 1 && activeAngle !== 0) {
|
||||
touchType = 'stretch'; // 伸缩裁剪区域
|
||||
} else {
|
||||
touchType = '';
|
||||
}
|
||||
// console.log('touchstart', e, activeAngle)
|
||||
},
|
||||
/**
|
||||
* 触摸移动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchmove: async function(e, o) {
|
||||
if(!img.src) return;
|
||||
// console.log('touchmove', e, o)
|
||||
e.touches = getTouchs(e.touches);
|
||||
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||
var point = e.touches[0];
|
||||
var start = touches[0];
|
||||
var x = point.pageX - start.pageX;
|
||||
var y = point.pageY - start.pageY;
|
||||
if(x !== 0 || y !== 0) {
|
||||
var maxX = area.width * (1 - area.minScale);
|
||||
var maxY = area.height * (1 - area.minScale);
|
||||
// console.log(x, y, maxX, maxY, offset, area)
|
||||
touches[0] = point;
|
||||
switch(activeAngle) {
|
||||
case 1: // 左上角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.top;
|
||||
// console.log(x, y, offset.left > area.left)
|
||||
// console.log(maxX, maxY)
|
||||
if(x >= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, offset.x - area.left)
|
||||
: false;
|
||||
if(x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 2: // 右上角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.top;
|
||||
if(x <= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 3: // 左下角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.bottom;
|
||||
if(x >= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, offset.x - area.left)
|
||||
: false;
|
||||
if(x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
case 4: // 右下角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.bottom;
|
||||
if(x <= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log(x, y, JSON.stringify(areaOffset))
|
||||
changeAreaRect({
|
||||
instance: await this.getInstance(),
|
||||
});
|
||||
// this.draw();
|
||||
}
|
||||
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||
var start = getDistanceByTouches(touches);
|
||||
var end = getDistanceByTouches(e.touches);
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
check: !area.bounce,
|
||||
scale: (end.c - start.c) / 100,
|
||||
x: end.x,
|
||||
y: end.y
|
||||
});
|
||||
touchType = 'scale';
|
||||
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||
touchType = 'move';
|
||||
} else {
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: !area.bounce,
|
||||
x: e.touches[0].pageX - touches[0].pageX,
|
||||
y: e.touches[0].pageY - touches[0].pageY
|
||||
});
|
||||
touchType = 'move';
|
||||
}
|
||||
touches = e.touches;
|
||||
},
|
||||
/**
|
||||
* 触摸结束
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchend: async function(e, o) {
|
||||
if(!img.src) return;
|
||||
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||
// 裁剪区域宽度被缩放到多少
|
||||
var left = areaOffset.left;
|
||||
var right = areaOffset.right;
|
||||
var top = areaOffset.top;
|
||||
var bottom = areaOffset.bottom;
|
||||
var w = area.width + right - left;
|
||||
var h = area.height + bottom - top;
|
||||
// 图像放大倍数
|
||||
var p = scale * (area.width / w) - scale;
|
||||
// 复原裁剪区域
|
||||
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
changeAreaRect({
|
||||
instance: await this.getInstance(),
|
||||
});
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
scale: p,
|
||||
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||
});
|
||||
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: true
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
rotateImage: async function(r) {
|
||||
rotate = (rotate + (r || 90)) % 360;
|
||||
|
||||
if(img.minScale >= 1) {
|
||||
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||
minScale = 1;
|
||||
if(img.width < area.height) {
|
||||
minScale = area.height / img.oldWidth;
|
||||
} else if(img.height < area.width) {
|
||||
minScale = (area.width / img.oldHeight)
|
||||
}
|
||||
if(minScale !== 1) {
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
scale: minScale - scale,
|
||||
x: sys.windowWidth / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: true,
|
||||
x: -ox - oy,
|
||||
y: -oy + ox
|
||||
});
|
||||
},
|
||||
rotateImage90: function() {
|
||||
this.rotateImage(90)
|
||||
},
|
||||
rotateImage270: function() {
|
||||
this.rotateImage(270)
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,746 @@
|
||||
<template>
|
||||
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
|
||||
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
|
||||
width: `${canvansWidth}px`,
|
||||
height: `${canvansHeight}px`
|
||||
}"></canvas>
|
||||
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
|
||||
width: `${canvansWidth}px`,
|
||||
height: `${canvansHeight}px`
|
||||
}"></canvas>
|
||||
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
|
||||
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image>
|
||||
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view>
|
||||
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
|
||||
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
|
||||
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
|
||||
</view>
|
||||
<block v-if="showGrid">
|
||||
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view>
|
||||
</block>
|
||||
<block v-if="showAngle">
|
||||
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]">
|
||||
<view :style="[{
|
||||
width: `${angleSize}px`,
|
||||
height: `${angleSize}px`
|
||||
}]"></view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<slot />
|
||||
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
|
||||
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
|
||||
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
|
||||
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
|
||||
</view>
|
||||
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
|
||||
<block v-else-if="!!imgSrc">
|
||||
<view class="rechoose" @click="chooseImage">重选</view>
|
||||
<button class="button" size="mini" @click="cropClick">确定</button>
|
||||
</block>
|
||||
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- #ifdef APP-VUE -->
|
||||
<script module="cropper" lang="renderjs">
|
||||
import cropper from './qf-image-cropper.render.js';
|
||||
// vue3 app renderjs中条件编译无效
|
||||
cropper.setPlatform('APP');
|
||||
export default {
|
||||
mixins: [ cropper ]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<script module="cropper" lang="renderjs">
|
||||
import cropper from './qf-image-cropper.render.js';
|
||||
export default {
|
||||
mixins: [ cropper ]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ -->
|
||||
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
|
||||
<!-- #endif -->
|
||||
<script>
|
||||
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
|
||||
const AREA_SIZE = 75;
|
||||
/** 图片默认宽高 */
|
||||
const IMG_SIZE = 300;
|
||||
|
||||
export default {
|
||||
name:"qf-image-cropper",
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
|
||||
styleIsolation: "isolated"
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
/** 图片资源地址 */
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 裁剪宽度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||
width: {
|
||||
type: Number,
|
||||
default: IMG_SIZE
|
||||
},
|
||||
/** 裁剪高度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: IMG_SIZE
|
||||
},
|
||||
/** 是否绘制裁剪区域边框 */
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否绘制裁剪区域网格参考线 */
|
||||
showGrid: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否展示四个支持伸缩的角 */
|
||||
showAngle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 裁剪区域最小缩放倍数 */
|
||||
areaScale: {
|
||||
type: Number,
|
||||
default: 0.3
|
||||
},
|
||||
/** 图片最小缩放倍数 */
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/** 图片最大缩放倍数 */
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
|
||||
checkRange: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
|
||||
backgroundColor: {
|
||||
type: String
|
||||
},
|
||||
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
|
||||
bounce: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否支持翻转 */
|
||||
rotatable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否支持逆向翻转 */
|
||||
reverseRotatable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 是否支持从本地选择素材 */
|
||||
choosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
|
||||
gpu: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 四个角尺寸,单位px */
|
||||
angleSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
/** 四个角边框宽度,单位px */
|
||||
angleBorderWidth: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String]
|
||||
},
|
||||
/** 裁剪图片圆角半径,单位px */
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'png'
|
||||
},
|
||||
/**
|
||||
* 图片从绘制到生成所需时间,单位ms
|
||||
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
|
||||
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间
|
||||
*/
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
// #ifdef H5
|
||||
/**
|
||||
* 页面是否是原生标题栏
|
||||
* H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
|
||||
* 注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的
|
||||
*/
|
||||
navigation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
emits: ["crop"],
|
||||
data() {
|
||||
return {
|
||||
// 用不同 id 使 v-for key 不重复
|
||||
maskList: [
|
||||
{ id: 'crop-mask-block-1' },
|
||||
{ id: 'crop-mask-block-2' },
|
||||
{ id: 'crop-mask-block-3' },
|
||||
{ id: 'crop-mask-block-4' },
|
||||
],
|
||||
gridList: [
|
||||
{ id: 'crop-grid-1' },
|
||||
{ id: 'crop-grid-2' },
|
||||
{ id: 'crop-grid-3' },
|
||||
{ id: 'crop-grid-4' },
|
||||
],
|
||||
angleList: [
|
||||
{ id: 'crop-angle-1' },
|
||||
{ id: 'crop-angle-2' },
|
||||
{ id: 'crop-angle-3' },
|
||||
{ id: 'crop-angle-4' },
|
||||
],
|
||||
/** 本地缓存的图片路径 */
|
||||
imgSrc: '',
|
||||
/** 图片的裁剪宽度 */
|
||||
imgWidth: IMG_SIZE,
|
||||
/** 图片的裁剪高度 */
|
||||
imgHeight: IMG_SIZE,
|
||||
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
|
||||
widthPercent: AREA_SIZE,
|
||||
/** 裁剪区域最大高度所占屏幕宽度百分比 */
|
||||
heightPercent: AREA_SIZE,
|
||||
/** 裁剪区域布局信息 */
|
||||
area: {},
|
||||
/** 未被缩放过的图片宽 */
|
||||
oldWidth: 0,
|
||||
/** 未被缩放过的图片高 */
|
||||
oldHeight: 0,
|
||||
/** 系统信息 */
|
||||
sys: uni.getSystemInfoSync(),
|
||||
scaleWidth: 0,
|
||||
scaleHeight: 0,
|
||||
rotate: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
use2d: false,
|
||||
canvansWidth: 0,
|
||||
canvansHeight: 0,
|
||||
// imageStyles: {},
|
||||
// maskStylesList: [{}, {}, {}, {}],
|
||||
// borderStyles: {},
|
||||
// gridStylesList: [{}, {}, {}, {}],
|
||||
// angleStylesList: [{}, {}, {}, {}],
|
||||
// circleBoxStyles: {},
|
||||
// circleStyles: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
initData() {
|
||||
// console.log('initData')
|
||||
return {
|
||||
timestamp: new Date().getTime(),
|
||||
area: {
|
||||
...this.area,
|
||||
bounce: this.bounce,
|
||||
showBorder: this.showBorder,
|
||||
showGrid: this.showGrid,
|
||||
showAngle: this.showAngle,
|
||||
angleSize: this.angleSize,
|
||||
angleBorderWidth: this.angleBorderWidth,
|
||||
minScale: this.areaScale,
|
||||
widthPercent: this.widthPercent,
|
||||
heightPercent: this.heightPercent,
|
||||
radius: this.radius,
|
||||
checkRange: this.checkRange,
|
||||
zIndex: +this.zIndex || 0,
|
||||
},
|
||||
sys: this.sys,
|
||||
img: {
|
||||
minScale: this.minScale,
|
||||
maxScale: this.maxScale,
|
||||
src: this.imgSrc,
|
||||
width: this.oldWidth,
|
||||
height: this.oldHeight,
|
||||
oldWidth: this.oldWidth,
|
||||
oldHeight: this.oldHeight,
|
||||
gpu: this.gpu,
|
||||
}
|
||||
}
|
||||
},
|
||||
imgProps() {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
src: this.src,
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imgProps: {
|
||||
handler(val, oldVal) {
|
||||
// 自定义裁剪尺,示例如下:
|
||||
this.imgWidth = Number(val.width) || IMG_SIZE;
|
||||
this.imgHeight = Number(val.height) || IMG_SIZE;
|
||||
let use2d = true;
|
||||
// #ifndef MP-WEIXIN
|
||||
use2d = false;
|
||||
// #endif
|
||||
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
|
||||
// use2d = false;
|
||||
// }
|
||||
let canvansWidth = this.imgWidth;
|
||||
let canvansHeight = this.imgHeight;
|
||||
let size = Math.max(canvansWidth, canvansHeight)
|
||||
let scalc = 1;
|
||||
if(size > 1365) {
|
||||
scalc = 1365 / size;
|
||||
}
|
||||
this.canvansWidth = canvansWidth * scalc;
|
||||
this.canvansHeight = canvansHeight * scalc;
|
||||
this.use2d = use2d;
|
||||
this.initArea();
|
||||
const src = val.src || this.imgSrc;
|
||||
src && this.initImage(src, oldVal === undefined);
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 提供给wxs调用,用来接收图片变更数据 */
|
||||
dataChange(e) {
|
||||
// console.log('dataChange', e)
|
||||
this.scaleWidth = e.width;
|
||||
this.scaleHeight = e.height;
|
||||
this.rotate = e.rotate;
|
||||
this.offsetX = e.x;
|
||||
this.offsetY = e.y;
|
||||
},
|
||||
/** 初始化裁剪区域布局信息 */
|
||||
initArea() {
|
||||
// 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度
|
||||
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
|
||||
// #ifndef H5
|
||||
this.sys.windowTop = 0;
|
||||
this.sys.navigation = true;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
// h5平台的窗口高度是包含标题栏的
|
||||
this.sys.windowTop = this.sys.windowTop || 44;
|
||||
this.sys.navigation = this.navigation;
|
||||
// #endif
|
||||
let wp = this.widthPercent;
|
||||
let hp = this.heightPercent;
|
||||
if (this.imgWidth > this.imgHeight) {
|
||||
hp = hp * this.imgHeight / this.imgWidth;
|
||||
} else if (this.imgWidth < this.imgHeight) {
|
||||
wp = wp * this.imgWidth / this.imgHeight;
|
||||
}
|
||||
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
|
||||
const width = size * wp / 100;
|
||||
const height = size * hp / 100;
|
||||
const left = (this.sys.windowWidth - width) / 2;
|
||||
const right = left + width;
|
||||
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
|
||||
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
|
||||
this.area = { width, height, left, right, top, bottom };
|
||||
this.scaleWidth = width;
|
||||
this.scaleHeight = height;
|
||||
},
|
||||
/** 从本地选取图片 */
|
||||
chooseImage(options) {
|
||||
// #ifdef MP-WEIXIN || MP-JD
|
||||
if(uni.chooseMedia) {
|
||||
uni.chooseMedia({
|
||||
...options,
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
success: (res) => {
|
||||
this.resetData();
|
||||
this.initImage(res.tempFiles[0].tempFilePath);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
uni.chooseImage({
|
||||
...options,
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
this.resetData();
|
||||
this.initImage(res.tempFiles[0].path);
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 重置数据 */
|
||||
resetData() {
|
||||
this.imgSrc = '';
|
||||
this.rotate = 0;
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
this.initArea();
|
||||
},
|
||||
/**
|
||||
* 初始化图片信息
|
||||
* @param {String} url 图片链接
|
||||
*/
|
||||
initImage(url, isFirst) {
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: async (res) => {
|
||||
if (isFirst && this.src === url) await (new Promise((resolve) => setTimeout(resolve, 50)));
|
||||
this.imgSrc = res.path;
|
||||
let scale = res.width / res.height;
|
||||
let areaScale = this.area.width / this.area.height;
|
||||
if (scale > 1) { // 横向图片
|
||||
if (scale >= areaScale) { // 图片宽不小于目标宽,则高固定,宽自适应
|
||||
this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth);
|
||||
} else { // 否则宽固定、高自适应
|
||||
this.scaleHeight = res.height * this.scaleWidth / res.width;
|
||||
}
|
||||
} else { // 纵向图片
|
||||
if (scale <= areaScale) { // 图片高不小于目标高,宽固定,高自适应
|
||||
this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height);
|
||||
} else { // 否则高固定,宽自适应
|
||||
this.scaleWidth = res.width * this.scaleHeight / res.height;
|
||||
}
|
||||
}
|
||||
// 记录原始宽高,为缩放比列做限制
|
||||
this.oldWidth = this.scaleWidth;
|
||||
this.oldHeight = this.scaleHeight;
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(err)
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 剪切图片圆角
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {Number} radius 圆角半径
|
||||
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||
* @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切
|
||||
*/
|
||||
drawClipImage(ctx, radius, scale, drawImage) {
|
||||
if(radius > 0) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
const w = this.canvansWidth;
|
||||
const h = this.canvansHeight;
|
||||
if(w === h && radius >= w / 2) { // 圆形
|
||||
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
|
||||
} else { // 圆角矩形
|
||||
if(w !== h) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(w / 2, h / 2, radius);
|
||||
// radius = Math.min(Math.max(w, h) / 2, radius);
|
||||
}
|
||||
ctx.moveTo(radius, 0);
|
||||
ctx.arcTo(w, 0, w, h, radius);
|
||||
ctx.arcTo(w, h, 0, h, radius);
|
||||
ctx.arcTo(0, h, 0, 0, radius);
|
||||
ctx.arcTo(0, 0, w, 0, radius);
|
||||
ctx.closePath();
|
||||
}
|
||||
ctx.clip();
|
||||
drawImage && drawImage(true);
|
||||
ctx.restore();
|
||||
} else {
|
||||
drawImage && drawImage(false);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 旋转图片
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {Number} rotate 旋转角度
|
||||
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||
*/
|
||||
drawRotateImage(ctx, rotate, scale) {
|
||||
if(rotate !== 0) {
|
||||
// 1. 以图片中心点为旋转中心点
|
||||
const x = this.scaleWidth * scale / 2;
|
||||
const y = this.scaleHeight * scale / 2;
|
||||
ctx.translate(x, y);
|
||||
// 2. 旋转画布
|
||||
ctx.rotate(rotate * Math.PI / 180);
|
||||
// 3. 旋转完画布后恢复设置旋转中心时所做的偏移
|
||||
ctx.translate(-x, -y);
|
||||
}
|
||||
},
|
||||
drawImage(ctx, image, callback) {
|
||||
// 生成图片的实际尺寸与截取区域比
|
||||
const scale = this.canvansWidth / this.area.width;
|
||||
if(this.backgroundColor) {
|
||||
if(ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
|
||||
else ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
}
|
||||
this.drawClipImage(ctx, this.radius, scale, () => {
|
||||
this.drawRotateImage(ctx, this.rotate, scale);
|
||||
const r = this.rotate / 90;
|
||||
ctx.drawImage(
|
||||
image,
|
||||
[
|
||||
(this.offsetX - this.area.left),
|
||||
(this.offsetY - this.area.top),
|
||||
-(this.offsetX - this.area.left),
|
||||
-(this.offsetY - this.area.top)
|
||||
][r] * scale,
|
||||
[
|
||||
(this.offsetY - this.area.top),
|
||||
-(this.offsetX - this.area.left),
|
||||
-(this.offsetY - this.area.top),
|
||||
(this.offsetX - this.area.left)
|
||||
][r] * scale,
|
||||
this.scaleWidth * scale,
|
||||
this.scaleHeight * scale
|
||||
);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 绘图
|
||||
* @param {Object} canvas
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {String} src 图片路径
|
||||
* @param {Function} callback 开始绘制时回调
|
||||
*/
|
||||
draw2DImage(canvas, ctx, src, callback) {
|
||||
// console.log('draw2DImage', canvas, ctx, src, callback)
|
||||
if(canvas) {
|
||||
const image = canvas.createImage();
|
||||
image.onload = () => {
|
||||
this.drawImage(ctx, image);
|
||||
// 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间
|
||||
callback && setTimeout(callback, this.delay);
|
||||
};
|
||||
image.onerror = (err) => {
|
||||
console.error(err)
|
||||
uni.hideLoading();
|
||||
};
|
||||
image.src = src;
|
||||
} else {
|
||||
this.drawImage(ctx, src);
|
||||
setTimeout(() => {
|
||||
ctx.draw(false, callback);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 画布转图片到本地缓存
|
||||
* @param {Object} canvas
|
||||
* @param {String} canvasId
|
||||
*/
|
||||
canvasToTempFilePath(canvas, canvasId) {
|
||||
// console.log('canvasToTempFilePath', canvas, canvasId)
|
||||
uni.canvasToTempFilePath({
|
||||
canvas,
|
||||
canvasId,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.canvansWidth,
|
||||
height: this.canvansHeight,
|
||||
destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响
|
||||
destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响
|
||||
fileType: this.fileType, // 目标文件的类型,默认png
|
||||
success: (res) => {
|
||||
// 生成的图片临时文件路径
|
||||
this.handleImage(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' });
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
/** 确认裁剪 */
|
||||
cropClick() {
|
||||
uni.showLoading({ title: '裁剪中...', mask: true });
|
||||
if(!this.use2d) {
|
||||
const ctx = uni.createCanvasContext('imgCanvas', this);
|
||||
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
this.draw2DImage(null, ctx, this.imgSrc, () => {
|
||||
this.canvasToTempFilePath(null, 'imgCanvas');
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('#imgCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
const canvas = res[0].node;
|
||||
|
||||
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||
canvas.width = res[0].width * dpr;
|
||||
canvas.height = res[0].height * dpr;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
|
||||
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
|
||||
this.canvasToTempFilePath(canvas);
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
handleImage(tempFilePath){
|
||||
// 在H5平台下,tempFilePath 为 base64
|
||||
// console.log(tempFilePath)
|
||||
uni.hideLoading();
|
||||
this.$emit('crop', { tempFilePath });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-cropper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #000;
|
||||
.img-canvas {
|
||||
position: absolute !important;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.pic-preview {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.crop-mask-block {
|
||||
background-color: rgba(51, 51, 51, 0.8);
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
.crop-circle-box {
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
.crop-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.crop-image {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
display: block !important;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
.crop-border {
|
||||
position: fixed;
|
||||
border: 1px solid #fff;
|
||||
box-sizing: border-box;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
}
|
||||
.crop-grid {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
border-style: dashed;
|
||||
border-color: #fff;
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.crop-angle {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
border-style: solid;
|
||||
border-color: #fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-bottom {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #1a1a1a;
|
||||
|
||||
.action-bar {
|
||||
position: absolute;
|
||||
top: -90rpx;
|
||||
left: 10rpx;
|
||||
display: flex;
|
||||
.rotate-icon {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=');
|
||||
background-size: 60% 60%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
&.is-reverse {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rechoose {
|
||||
color: #ffffffde;
|
||||
padding: 0 $uni-spacing-row-lg;
|
||||
line-height: 100rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.choose-btn {
|
||||
color: #ffffffde;
|
||||
text-align: center;
|
||||
line-height: 100rpx;
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: auto $uni-spacing-row-lg auto auto;
|
||||
background-color: #BBE600;
|
||||
color: #232323de;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.safe-area-inset-bottom {
|
||||
padding-bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
|
||||
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -0,0 +1,604 @@
|
||||
/**
|
||||
* 图片编辑器-手势监听
|
||||
* 1. wxs 暂不支持 es6 语法
|
||||
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上(uni-app 2.2.5及以上版本)
|
||||
*/
|
||||
/** 图片偏移量 */
|
||||
var offset = { x: 0, y: 0 };
|
||||
/** 图片缩放比例 */
|
||||
var scale = 1;
|
||||
/** 图片最小缩放比例 */
|
||||
var minScale = 1;
|
||||
/** 图片旋转角度 */
|
||||
var rotate = 0;
|
||||
/** 触摸点 */
|
||||
var touches = [];
|
||||
/** 图片布局信息 */
|
||||
var img = {};
|
||||
/** 系统信息 */
|
||||
var sys = {};
|
||||
/** 裁剪区域布局信息 */
|
||||
var area = {};
|
||||
/** 触摸行为类型 */
|
||||
var touchType = '';
|
||||
/** 操作角的位置 */
|
||||
var activeAngle = 0;
|
||||
/** 裁剪区域布局信息偏移量 */
|
||||
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
/**
|
||||
* 计算两点间距
|
||||
* @param {Object} touches 触摸点信息
|
||||
*/
|
||||
function getDistanceByTouches(touches) {
|
||||
// 根据勾股定理求两点间距离
|
||||
var a = touches[1].pageX - touches[0].pageX;
|
||||
var b = touches[1].pageY - touches[0].pageY;
|
||||
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||
// 求两点间的中点坐标
|
||||
// 1. a、b可能为负值
|
||||
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||
var x = touches[1].pageX - a / 2;
|
||||
var y = touches[1].pageY - b / 2;
|
||||
return { c, x, y };
|
||||
};
|
||||
/**
|
||||
* 修正取值
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @param {Object} c
|
||||
* @param {Object} reverse 是否反向
|
||||
*/
|
||||
function correctValue(a, b, c, reverse) {
|
||||
return reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
*/
|
||||
function checkRange(e) {
|
||||
var r = rotate / 90 % 2;
|
||||
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||
var o = (img.height - img.width) / 2; // 宽高差值一半
|
||||
return {
|
||||
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, img.height < area.height),
|
||||
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, img.width < area.width)
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 变更图片布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeImageRect(e) {
|
||||
offset.x += e.x || 0;
|
||||
offset.y += e.y || 0;
|
||||
var image = e.instance.selectComponent('.crop-image');
|
||||
if(e.check && area.checkRange) { // 检查边界
|
||||
var point = checkRange(offset);
|
||||
if(offset.x !== point.x || offset.y !== point.y) {
|
||||
offset = point;
|
||||
}
|
||||
}
|
||||
// image.setStyle({
|
||||
// width: img.width + 'px',
|
||||
// height: img.height + 'px',
|
||||
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
|
||||
// });
|
||||
var ox = (img.width - img.oldWidth) / 2;
|
||||
var oy = (img.height - img.oldHeight) / 2;
|
||||
image.setStyle({
|
||||
width: img.oldWidth + 'px',
|
||||
height: img.oldHeight + 'px',
|
||||
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
});
|
||||
|
||||
e.instance.callMethod('dataChange', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
rotate: rotate
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 变更裁剪区域布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeAreaRect(e) {
|
||||
// 变更蒙版样式
|
||||
var masks = e.instance.selectAllComponents('.crop-mask-block');
|
||||
var maskStyles = [
|
||||
{
|
||||
left: 0,
|
||||
width: (area.left + areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.right + areaOffset.right) + 'px',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
height: (area.top + areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
}
|
||||
];
|
||||
var len = masks.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
masks[i].setStyle(maskStyles[i]);
|
||||
}
|
||||
|
||||
// 变更边框样式
|
||||
if(area.showBorder) {
|
||||
var border = e.instance.selectComponent('.crop-border');
|
||||
border.setStyle({
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
});
|
||||
}
|
||||
|
||||
// 变更参考线样式
|
||||
if(area.showGrid) {
|
||||
var grids = e.instance.selectAllComponents('.crop-grid');
|
||||
var gridStyles = [
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
];
|
||||
var len = grids.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
grids[i].setStyle(gridStyles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 变更四个伸缩角样式
|
||||
if(area.showAngle) {
|
||||
var angles = e.instance.selectAllComponents('.crop-angle');
|
||||
var angleStyles = [
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
];
|
||||
var len = angles.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
angles[i].setStyle(angleStyles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 变更圆角样式
|
||||
if(area.radius > 0) {
|
||||
var circleBox = e.instance.selectComponent('.crop-circle-box');
|
||||
var circle = e.instance.selectComponent('.crop-circle');
|
||||
var radius = area.radius;
|
||||
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||
radius = (area.width / 2);
|
||||
} else { // 圆角矩形
|
||||
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||
}
|
||||
}
|
||||
circleBox.setStyle({
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
});
|
||||
circle.setStyle({
|
||||
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||
'border-radius': radius + 'px'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 缩放图片
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function scaleImage(e) {
|
||||
var last = scale;
|
||||
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||
if(last !== scale) {
|
||||
img.width = img.oldWidth * scale;
|
||||
img.height = img.oldHeight * scale;
|
||||
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||
e.x = (e.x - offset.x) * (1 - scale / last);
|
||||
e.y = (e.y - offset.y) * (1 - scale / last);
|
||||
changeImageRect(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 获取触摸点在哪个角
|
||||
* @param {number} x 触摸点x轴坐标
|
||||
* @param {number} y 触摸点y轴坐标
|
||||
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||
*/
|
||||
function getToucheAngle(x, y) {
|
||||
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 1; // 左上角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 2; // 右上角
|
||||
}
|
||||
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 3; // 左下角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 4; // 右下角
|
||||
}
|
||||
}
|
||||
return 0; // 无触摸到角
|
||||
};
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
function resetData() {
|
||||
offset = { x: 0, y: 0 };
|
||||
scale = 1;
|
||||
minScale = img.minScale;
|
||||
rotate = 0;
|
||||
};
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
function rotateImage(e, o, r) {
|
||||
rotate = (rotate + r) % 360;
|
||||
|
||||
if(img.minScale >= 1) {
|
||||
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||
minScale = 1;
|
||||
if(img.width < area.height) {
|
||||
minScale = area.height / img.oldWidth;
|
||||
} else if(img.height < area.width) {
|
||||
minScale = (area.width / img.oldHeight)
|
||||
}
|
||||
if(minScale !== 1) {
|
||||
scaleImage({
|
||||
instance: o,
|
||||
scale: minScale - scale,
|
||||
x: sys.windowWidth / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: true,
|
||||
x: -ox - oy,
|
||||
y: -oy + ox
|
||||
});
|
||||
};
|
||||
module.exports = {
|
||||
/**
|
||||
* 初始化:观察数据变更
|
||||
* @param {Object} newVal 新数据
|
||||
* @param {Object} oldVal 旧数据
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
initObserver: function(newVal, oldVal, o, i) {
|
||||
if(newVal) {
|
||||
img = newVal.img;
|
||||
sys = newVal.sys;
|
||||
area = newVal.area;
|
||||
minScale = img.minScale;
|
||||
resetData();
|
||||
img.src && changeImageRect({
|
||||
instance: o,
|
||||
x: (sys.windowWidth - img.width) / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
|
||||
});
|
||||
changeAreaRect({
|
||||
instance: o
|
||||
});
|
||||
// console.log('initRect', JSON.stringify(newVal))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 鼠标滚轮滚动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
mousewheel: function(e, o) {
|
||||
if(!img.src) return;
|
||||
scaleImage({
|
||||
instance: o,
|
||||
check: true,
|
||||
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 触摸开始
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchstart: function(e, o) {
|
||||
if(!img.src) return;
|
||||
touches = e.touches;
|
||||
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||
if(touches.length === 1 && activeAngle !== 0) {
|
||||
touchType = 'stretch'; // 伸缩裁剪区域
|
||||
} else {
|
||||
touchType = '';
|
||||
}
|
||||
// console.log('touchstart', JSON.stringify(e), activeAngle)
|
||||
},
|
||||
/**
|
||||
* 触摸移动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchmove: function(e, o) {
|
||||
if(!img.src) return;
|
||||
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
|
||||
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||
var point = e.touches[0];
|
||||
var start = touches[0];
|
||||
var x = point.pageX - start.pageX;
|
||||
var y = point.pageY - start.pageY;
|
||||
if(x !== 0 || y !== 0) {
|
||||
var maxX = area.width * (1 - area.minScale);
|
||||
var maxY = area.height * (1 - area.minScale);
|
||||
// console.log(x, y, maxX, maxY, offset, area)
|
||||
touches[0] = point;
|
||||
switch(activeAngle) {
|
||||
case 1: // 左上角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.top;
|
||||
if(x >= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, offset.x - area.left)
|
||||
: false;
|
||||
if(x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 2: // 右上角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.top;
|
||||
if(x <= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 3: // 左下角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.bottom;
|
||||
if(x >= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, offset.x - area.left)
|
||||
: false;
|
||||
if(x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
case 4: // 右下角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.bottom;
|
||||
if(x <= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log(x, y, JSON.stringify(areaOffset))
|
||||
changeAreaRect({
|
||||
instance: o,
|
||||
});
|
||||
// this.draw();
|
||||
}
|
||||
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||
var start = getDistanceByTouches(touches);
|
||||
var end = getDistanceByTouches(e.touches);
|
||||
scaleImage({
|
||||
instance: o,
|
||||
check: !area.bounce,
|
||||
scale: (end.c - start.c) / 100,
|
||||
x: end.x,
|
||||
y: end.y
|
||||
});
|
||||
touchType = 'scale';
|
||||
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||
touchType = 'move';
|
||||
} else {
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: !area.bounce,
|
||||
x: e.touches[0].pageX - touches[0].pageX,
|
||||
y: e.touches[0].pageY - touches[0].pageY
|
||||
});
|
||||
touchType = 'move';
|
||||
}
|
||||
touches = e.touches;
|
||||
},
|
||||
/**
|
||||
* 触摸结束
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchend: function(e, o) {
|
||||
if(!img.src) return;
|
||||
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||
// 裁剪区域宽度被缩放到多少
|
||||
var left = areaOffset.left;
|
||||
var right = areaOffset.right;
|
||||
var top = areaOffset.top;
|
||||
var bottom = areaOffset.bottom;
|
||||
var w = area.width + right - left;
|
||||
var h = area.height + bottom - top;
|
||||
// 图像放大倍数
|
||||
var p = scale * (area.width / w) - scale;
|
||||
// 复原裁剪区域
|
||||
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
changeAreaRect({
|
||||
instance: o,
|
||||
});
|
||||
scaleImage({
|
||||
instance: o,
|
||||
scale: p,
|
||||
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||
});
|
||||
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: true
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
rotateImage: function(e, o) {
|
||||
rotateImage(e, o, 90);
|
||||
},
|
||||
rotateImage90: function(e, o) {
|
||||
rotateImage(e, o, 90)
|
||||
},
|
||||
rotateImage270: function(e, o) {
|
||||
rotateImage(e, o, 270)
|
||||
},
|
||||
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
|
||||
imageStyles: '',
|
||||
maskStylesList: ['', '', '', ''],
|
||||
borderStyles: '',
|
||||
gridStylesList: ['', '', '', ''],
|
||||
angleStylesList: ['', '', '', ''],
|
||||
circleBoxStyles: '',
|
||||
circleStyles: '',
|
||||
}
|
81
uni_modules/qf-image-cropper/package.json
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"id": "qf-image-cropper",
|
||||
"displayName": "图片裁剪插件",
|
||||
"version": "2.2.4",
|
||||
"description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。",
|
||||
"keywords": [
|
||||
"qf-image-cropper",
|
||||
"图片裁剪",
|
||||
"图片编辑",
|
||||
"头像裁剪",
|
||||
"小程序"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件不采集任何数据",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "n",
|
||||
"百度": "n",
|
||||
"字节跳动": "n",
|
||||
"QQ": "u",
|
||||
"钉钉": "n",
|
||||
"快手": "n",
|
||||
"飞书": "n",
|
||||
"京东": "n"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
95
uni_modules/qf-image-cropper/readme.md
Normal file
@ -0,0 +1,95 @@
|
||||
# qf-image-cropper
|
||||
## 图片裁剪插件
|
||||
uniapp微信小程序图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。
|
||||
|
||||
### 平台支持:
|
||||
1. 支持微信小程序:移动端、PC端、开发者工具
|
||||
2. 支持H5平台(2.1.0版本起)
|
||||
3. 支持APP平台(2.1.5版本起):Android、IOS
|
||||
4. 其他平台暂未测试兼容性未知
|
||||
|
||||
### 支持功能:
|
||||
1. 自定义裁剪尺寸
|
||||
2. 定点等比例缩放:移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||
3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||
4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸
|
||||
5. 裁剪生成新图片
|
||||
6. 本地选择图片
|
||||
7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线
|
||||
8. 裁剪圆角图片:圆形、圆角矩形
|
||||
|
||||
### 属性说明
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
|:---|:---|:---|:---|
|
||||
| src | String | | 图片资源地址 |
|
||||
| width | Number | 300 | 裁剪宽度 |
|
||||
| height | Number | 300 | 裁剪高度 |
|
||||
| showBorder | Boolean | true | 是否绘制裁剪区域边框 |
|
||||
| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 |
|
||||
| showAngle | Boolean | true | 是否展示四个支持伸缩的角 |
|
||||
| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 |
|
||||
| minScale | Number | 1 | 图片最小缩放倍数 |
|
||||
| maxScale | Number | 5 | 图片最大缩放倍数 |
|
||||
| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 |
|
||||
| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 |
|
||||
| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 |
|
||||
| rotatable | Boolean | true | 是否支持翻转 |
|
||||
| reverseRotatable | Boolean | false | 是否支持逆向翻转 |
|
||||
| choosable | Boolean | true | 是否支持从本地选择素材 |
|
||||
| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 |
|
||||
| angleSize | Number | 20 | 四个角尺寸,单位px |
|
||||
| angleBorderWidth | Number | 2 | 四个角边框宽度,单位px |
|
||||
| zIndex | Number/String | | 调整组件层级 |
|
||||
| radius | Number | | 裁剪图片圆角半径,单位px |
|
||||
| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' |
|
||||
| delay | Number | 1000 | 图片从绘制到生成所需时间,单位ms<br>微信小程序平台使用 `Canvas 2D` 绘制时有效<br>如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 |
|
||||
| navigation | Boolean | true | 页面是否是原生标题栏:<br>H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。<br>注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的 |
|
||||
| @crop | EventHandle | | 剪裁完成后触发,event = { tempFilePath }。在H5平台下,tempFilePath 为 base64 |
|
||||
|
||||
### 基本用法
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QfImageCropper from '@/components/qf-image-cropper/qf-image-cropper.vue';
|
||||
export default {
|
||||
components: {
|
||||
QfImageCropper
|
||||
},
|
||||
methods: {
|
||||
handleCrop(e) {
|
||||
uni.previewImage({
|
||||
urls: [e.tempFilePath],
|
||||
current: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
通过ref组件实例可在进入页面后直接打开相册选择图片
|
||||
```
|
||||
mounted() {
|
||||
this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] });
|
||||
}
|
||||
```
|
||||
### 使用说明
|
||||
1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。
|
||||
```
|
||||
{
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
```
|
||||
2.建议使用本插件不要设置过大宽高的目标图片尺寸,建议1365x1365以内,否则可能会导致如下问题:
|
||||
```
|
||||
1.界面卡顿,内存占用过高
|
||||
2.生成图片失真(模糊)
|
||||
3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。
|
||||
```
|
||||
3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。
|
||||
4.src属性设置网络图片时,图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。
|
11
unpackage/dist/dev/.nvue/app.css.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
var require_app_css = __commonJS({
|
||||
"app.css.js"(exports) {
|
||||
const _style_0 = {};
|
||||
exports.styles = [_style_0];
|
||||
}
|
||||
});
|
||||
export default require_app_css();
|
2
unpackage/dist/dev/.nvue/app.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Promise.resolve("./app.css.js").then(() => {
|
||||
});
|
16
unpackage/dist/dev/app-plus/__uniappautomator.js
vendored
Normal file
@ -1,8 +1,8 @@
|
||||
|
||||
var isReady=false;var onReadyCallbacks=[];
|
||||
var isServiceReady=false;var onServiceReadyCallbacks=[];
|
||||
var __uniConfig = {"pages":["pages/common/login/index","pages/common/index/index","pages/common/user/index","pages/common/scan/scan","pages/common/qrcode/qrcode","pages/common/send/index","pages/common/userAgreement/index","pages/common/privacyAgreement/index","pages/common/aboutUs/index","pages/6170/deviceControl/index","pages/6170/operationVideo/index","pages/6170/addvideo/index","pages/6170/editVideo/index","pages/6170/operatingInstruct/index","pages/6155/index","pages/6155/bluetooth/bluetooth"],"window":{"navigationBarTextStyle":"white","navigationBarTitleText":"uni-app","navigationBarBackgroundColor":"#121212","backgroundColor":"#121212"},"tabBar":{"color":"#fff","selectedColor":"#BBE600","backgroundColor":"#202020","list":[{"pagePath":"pages/common/index/index","text":"我的设备","iconPath":"/static/tabs/device.png","selectedIconPath":"/static/tabs/device-HL.png"},{"pagePath":"pages/common/user/index","text":"我的","iconPath":"/static/tabs/my.png","selectedIconPath":"/static/tabs/my-HL.png"}]},"darkmode":false,"nvueCompiler":"uni-app","nvueStyleCompiler":"uni-app","renderer":"auto","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":false},"appname":"JingQuan","compilerVersion":"4.64","entryPagePath":"pages/common/login/index","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000}};
|
||||
var __uniRoutes = [{"path":"/pages/common/login/index","meta":{"isQuit":true},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/index/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/user/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationBarTitleText":"我的"}},{"path":"/pages/common/scan/scan","meta":{},"window":{"navigationBarTitleText":"扫描"}},{"path":"/pages/common/qrcode/qrcode","meta":{},"window":{"navigationBarTitleText":"扫描到的设备"}},{"path":"/pages/common/send/index","meta":{},"window":{"navigationBarTitleText":"发送信息"}},{"path":"/pages/common/userAgreement/index","meta":{},"window":{"navigationBarTitleText":"用户协议"}},{"path":"/pages/common/privacyAgreement/index","meta":{},"window":{"navigationBarTitleText":"隐私协议"}},{"path":"/pages/common/aboutUs/index","meta":{},"window":{"navigationBarTitleText":"关于我们"}},{"path":"/pages/6170/deviceControl/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/6170/operationVideo/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/6170/addvideo/index","meta":{},"window":{"navigationBarTitleText":"添加"}},{"path":"/pages/6170/editVideo/index","meta":{},"window":{"navigationBarTitleText":"编辑视频"}},{"path":"/pages/6170/operatingInstruct/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/6155/index","meta":{},"window":{"navigationBarTitleText":"6155"}},{"path":"/pages/6155/bluetooth/bluetooth","meta":{},"window":{"navigationStyle":"custom"}}];
|
||||
var __uniConfig = {"pages":["pages/common/login/index","pages/common/index/index","pages/common/user/index","pages/common/scan/scan","pages/common/qrcode/qrcode","pages/common/send/index","pages/common/userAgreement/index","pages/common/privacyAgreement/index","pages/common/aboutUs/index","pages/6170/deviceControl/index","pages/common/operationVideo/index","pages/common/addvideo/index","pages/common/operatingInstruct/index","pages/common/productDes/index","pages/common/addBLE/addEquip","pages/common/addBLE/LinkBle","pages/6155/deviceDetail","pages/6155/ImgCrop","pages/common/map/index"],"window":{"navigationBarTextStyle":"white","navigationBarTitleText":"uni-app","navigationBarBackgroundColor":"#121212","backgroundColor":"#121212"},"tabBar":{"color":"#fff","selectedColor":"#BBE600","backgroundColor":"#202020","list":[{"pagePath":"pages/common/index/index","text":"我的设备","iconPath":"/static/tabs/device.png","selectedIconPath":"/static/tabs/device-HL.png"},{"pagePath":"pages/common/user/index","text":"我的","iconPath":"/static/tabs/my.png","selectedIconPath":"/static/tabs/my-HL.png"}]},"darkmode":false,"nvueCompiler":"uni-app","nvueStyleCompiler":"uni-app","renderer":"auto","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":false},"appname":"JingQuan","compilerVersion":"4.64","entryPagePath":"pages/common/login/index","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000}};
|
||||
var __uniRoutes = [{"path":"/pages/common/login/index","meta":{"isQuit":true},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/index/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/user/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationBarTitleText":"我的"}},{"path":"/pages/common/scan/scan","meta":{},"window":{"navigationBarTitleText":"扫描"}},{"path":"/pages/common/qrcode/qrcode","meta":{},"window":{"navigationBarTitleText":"扫描到的设备"}},{"path":"/pages/common/send/index","meta":{},"window":{"navigationBarTitleText":"发送信息"}},{"path":"/pages/common/userAgreement/index","meta":{},"window":{"navigationBarTitleText":"用户协议"}},{"path":"/pages/common/privacyAgreement/index","meta":{},"window":{"navigationBarTitleText":"隐私协议"}},{"path":"/pages/common/aboutUs/index","meta":{},"window":{"navigationBarTitleText":"关于我们"}},{"path":"/pages/6170/deviceControl/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/operationVideo/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/addvideo/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/operatingInstruct/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/productDes/index","meta":{},"window":{"navigationStyle":"custom"}},{"path":"/pages/common/addBLE/addEquip","meta":{},"window":{"navigationBarTitleText":"添加设备"}},{"path":"/pages/common/addBLE/LinkBle","meta":{},"window":{"navigationBarTitleText":"扫描到的设备"}},{"path":"/pages/6155/deviceDetail","meta":{},"window":{"navigationBarTitleText":"HBY 6155"}},{"path":"/pages/6155/ImgCrop","meta":{},"window":{"navigationBarTitleText":"图像裁剪","navigationStyle":"custom","fullscreen":true}},{"path":"/pages/common/map/index","meta":{},"window":{"navigationBarTitleText":"地图"}}];
|
||||
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
|
||||
__uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
|
||||
service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:Math.round(f/20)})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,global:void 0,window:void 0,document:void 0,frames:void 0,self:void 0,location:void 0,navigator:void 0,localStorage:void 0,history:void 0,Caches:void 0,screen:void 0,alert:void 0,confirm:void 0,prompt:void 0,fetch:void 0,XMLHttpRequest:void 0,WebSocket:void 0,webkit:void 0,print:void 0}}}});
|
||||
|
4496
unpackage/dist/dev/app-plus/app-service.js
vendored
7891
unpackage/dist/dev/app-plus/app-view.js
vendored
2
unpackage/dist/dev/app-plus/manifest.json
vendored
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/add.png
vendored
Normal file
After Width: | Height: | Size: 145 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/battry.png
vendored
Normal file
After Width: | Height: | Size: 268 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/equip.png
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/fan.png
vendored
Normal file
After Width: | Height: | Size: 275 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/fuLamp.png
vendored
Normal file
After Width: | Height: | Size: 415 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/mainLamp.png
vendored
Normal file
After Width: | Height: | Size: 413 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/open.png
vendored
Normal file
After Width: | Height: | Size: 357 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/param.png
vendored
Normal file
After Width: | Height: | Size: 289 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/qiang.png
vendored
Normal file
After Width: | Height: | Size: 612 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/remark.png
vendored
Normal file
After Width: | Height: | Size: 269 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/ruo.png
vendored
Normal file
After Width: | Height: | Size: 528 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/sendSucc.png
vendored
Normal file
After Width: | Height: | Size: 500 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/shan.png
vendored
Normal file
After Width: | Height: | Size: 304 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/slideToggle.png
vendored
Normal file
After Width: | Height: | Size: 194 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/time.png
vendored
Normal file
After Width: | Height: | Size: 321 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/uploadSuccess.png
vendored
Normal file
After Width: | Height: | Size: 282 B |
BIN
unpackage/dist/dev/app-plus/static/images/6155/DeviceDetail/video.png
vendored
Normal file
After Width: | Height: | Size: 212 B |
BIN
unpackage/dist/dev/app-plus/static/images/BLEAdd/device.png
vendored
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
unpackage/dist/dev/app-plus/static/images/BLEAdd/linked.png
vendored
Normal file
After Width: | Height: | Size: 793 B |
BIN
unpackage/dist/dev/app-plus/static/images/BLEAdd/noLink.png
vendored
Normal file
After Width: | Height: | Size: 670 B |
BIN
unpackage/dist/dev/app-plus/static/images/BLEAdd/wifi.png
vendored
Normal file
After Width: | Height: | Size: 522 B |
BIN
unpackage/dist/dev/app-plus/static/images/delete-icon.png
vendored
Normal file
After Width: | Height: | Size: 605 B |
7
unpackage/dist/dev/app-plus/uni-app-view.umd.js
vendored
Normal file
@ -1,6 +1,6 @@
|
||||
const BASE_URL = 'http://192.168.2.34:8001';
|
||||
|
||||
const BASE_URL = 'http://192.168.2.34:8000';
|
||||
const request = (options) => {
|
||||
console.log("options"+JSON.stringify(options))
|
||||
return new Promise((resolve, reject) => {
|
||||
// 处理GET请求参数
|
||||
let url = BASE_URL + options.url;
|
||||
@ -40,5 +40,8 @@ const request = (options) => {
|
||||
uni.request(config);
|
||||
});
|
||||
};
|
||||
|
||||
// 导出基础URL以便其他地方使用
|
||||
export const baseURL = BASE_URL
|
||||
export const getToken = () => uni.getStorageSync('token') // 获取token的方法
|
||||
export const clientid =() => uni.getStorageSync('clientID');
|
||||
export default request;
|