完成选择热门icon页,支付宝账单新增80%
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version" : "1.0",
|
||||||
|
"configurations" : [
|
||||||
|
{
|
||||||
|
"playground" : "custom",
|
||||||
|
"type" : "uni-app:app-ios"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"playground" : "custom",
|
||||||
|
"type" : "uni-app:app-android"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -24,8 +24,8 @@
|
||||||
</view>
|
</view>
|
||||||
<view v-if="item.isRefund" class="refund">已全额退款</view>
|
<view v-if="item.isRefund" class="refund">已全额退款</view>
|
||||||
<view v-if="isBalance" class="balance secondary">余额 <text class="balance-text">{{
|
<view v-if="isBalance" class="balance secondary">余额 <text class="balance-text">{{
|
||||||
Number(item.balance).toFixed(2)
|
Number(item.balance).toFixed(2)
|
||||||
}}</text>元</view>
|
}}</text>元</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -35,110 +35,108 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
defineProps,
|
onMounted,
|
||||||
defineEmits,
|
reactive
|
||||||
onMounted,
|
} from 'vue'
|
||||||
reactive
|
|
||||||
} from 'vue'
|
|
||||||
|
|
||||||
// 定义组件属性
|
// 定义组件属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
list: {
|
list: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
isBalance: {
|
isBalance: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['onBill'])
|
const emit = defineEmits(['onBill'])
|
||||||
|
|
||||||
const data = reactive({})
|
const data = reactive({})
|
||||||
|
|
||||||
onMounted(() => {})
|
onMounted(() => { })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* @import '../../common/main.css'; */
|
/* @import '../../common/main.css'; */
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.balance-list-container {
|
.balance-list-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-item {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.balance-item-text-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-left: 12px;
|
||||||
|
box-shadow: 0 0.3px 0 0 #F0F0EE;
|
||||||
|
padding: 12px 12px 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-item {
|
.img {
|
||||||
|
margin: 12px 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-item-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
flex-direction: column;
|
||||||
flex-direction: row;
|
// justify-content: space-between;
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.balance-item-text-container {
|
.name {
|
||||||
flex: 1;
|
color: #343434;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-left: 12px;
|
|
||||||
box-shadow: 0 0.3px 0 0 #F0F0EE;
|
|
||||||
padding: 12px 12px 12px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.img {
|
.secondary {
|
||||||
margin: 12px 0;
|
color: var(--text-secondary);
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-item-text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
// justify-content: space-between;
|
|
||||||
|
|
||||||
.name {
|
|
||||||
color: #343434;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.money {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-color {
|
|
||||||
color: #F6610F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red-add-color {
|
|
||||||
color: #F53646;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minus-color {
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.balance-text {
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.refund {
|
|
||||||
color: #EA6B48;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-right {
|
.money {
|
||||||
align-items: flex-end;
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-color {
|
||||||
|
color: #F6610F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-add-color {
|
||||||
|
color: #F53646;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minus-color {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.balance-text {
|
||||||
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.refund {
|
||||||
|
color: #EA6B48;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -46,8 +46,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import popup from '../popup/popup.vue'
|
import popup from '../popup/popup.vue'
|
||||||
import {
|
import {
|
||||||
defineProps,
|
|
||||||
defineEmits,
|
|
||||||
onMounted,
|
onMounted,
|
||||||
reactive,
|
reactive,
|
||||||
ref
|
ref
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name" : "alipay-emulator",
|
"name" : "alipay-emulator",
|
||||||
"appid" : "__UNI__D535736",
|
"appid" : "__UNI__B05EDBF",
|
||||||
"description" : "",
|
"description" : "",
|
||||||
"versionName" : "1.0.0",
|
"versionName" : "1.0.0",
|
||||||
"versionCode" : "100",
|
"versionCode" : "100",
|
||||||
|
|
@ -41,7 +41,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
/* ios打包配置 */
|
/* ios打包配置 */
|
||||||
"ios" : {},
|
"ios" : {
|
||||||
|
"dSYMs" : false
|
||||||
|
},
|
||||||
/* SDK配置 */
|
/* SDK配置 */
|
||||||
"sdkConfigs" : {}
|
"sdkConfigs" : {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,13 @@
|
||||||
"navigationBarTitleText": "新增账单",
|
"navigationBarTitleText": "新增账单",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/common/hot-icon/hot-icon",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "热门图标",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
|
|
|
||||||
|
|
@ -5,55 +5,64 @@
|
||||||
"selectId": "1",
|
"selectId": "1",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"isAdd": false,
|
"isAdd": false,
|
||||||
|
"imageUrl": "",
|
||||||
|
"name": "",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "商品说明",
|
"label": "商品说明",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "收单机构",
|
"label": "收单机构",
|
||||||
"value": "支付宝支付科技有限公司",
|
"value": "支付宝支付科技有限公司",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "collectionOrganization"
|
"key": "collectionOrganization",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "收款方全称",
|
"label": "收款方全称",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "receiverFullName"
|
"key": "receiverFullName",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "日用百货",
|
"value": "日用百货",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -68,48 +77,56 @@
|
||||||
"selectId": "2",
|
"selectId": "2",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"isAdd": false,
|
"isAdd": false,
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/shangpin.png",
|
||||||
|
"name": "**x",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "商品说明",
|
"label": "商品说明",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "收款方全称",
|
"label": "收款方全称",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "receiverFullName"
|
"key": "receiverFullName",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "日用百货",
|
"value": "日用百货",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -125,55 +142,64 @@
|
||||||
"selectId": "3",
|
"selectId": "3",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"isAdd": false,
|
"isAdd": false,
|
||||||
|
"name": "",
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/shangpin.png",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "商品说明",
|
"label": "商品说明",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "收单机构",
|
"label": "收单机构",
|
||||||
"value": "支付宝支付科技有限公司",
|
"value": "支付宝支付科技有限公司",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "collectionOrganization"
|
"key": "collectionOrganization",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "清算机构",
|
"label": "清算机构",
|
||||||
"value": "中国中国银联股份有限公司",
|
"value": "中国中国银联股份有限公司",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "clearingOrganization"
|
"key": "clearingOrganization",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "餐饮美食",
|
"value": "餐饮美食",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -187,41 +213,48 @@
|
||||||
"selectId": "4",
|
"selectId": "4",
|
||||||
"orderStatus": "还款成功",
|
"orderStatus": "还款成功",
|
||||||
"isAdd": false,
|
"isAdd": false,
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/huabei.png",
|
||||||
|
"name": "花呗",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "还款到",
|
"label": "还款到",
|
||||||
"value": "花呗",
|
"value": "花呗",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "repaymentTo"
|
"key": "repaymentTo",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "信用借还",
|
"value": "信用借还",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -232,49 +265,57 @@
|
||||||
"type": "转账1",
|
"type": "转账1",
|
||||||
"selectId": "5",
|
"selectId": "5",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"isAdd": false,
|
"isAdd": true,
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/default.png",
|
||||||
|
"name": "xxxx有限公司",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "转账备注",
|
"label": "转账备注",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "transferNote"
|
"key": "transferNote",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "对方账户",
|
"label": "对方账户",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "counterAccount"
|
"key": "counterAccount",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "订单号",
|
"label": "订单号",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "orderNumber"
|
"key": "orderNumber",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "转账红包",
|
"value": "转账红包",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -288,41 +329,48 @@
|
||||||
"selectId": "6",
|
"selectId": "6",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"isAdd": true,
|
"isAdd": true,
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/default.png",
|
||||||
|
"name": "xxx",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "转账备注",
|
"label": "转账备注",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "transferNote"
|
"key": "transferNote",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "对方账户",
|
"label": "对方账户",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "counterAccount"
|
"key": "counterAccount",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "转账红包",
|
"value": "转账红包",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -337,41 +385,48 @@
|
||||||
"isAdd": false,
|
"isAdd": false,
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"selectId": "7",
|
"selectId": "7",
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/default.png",
|
||||||
|
"name": "xxx",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "对方账户",
|
"label": "对方账户",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "counterAccount"
|
"key": "counterAccount",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "转账红包",
|
"value": "转账红包",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -387,42 +442,49 @@
|
||||||
"selectId": "8",
|
"selectId": "8",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"isAdd": true,
|
"isAdd": true,
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/default.png",
|
||||||
|
"name": "**x",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "关联记录",
|
"label": "关联记录",
|
||||||
"value": "",
|
"value": "查看关联记录",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"textColor": "#12447A",
|
"textColor": "#12447A",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "relatedRecord"
|
"key": "relatedRecord",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "商品说明",
|
"label": "商品说明",
|
||||||
"value": "",
|
"value": "收钱码收款",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "收入",
|
"value": "收入",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -438,49 +500,57 @@
|
||||||
"selectId": "9",
|
"selectId": "9",
|
||||||
"orderStatus": "退款成功",
|
"orderStatus": "退款成功",
|
||||||
"isAdd": true,
|
"isAdd": true,
|
||||||
|
"imageUrl": "/static/image/common/hot-icon/tuikuan.png",
|
||||||
|
"name": "",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "关联记录",
|
"label": "关联记录",
|
||||||
"value": "",
|
"value": "查看原账单",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"textColor": "#12447A",
|
"textColor": "#12447A",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "relatedRecord"
|
"key": "relatedRecord",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "创建时间",
|
"label": "创建时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "退款方式",
|
"label": "退款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
|
||||||
"type": "text",
|
|
||||||
"focus": false,
|
|
||||||
"key": "refundMethod"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "商品说明",
|
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "refundMethod",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "商品说明",
|
||||||
|
"value": "退款-xxx",
|
||||||
|
"type": "text",
|
||||||
|
"focus": false,
|
||||||
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "退款",
|
"value": "退款",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -493,49 +563,58 @@
|
||||||
{
|
{
|
||||||
"type": "充值缴费1",
|
"type": "充值缴费1",
|
||||||
"selectId": "10",
|
"selectId": "10",
|
||||||
|
"isAdd": false,
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
|
"imageUrl": "",
|
||||||
|
"name": "中国移动",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "支付时间",
|
"label": "支付时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
|
||||||
"type": "text",
|
|
||||||
"focus": false,
|
|
||||||
"key": "payMethod"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "商品说明",
|
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "商品说明",
|
||||||
|
"value": "为123****5623充值",
|
||||||
|
"type": "text",
|
||||||
|
"focus": false,
|
||||||
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "收款方全称",
|
"label": "收款方全称",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "counterAccount"
|
"key": "receiverFullName",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "充值缴费",
|
"value": "充值缴费",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -550,6 +629,9 @@
|
||||||
{
|
{
|
||||||
"type": "充值缴费2",
|
"type": "充值缴费2",
|
||||||
"selectId": "11",
|
"selectId": "11",
|
||||||
|
"isAdd": false,
|
||||||
|
"imageUrl": "",
|
||||||
|
"name": "石油天然气公司",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
|
|
@ -557,49 +639,56 @@
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
|
||||||
"type": "text",
|
|
||||||
"focus": false,
|
|
||||||
"key": "payMethod"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "缴费说明",
|
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "缴费说明",
|
||||||
|
"value": "xxxxxxxx缴费",
|
||||||
|
"type": "text",
|
||||||
|
"focus": false,
|
||||||
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "户号",
|
"label": "户号",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "householdNumber"
|
"key": "householdNumber",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "订单号",
|
"label": "订单号",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "orderNumber"
|
"key": "orderNumber",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "充值缴费",
|
"value": "充值缴费",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -611,43 +700,62 @@
|
||||||
{
|
{
|
||||||
"type": "网购1",
|
"type": "网购1",
|
||||||
"selectId": "12",
|
"selectId": "12",
|
||||||
|
"isAdd": false,
|
||||||
|
"imageUrl": "",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
"label": "关联记录",
|
"label": "关联记录",
|
||||||
"value": "",
|
"value": "查看关联记录",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"textColor": "#12447A",
|
"textColor": "#12447A",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "relatedRecord"
|
"key": "relatedRecord",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "支付时间",
|
"label": "支付时间",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "日用百货",
|
"value": "日用百货",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "交易详情",
|
||||||
|
"value": {
|
||||||
|
"imgUrl": "",
|
||||||
|
"text": "",
|
||||||
|
"quantity": "共1件"
|
||||||
|
},
|
||||||
|
"type": "link",
|
||||||
|
"focus": false,
|
||||||
|
"key": "tradeDetail",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
@ -661,6 +769,8 @@
|
||||||
{
|
{
|
||||||
"type": "网购2",
|
"type": "网购2",
|
||||||
"selectId": "13",
|
"selectId": "13",
|
||||||
|
"isAdd": false,
|
||||||
|
"imageUrl": "",
|
||||||
"orderStatus": "交易成功",
|
"orderStatus": "交易成功",
|
||||||
"itemInfoList": [
|
"itemInfoList": [
|
||||||
{
|
{
|
||||||
|
|
@ -668,42 +778,48 @@
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "time",
|
"type": "time",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "createTime"
|
"key": "createTime",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "付款方式",
|
"label": "付款方式",
|
||||||
"value": "招商银行储蓄卡(0123)",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "payMethod"
|
"key": "payMethod",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "商品说明",
|
"label": "商品说明",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "productDescription"
|
"key": "productDescription",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "收款方全称",
|
"label": "收款方全称",
|
||||||
"value": "",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "counterAccount"
|
"key": "receiverFullName",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "账单分类",
|
"label": "账单分类",
|
||||||
"value": "日用百货",
|
"value": "日用百货",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "billCategory"
|
"key": "billCategory",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "标签和备注",
|
"label": "标签和备注",
|
||||||
"value": "添加",
|
"value": "",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"key": "tagAndNote"
|
"key": "tagAndNote",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultBottomIcons": [
|
"defaultBottomIcons": [
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,12 @@
|
||||||
<view class="add-bill-container">
|
<view class="add-bill-container">
|
||||||
<!-- 随机骰子 -->
|
<!-- 随机骰子 -->
|
||||||
<view class="random-dice">
|
<view class="random-dice">
|
||||||
<image class="random-dice-image" src="/static/image/common/random-dice.png"></image>
|
<image class="random-dice-image" src="/static/image/common/random-dice.png" @click="randomBillInfo">
|
||||||
|
</image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 头像 -->
|
<!-- 头像 -->
|
||||||
<view class="avatar-box">
|
<view class="avatar-box">
|
||||||
<image class="avatar-image" src="/static/image/bill/add-bill/add-avatar.png"></image>
|
<image class="avatar-image" :src="billData.imgUrl || defaultImage" @click="changeAvatar"></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 主要信息 -->
|
<!-- 主要信息 -->
|
||||||
<view class="main-info flex-align-center flex-column">
|
<view class="main-info flex-align-center flex-column">
|
||||||
|
|
@ -30,14 +31,19 @@
|
||||||
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
|
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 金额 -->
|
<!-- 金额 -->
|
||||||
<view class=" money info-item-input" style="height: 77rpx;">
|
<view class="money-box flex-align-center">
|
||||||
<!-- 隐藏的text用于测量宽度 -->
|
<view class="add money alipay-font">{{ billData.isAdd ? '+' : '-' }}</view>
|
||||||
<text class="text-measure font-w500" style="font-size: 64rpx;">{{ billData.money }}</text>
|
<!-- 金额 -->
|
||||||
|
<view class=" money info-item-input alipay-font" style="height: 77rpx;">
|
||||||
|
<!-- 隐藏的text用于测量宽度 -->
|
||||||
|
<text class="text-measure font-w500" style="font-size: 64rpx;">{{ billData.money }}</text>
|
||||||
|
|
||||||
<input class="text-input font-w500" style="font-size: 64rpx;" type="digit"
|
<input class="text-input font-w500" style="font-size: 64rpx;" type="digit"
|
||||||
v-model="billData.money" />
|
v-model="billData.money" />
|
||||||
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
|
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 订单状态 -->
|
<!-- 订单状态 -->
|
||||||
<view class="order-status-info" :class="{ isRefund: billData.merchantOption.refund }">
|
<view class="order-status-info" :class="{ isRefund: billData.merchantOption.refund }">
|
||||||
{{ billData.orderStatus }}
|
{{ billData.orderStatus }}
|
||||||
|
|
@ -45,30 +51,48 @@
|
||||||
</view>
|
</view>
|
||||||
<!-- 详情信息列表 -->
|
<!-- 详情信息列表 -->
|
||||||
<view class="detail-info-container">
|
<view class="detail-info-container">
|
||||||
<view class="info-item-box" v-for="item in billData.itemInfoList" :key="item.type">
|
<template v-for="item in billData.itemInfoList" :key="item.id">
|
||||||
<view class="item-label">
|
<view class="info-item-box">
|
||||||
{{ item.label }}
|
<view class="item-label">
|
||||||
</view>
|
{{ item.label }}
|
||||||
<view class="info-item-input" @click="onClickItemInfo(item)">
|
</view>
|
||||||
<!-- 隐藏的text用于测量宽度 -->
|
<view v-if="item.type != 'link'" class="info-item-input" @click="onClickItemInfo(item)">
|
||||||
<text class="text-measure"
|
<!-- 隐藏的text用于测量宽度 -->
|
||||||
:class="{ visibility: item.type == 'time' || item.type == 'select' }">{{ item.value
|
<text class="text-measure"
|
||||||
}}</text>
|
:class="{ visibility: item.type == 'time' || item.type == 'select' }">{{ item.value
|
||||||
|
}}</text>
|
||||||
|
|
||||||
<input v-if="item.type == 'text' || item.type == 'digit' || item.type == 'number'"
|
<input v-if="item.type == 'text' || item.type == 'digit' || item.type == 'number'"
|
||||||
:style="{ color: item.textColor ? item.textColor : '#1a1a1a' }" class="text-input"
|
:style="{ color: item.textColor ? item.textColor : '#1a1a1a' }" class="text-input"
|
||||||
:type="item.type" :focus="item.focus" v-model="item.value" @click.stop />
|
:type="item.type" :focus="item.focus" v-model="item.value" @click.stop />
|
||||||
|
</view>
|
||||||
|
<image v-if="item.type != 'link'" class="edit-image" src="/static/image/bill/add-bill/edit.png"
|
||||||
|
@click="onClickItemInfo(item)">
|
||||||
|
</image>
|
||||||
</view>
|
</view>
|
||||||
<image class="edit-image" src="/static/image/bill/add-bill/edit.png" @click="onClickItemInfo(item)">
|
<view v-if="item.type == 'link'" class="info-item-link">
|
||||||
</image>
|
<view class="img-box">
|
||||||
</view>
|
<image class="img w100 h100" :src="item.value.imgUrl || defaultImage"></image>
|
||||||
|
</view>
|
||||||
|
<view class="textarea-box flex-1">
|
||||||
|
<textarea class="textarea w100 h100" name="" v-model="item.value.text" id=""
|
||||||
|
placeholder="请输入交易商品名称"></textarea>
|
||||||
|
</view>
|
||||||
|
<view class="right-input-box flex-align-center">
|
||||||
|
<!-- <text class="right-text">共{{ item.value.number }}件</text> -->
|
||||||
|
<input class="right-text" type="text" v-model="item.value.quantity" @click.stop />
|
||||||
|
<image class="edit-image" src="/static/image/bill/add-bill/edit.png">
|
||||||
|
</image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- switch选项列表 -->
|
<!-- switch选项列表 -->
|
||||||
<view class="switch-option-container">
|
<view class="switch-option-container">
|
||||||
<template v-for="option in data.switchOptions" :key="option.id">
|
<template v-for="option in data.switchOptions" :key="option.id">
|
||||||
<view v-if="option.isShow()" class="border-bottom"
|
<view v-if="option.isShow()" class="border-bottom"
|
||||||
:class="{ 'no-border-bottom': billData.merchantOption[option.key] && option.isShow() }">
|
:class="{ 'no-border-bottom': isNoBorderBottom(option) }">
|
||||||
<view class="switch-option">
|
<view class="switch-option">
|
||||||
<view class="switch-option-text">{{ option.name }}</view>
|
<view class="switch-option-text">{{ option.name }}</view>
|
||||||
<switch v-if="option.isSwitch" color="#1676FE" :checked="billData.merchantOption[option.key]"
|
<switch v-if="option.isSwitch" color="#1676FE" :checked="billData.merchantOption[option.key]"
|
||||||
|
|
@ -78,7 +102,7 @@
|
||||||
<view class="service-detail"
|
<view class="service-detail"
|
||||||
v-if="option.key == 'serviceDetail' && billData.merchantOption.serviceDetail && option.isShow()">
|
v-if="option.key == 'serviceDetail' && billData.merchantOption.serviceDetail && option.isShow()">
|
||||||
<image class="service-detail-image"
|
<image class="service-detail-image"
|
||||||
src="/static/image/bill/add-bill/service-detail-image.png" />
|
:src="billData.merchantOption.serverDetailInfo.imgUrl || defaultImage" />
|
||||||
<view class="flex-1 over-hidden">
|
<view class="flex-1 over-hidden">
|
||||||
<view class="service-detail-info info-item-input">
|
<view class="service-detail-info info-item-input">
|
||||||
<!-- 隐藏的text用于测量宽度 -->
|
<!-- 隐藏的text用于测量宽度 -->
|
||||||
|
|
@ -191,7 +215,8 @@
|
||||||
import navBar from '@/components/nav-bar/nav-bar.vue'
|
import navBar from '@/components/nav-bar/nav-bar.vue'
|
||||||
import addBillJson from './add-bill.json'
|
import addBillJson from './add-bill.json'
|
||||||
import DateTimePicker from '@/components/dengrq-datetime-picker/dateTimePicker/index.vue';
|
import DateTimePicker from '@/components/dengrq-datetime-picker/dateTimePicker/index.vue';
|
||||||
import { stringUtil } from '@/utils/common.js';
|
import { stringUtil, randomUtil, util, uiUtil } from '@/utils/common.js';
|
||||||
|
import hotIcon from "@/static/json/hot-icon.json"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
reactive,
|
reactive,
|
||||||
|
|
@ -202,9 +227,11 @@ import {
|
||||||
nextTick
|
nextTick
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import {
|
import {
|
||||||
|
onLoad,
|
||||||
onShow,
|
onShow,
|
||||||
} from '@dcloudio/uni-app'
|
} from '@dcloudio/uni-app'
|
||||||
|
|
||||||
|
|
||||||
// 时间选择器
|
// 时间选择器
|
||||||
const timepopup = ref(null)
|
const timepopup = ref(null)
|
||||||
|
|
||||||
|
|
@ -308,17 +335,23 @@ const keySet = new Set()
|
||||||
classifyTabBar.value.forEach(item => {
|
classifyTabBar.value.forEach(item => {
|
||||||
if (item.itemInfoList) {
|
if (item.itemInfoList) {
|
||||||
item.itemInfoList.forEach(info => {
|
item.itemInfoList.forEach(info => {
|
||||||
|
if (info.key == "payMethod") {
|
||||||
|
info.value = "招商银行储蓄卡(0123)"
|
||||||
|
} else if (info.key == "tagAndNote") {
|
||||||
|
info.value = "添加"
|
||||||
|
}
|
||||||
if (info.key && !keySet.has(info.key)) {
|
if (info.key && !keySet.has(info.key)) {
|
||||||
keySet.add(info.key)
|
keySet.add(info.key)
|
||||||
allFieldsList.value.push({
|
allFieldsList.value.push({
|
||||||
key: info.key,
|
key: info.key,
|
||||||
value: info.value ? info.value : ""
|
value: info.value ? info.value : "",
|
||||||
|
type: info.type
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const defaultImage = "/static/image/bill/add-bill/add-avatar.png"
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
navBar: {
|
navBar: {
|
||||||
title: '新增账单',
|
title: '新增账单',
|
||||||
|
|
@ -339,11 +372,17 @@ const data = reactive({
|
||||||
startDate: "",
|
startDate: "",
|
||||||
endDate: "",
|
endDate: "",
|
||||||
},
|
},
|
||||||
|
storeData: {
|
||||||
|
imgUrl: "",
|
||||||
|
name: ""
|
||||||
|
},
|
||||||
// 账单数据
|
// 账单数据
|
||||||
billData: {
|
billData: {
|
||||||
id: "",
|
id: "",
|
||||||
selectId: -1,
|
selectId: -1,
|
||||||
|
imgUrl: "",
|
||||||
name: '',
|
name: '',
|
||||||
|
isAdd: false,
|
||||||
money: "",
|
money: "",
|
||||||
orderStatus: '交易成功',
|
orderStatus: '交易成功',
|
||||||
itemInfoList: classifyTabBar.value[0].itemInfoList,
|
itemInfoList: classifyTabBar.value[0].itemInfoList,
|
||||||
|
|
@ -376,12 +415,23 @@ let { billData, datePickerData, selectItemInfo } = toRefs(data)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
billData.value.selectId = 1
|
billData.value.selectId = 1
|
||||||
|
randomBillInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => { })
|
onShow(() => { })
|
||||||
|
|
||||||
|
onLoad((option) => {
|
||||||
|
console.log(option)
|
||||||
|
uni.$on('addBill', (res) => {
|
||||||
|
console.log("addBill", res);
|
||||||
|
billData.value.imgUrl = res.url
|
||||||
|
billData.value.name = res.name
|
||||||
|
console.log(billData.value)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
// 监听selectId
|
// 监听selectId
|
||||||
watch(() => billData.value.selectId, (newVal) => {
|
watch(() => billData.value.selectId, (newVal, oldVal) => {
|
||||||
// 储存到全部字段的值中
|
// 储存到全部字段的值中
|
||||||
allFieldsList.value.forEach(item => {
|
allFieldsList.value.forEach(item => {
|
||||||
billData.value.itemInfoList.forEach(oldItem => {
|
billData.value.itemInfoList.forEach(oldItem => {
|
||||||
|
|
@ -402,7 +452,7 @@ watch(() => billData.value.selectId, (newVal) => {
|
||||||
currentItemInfoList.forEach(item => {
|
currentItemInfoList.forEach(item => {
|
||||||
allFieldsList.value.forEach(oldItem => {
|
allFieldsList.value.forEach(oldItem => {
|
||||||
if (oldItem.key == item.key) {
|
if (oldItem.key == item.key) {
|
||||||
item.value = oldItem.value
|
item.value = item.value || oldItem.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -420,7 +470,19 @@ watch(() => billData.value.selectId, (newVal) => {
|
||||||
billData.value.bottomIcons = classifyTabBar.value.find(item => item.selectId == newVal).defaultBottomIcons
|
billData.value.bottomIcons = classifyTabBar.value.find(item => item.selectId == newVal).defaultBottomIcons
|
||||||
console.log("当前分类的底部字段", billData.value.bottomIcons)
|
console.log("当前分类的底部字段", billData.value.bottomIcons)
|
||||||
|
|
||||||
|
// 获取当前选中项
|
||||||
|
const currentItem = classifyTabBar.value.find(item => item.selectId == newVal)
|
||||||
|
|
||||||
|
// 设置当前分类的是否为收入
|
||||||
|
billData.value.isAdd = currentItem.isAdd
|
||||||
|
|
||||||
|
// 设置名称和头像
|
||||||
|
billData.value.name = currentItem.name || data.storeData.name
|
||||||
|
if (newVal == 10 || newVal == 11) {
|
||||||
|
billData.value.imgUrl = currentItem.imageUrl
|
||||||
|
} else {
|
||||||
|
billData.value.imgUrl = currentItem.imageUrl || data.storeData.imgUrl
|
||||||
|
}
|
||||||
setItemInfoValue(newVal)
|
setItemInfoValue(newVal)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -428,73 +490,120 @@ watch(() => billData.value.selectId, (newVal) => {
|
||||||
* 设置每个字段信息值
|
* 设置每个字段信息值
|
||||||
*/
|
*/
|
||||||
const setItemInfoValue = (id) => {
|
const setItemInfoValue = (id) => {
|
||||||
|
// 设置当前分类的特定字段值
|
||||||
billData.value.itemInfoList.forEach(item => {
|
billData.value.itemInfoList.forEach(item => {
|
||||||
|
if (item.key == 'orderNumber') {
|
||||||
|
item.value = randomUtil.randomOrderNumber(28)
|
||||||
|
} else if (item.type == 'time') {
|
||||||
|
item.value = randomUtil.randomTime()
|
||||||
|
console.log("item.type == 'time'", item.value)
|
||||||
|
} else if (item.key == 'householdNumber') {
|
||||||
|
item.value = randomUtil.randomOrderNumber(10)
|
||||||
|
} else if (item.key == 'receiverFullName') {
|
||||||
|
item.value = `${billData.value.name}有限公司`
|
||||||
|
} else if (item.key == 'productDescription') {
|
||||||
|
item.value = `xxxxxxx消费`
|
||||||
|
}
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 1:
|
|
||||||
if (item.key == 'receiverFullName') {
|
|
||||||
item.value = `${billData.value.name}有限公司`
|
|
||||||
} else if (item.key == 'productDescription') {
|
|
||||||
item.value = `xxxxxxx消费`
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
if (item.key == 'productDescription') {
|
if (item.key == 'receiverFullName') {
|
||||||
item.value = `收钱码收款`
|
item.value = `${billData.value.name}(个人)`
|
||||||
} else if (item.key == 'receiverFullName') {
|
|
||||||
item.value = `***(个人)`
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
|
||||||
if (item.key == 'productDescription') {
|
|
||||||
item.value = `xxxxxxx消费`
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
break;
|
|
||||||
case 5:
|
case 5:
|
||||||
if (item.key == 'transferNote') {
|
if (item.key == 'counterAccount') {
|
||||||
item.value = `报销`
|
|
||||||
} else if (item.key == 'counterAccount') {
|
|
||||||
item.value = `${billData.value.name}`
|
item.value = `${billData.value.name}`
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
if (item.key == 'transferNote') {
|
if (item.key == 'counterAccount' && item.value == '') {
|
||||||
item.value = `转账`
|
|
||||||
} else if (item.key == 'counterAccount') {
|
|
||||||
item.value = `xxx123654789`
|
item.value = `xxx123654789`
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
break;
|
if (item.key == 'counterAccount' && item.value == '') {
|
||||||
case 8:
|
item.value = `xxx123654789`
|
||||||
if (item.key == 'relatedRecord') {
|
|
||||||
item.value = `查看关联记录`
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 9:
|
|
||||||
if (item.key == 'relatedRecord') {
|
|
||||||
item.value = `查看原账单`
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换头像
|
||||||
|
*/
|
||||||
|
const changeAvatar = () => {
|
||||||
|
util.goPage("/pages/common/hot-icon/hot-icon" + "?page=addBill")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成账单信息
|
||||||
|
*/
|
||||||
|
const randomBillInfo = () => {
|
||||||
|
// 随机生成金额
|
||||||
|
billData.value.money = randomUtil.randomMoney()
|
||||||
|
console.log("allFieldsList", allFieldsList.value)
|
||||||
|
// 随机抽取头像
|
||||||
|
const hotIconList = hotIcon.moneyHotIcon.filter(item => item.classify != '系统图标' && item.classify != '自定义' && item.classify != '银行卡')
|
||||||
|
if (hotIconList.length > 0) {
|
||||||
|
const randomIcon = hotIconList[randomUtil.random(0, hotIconList.length - 1)]
|
||||||
|
billData.value.imgUrl = `/static/image/common/hot-icon/${randomIcon.label}.png`
|
||||||
|
billData.value.name = randomIcon.name
|
||||||
|
}
|
||||||
|
|
||||||
|
data.storeData = {
|
||||||
|
imgUrl: billData.value.imgUrl,
|
||||||
|
name: billData.value.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机骰子生成数据
|
||||||
|
billData.value.itemInfoList.forEach(item => {
|
||||||
|
if (item.key == 'orderNumber') {
|
||||||
|
item.value = randomUtil.randomOrderNumber(28)
|
||||||
|
} else if (item.type == 'time') {
|
||||||
|
item.value = randomUtil.randomTime()
|
||||||
|
console.log("item.type == 'time'", item.value)
|
||||||
|
} else if (item.key == 'householdNumber') {
|
||||||
|
item.value = randomUtil.randomOrderNumber(10)
|
||||||
|
} else if (item.key == 'receiverFullName') {
|
||||||
|
item.value = `${billData.value.name}有限公司`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("随机生成账单信息", billData.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验表单数据
|
||||||
|
*/
|
||||||
|
const validateBillData = () => {
|
||||||
|
if (!billData.value.name) {
|
||||||
|
uiUtil.showError("请输入名称")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!billData.value.money) {
|
||||||
|
uiUtil.showError("请输入金额")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 校验itemInfoList
|
||||||
|
for (let i = 0; i < billData.value.itemInfoList.length; i++) {
|
||||||
|
const item = billData.value.itemInfoList[i]
|
||||||
|
if (item.required && !item.value) {
|
||||||
|
uiUtil.showError(`请输入${item.label}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存账单
|
* 保存账单
|
||||||
*/
|
*/
|
||||||
const onRightClick = () => {
|
const onRightClick = () => {
|
||||||
|
if (!validateBillData()) return
|
||||||
billData.value.id = stringUtil.uuid()
|
billData.value.id = stringUtil.uuid()
|
||||||
console.log("保存", billData.value)
|
console.log("保存", billData.value)
|
||||||
}
|
}
|
||||||
|
|
@ -543,6 +652,14 @@ const onSwitchChange = (e, option) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否需要移除下边框
|
||||||
|
*/
|
||||||
|
const isNoBorderBottom = (option) => {
|
||||||
|
const keys = ['serviceDetail', 'recommendService', 'serviceRecommend'];
|
||||||
|
return billData.value.merchantOption[option.key] && keys.includes(option.key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化时间
|
* 格式化时间
|
||||||
*/
|
*/
|
||||||
|
|
@ -618,6 +735,14 @@ page {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 12rpx 24rpx;
|
padding: 12rpx 24rpx;
|
||||||
|
|
@ -664,11 +789,13 @@ page {
|
||||||
.avatar-image {
|
.avatar-image {
|
||||||
width: 80rpx;
|
width: 80rpx;
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-info {
|
.main-info {
|
||||||
color: var(--text-color);
|
color: #1a1a1a;
|
||||||
|
font-size: 64rpx;
|
||||||
|
|
||||||
.order-status-info {
|
.order-status-info {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
|
|
@ -694,6 +821,41 @@ page {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-item-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background-color: #F6F7FB;
|
||||||
|
border-radius: 20rpx 20rpx 20rpx 20rpx;
|
||||||
|
|
||||||
|
.img-box {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-right: 22rpx;
|
||||||
|
background-color: #E8EDF2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea-box {
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-input-box {
|
||||||
|
.right-text {
|
||||||
|
color: #969696;
|
||||||
|
font-size: 22rpx;
|
||||||
|
max-width: 60rpx;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item-box {
|
.info-item-box {
|
||||||
|
|
@ -751,7 +913,7 @@ page {
|
||||||
}
|
}
|
||||||
|
|
||||||
.money {
|
.money {
|
||||||
margin-top: 14rpx;
|
// margin-top: 14rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-option-container {
|
.switch-option-container {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,369 @@
|
||||||
|
<template>
|
||||||
|
<view class="container" v-if="!isOpenCropper">
|
||||||
|
<!-- 自定义头部导航栏 -->
|
||||||
|
<navBar :title="data.navbar.title" :bgColor="data.navbar.bgColor" :isBack="true" isRightButton
|
||||||
|
@right-click="submitBtn">
|
||||||
|
</navBar>
|
||||||
|
<view class="icon-box">
|
||||||
|
<view class="list group" v-for="(group, index) in data.showHotListIcon" :key="index">
|
||||||
|
<view class="title">
|
||||||
|
{{ group.classify }}
|
||||||
|
</view>
|
||||||
|
<view class="title-box"></view>
|
||||||
|
<view class="item" v-for="(item, idx) in group.data" :key="idx"
|
||||||
|
:class="{ active: data.activeId === item.selectId }" @click="onSelect(item)">
|
||||||
|
<image class="" :src="item.url" mode=""></image>
|
||||||
|
<view class="" v-if="item.name">
|
||||||
|
{{ item.name }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<view v-if="isOpenCropper">
|
||||||
|
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 自定义头部
|
||||||
|
import navBar from '@/components/nav-bar/nav-bar.vue'
|
||||||
|
import hotIcon from "@/static/json/hot-icon.json"
|
||||||
|
|
||||||
|
import {
|
||||||
|
reactive,
|
||||||
|
toRefs
|
||||||
|
} from "vue";
|
||||||
|
import {
|
||||||
|
onLoad,
|
||||||
|
onShow,
|
||||||
|
onReady,
|
||||||
|
onPullDownRefresh,
|
||||||
|
onReachBottom
|
||||||
|
} from "@dcloudio/uni-app";
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
navbar: {
|
||||||
|
title: "热门图标",
|
||||||
|
bgColor: '#EDEDED',
|
||||||
|
},
|
||||||
|
isOpenCropper: false,
|
||||||
|
activeId: '',
|
||||||
|
hotIconList: [],
|
||||||
|
fromPage: "addBill",
|
||||||
|
activeData: "",
|
||||||
|
showHotListIcon: []
|
||||||
|
})
|
||||||
|
|
||||||
|
let {
|
||||||
|
isOpenCropper
|
||||||
|
} = toRefs(data)
|
||||||
|
|
||||||
|
|
||||||
|
onLoad((option) => {
|
||||||
|
if (option.page) {
|
||||||
|
data.fromPage = option.page
|
||||||
|
}
|
||||||
|
data.hotIconList = []
|
||||||
|
console.log(data.hotIconList, hotIcon.moneyHotIcon)
|
||||||
|
hotIcon.moneyHotIcon.forEach((i, index) => {
|
||||||
|
data.hotIconList.push({
|
||||||
|
selectId: index,
|
||||||
|
name: i.name,
|
||||||
|
url: `/static/image/common/hot-icon/${i.label}.png`,
|
||||||
|
isUpLoad: i.isUpLoad,
|
||||||
|
classify: i.classify
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log(data.hotIconList)
|
||||||
|
getImage()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} item
|
||||||
|
* 选择图片
|
||||||
|
*/
|
||||||
|
function onSelect(item) {
|
||||||
|
if (item.isUpLoad) {
|
||||||
|
data.activeId = ''
|
||||||
|
data.activeData = ''
|
||||||
|
isOpenCropper.value = true
|
||||||
|
// chooseImage()
|
||||||
|
} else {
|
||||||
|
console.log(item);
|
||||||
|
data.activeId = item.selectId
|
||||||
|
data.activeData = item
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} e
|
||||||
|
* 图片裁剪完成
|
||||||
|
*/
|
||||||
|
async function handleCrop(e) {
|
||||||
|
|
||||||
|
console.log(e);
|
||||||
|
const savedFilePath = await saveAndDisplayImage(e.tempFilePath)
|
||||||
|
let arr = [{
|
||||||
|
selectId: generateAlphanumericCode(10),
|
||||||
|
name: "",
|
||||||
|
url: savedFilePath,
|
||||||
|
isUpLoad: false,
|
||||||
|
classify: "自定义"
|
||||||
|
}]
|
||||||
|
console.log("图片储存成功", arr)
|
||||||
|
// 合并数组
|
||||||
|
data.hotIconList = insertAfterFirstInPlace(data.hotIconList, arr);
|
||||||
|
await addImg(arr[0])
|
||||||
|
formatJson()
|
||||||
|
isOpenCropper.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const formatJson = () => {
|
||||||
|
const groupedMap = new Map();
|
||||||
|
data.hotIconList.forEach(item => {
|
||||||
|
if (!groupedMap.has(item.classify)) {
|
||||||
|
groupedMap.set(item.classify, {
|
||||||
|
classify: item.classify,
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
groupedMap.get(item.classify).data.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = Array.from(groupedMap.values()).map((group, index) => {
|
||||||
|
return {
|
||||||
|
classify: group.classify,
|
||||||
|
data: group.data.map((item, idx) => {
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
data.showHotListIcon = list
|
||||||
|
console.log("分组后的json", list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取图片
|
||||||
|
*/
|
||||||
|
const getImage = () => {
|
||||||
|
//获取图片列表
|
||||||
|
// console.log("图片获取成功", res)
|
||||||
|
// const imgArr = res
|
||||||
|
// console.log(imgArr)
|
||||||
|
// imgArr.forEach(i => {
|
||||||
|
// i.selectId = generateAlphanumericCode()
|
||||||
|
// i.classify = i.classify ? i.classify : '自定义'
|
||||||
|
// })
|
||||||
|
// insertAfterFirstInPlace(data.hotIconList, imgArr);
|
||||||
|
formatJson()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成字母数字混合编码
|
||||||
|
function generateAlphanumericCode(length = 8) {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
return Array.from({
|
||||||
|
length
|
||||||
|
}, () =>
|
||||||
|
chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增图片
|
||||||
|
*/
|
||||||
|
const addImg = (data) => {
|
||||||
|
// //增加数据
|
||||||
|
// let sql = `INSERT INTO image (url,type,name) VALUES ('${data.url}','moneyHotIcon','${data.name}')`;
|
||||||
|
// proxy.$sqliteApi.addTable(sql).then((res) => {
|
||||||
|
// console.log("图片添加成功", res)
|
||||||
|
// }).catch(e => {
|
||||||
|
// console.log("图片添加失败", e)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从本地选择图片
|
||||||
|
*/
|
||||||
|
function chooseImage() {
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 9, // 最多可选择9张图片
|
||||||
|
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图
|
||||||
|
sourceType: ['album'], // 从相册选择
|
||||||
|
success: (res) => {
|
||||||
|
res.tempFilePaths.forEach(async item => {
|
||||||
|
const savedFilePath = await saveAndDisplayImage(item)
|
||||||
|
let arr = [{
|
||||||
|
selectId: generateAlphanumericCode(10),
|
||||||
|
name: "",
|
||||||
|
url: savedFilePath,
|
||||||
|
isUpLoad: item.isUpLoad
|
||||||
|
}]
|
||||||
|
console.log("图片储存成功", arr)
|
||||||
|
// 合并数组
|
||||||
|
data.hotIconList = insertAfterFirstInPlace(data.hotIconList, arr);
|
||||||
|
formatJson()
|
||||||
|
addImg(arr[0])
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('选择图片失败:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: '选择图片失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} file
|
||||||
|
* 保存图片到本地
|
||||||
|
*/
|
||||||
|
async function saveAndDisplayImage(file) {
|
||||||
|
try {
|
||||||
|
// 这里是你保存图片的逻辑
|
||||||
|
const {
|
||||||
|
savedFilePath
|
||||||
|
} = await uni.saveFile({
|
||||||
|
tempFilePath: file
|
||||||
|
})
|
||||||
|
return savedFilePath
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存图片失败:', error)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onReady(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
onShow(() => { })
|
||||||
|
|
||||||
|
|
||||||
|
onPullDownRefresh(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
onReachBottom(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
function insertAfterFirstInPlace(mainArray, newArray) {
|
||||||
|
console.log("mainArray", mainArray)
|
||||||
|
console.log("newArray", newArray)
|
||||||
|
mainArray.splice(1, 0, ...newArray);
|
||||||
|
return mainArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitBtn() {
|
||||||
|
if (data.activeId == '') {
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: "请选择图标"
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log("来自页面", data.fromPage);
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1,
|
||||||
|
success: () => {
|
||||||
|
// 延迟确保目标页面已准备好
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.$emit(data.fromPage, data.activeData);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding-bottom: calc(8rpx + constant(safe-area-inset-bottom)); // 兼容 IOS<11.2
|
||||||
|
padding-bottom: calc(8rpx + env(safe-area-inset-bottom)); // 兼容 IOS>11.2
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 12px 0;
|
||||||
|
padding-top: 2px;
|
||||||
|
margin: 0 24rpx;
|
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 0 2px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background-color: #fff;
|
||||||
|
width: calc(25% - 20px);
|
||||||
|
height: 75px;
|
||||||
|
border-radius: 7px 7px 7px 7px;
|
||||||
|
border: 1px solid #E8E8E8;
|
||||||
|
margin: 9px 10px;
|
||||||
|
// margin-top: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #1A1A1A;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
margin-top: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background: #BCEDD3;
|
||||||
|
border: 1px solid #07C160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: absolute;
|
||||||
|
font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #2C2C2C;
|
||||||
|
z-index: 2;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
width: 41px;
|
||||||
|
height: 8px;
|
||||||
|
left: 6px;
|
||||||
|
background: linear-gradient(90deg, #9AC8FF 0%, rgba(42, 255, 195, 0) 67.86%);
|
||||||
|
border-radius: 10px 10px 10px 10px;
|
||||||
|
z-index: 0;
|
||||||
|
transform: rotate(345deg);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 922 B |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -0,0 +1,413 @@
|
||||||
|
{
|
||||||
|
"moneyHotIcon": [
|
||||||
|
{
|
||||||
|
"name": "本地上传",
|
||||||
|
"label": "add",
|
||||||
|
"isUpLoad": true,
|
||||||
|
"classify": "自定义"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "默认",
|
||||||
|
"label": "default",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "商品",
|
||||||
|
"label": "shangpin",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "花呗",
|
||||||
|
"label": "huabei",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "余额宝",
|
||||||
|
"label": "yuebao",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "退款",
|
||||||
|
"label": "tuikuan",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "燃气费",
|
||||||
|
"label": "ranqifei",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "电费",
|
||||||
|
"label": "dianfei",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "水费",
|
||||||
|
"label": "shuifei",
|
||||||
|
"classify": "系统图标"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "古茗",
|
||||||
|
"label": "guming",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "一点点",
|
||||||
|
"label": "yidiandian",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "蜜雪冰城",
|
||||||
|
"label": "mixuebingcheng",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "茶百道",
|
||||||
|
"label": "chabaidao",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "星巴克",
|
||||||
|
"label": "xingbake",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "瑞幸咖啡",
|
||||||
|
"label": "ruixingkafei",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "奈雪的茶",
|
||||||
|
"label": "naixuedecha",
|
||||||
|
"classify": "奶茶饮品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "麦当劳",
|
||||||
|
"label": "maidanglao",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "塔斯汀",
|
||||||
|
"label": "tasiting",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "肯德基",
|
||||||
|
"label": "kendeji",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "华莱士",
|
||||||
|
"label": "hualaishi",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "必胜客",
|
||||||
|
"label": "bishengke",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "汉堡王",
|
||||||
|
"label": "hanbaowang",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "海底捞",
|
||||||
|
"label": "haidilao",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "正新鸡排",
|
||||||
|
"label": "zhengxinjipai",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "米村拌饭",
|
||||||
|
"label": "micunbanfan",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "煎饼道",
|
||||||
|
"label": "jianbingdao",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "周黑鸭",
|
||||||
|
"label": "zhouheiya",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "沙县小吃",
|
||||||
|
"label": "shaxianxiaochi",
|
||||||
|
"classify": "美食"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "淘宝",
|
||||||
|
"label": "taobao",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "抖音商城",
|
||||||
|
"label": "douyinshangcheng",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "拼多多",
|
||||||
|
"label": "pinduoduo",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "京东",
|
||||||
|
"label": "jingdong",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "饿了么",
|
||||||
|
"label": "eleme",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "闲鱼",
|
||||||
|
"label": "xianyu",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "转转",
|
||||||
|
"label": "zhuanzhuan",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "阿里巴巴",
|
||||||
|
"label": "1688",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "美团",
|
||||||
|
"label": "meituan",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "美团外卖",
|
||||||
|
"label": "meituanwaimai",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "大众点评",
|
||||||
|
"label": "dazhongdianping",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "唯品会",
|
||||||
|
"label": "weipinhui",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "天猫",
|
||||||
|
"label": "tianmao",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "盒马鲜生",
|
||||||
|
"label": "hemaxiansheng",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "百果园",
|
||||||
|
"label": "baiguoyuan",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "711",
|
||||||
|
"label": "711",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "罗森",
|
||||||
|
"label": "luosen",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "卖德龙",
|
||||||
|
"label": "maidelong",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "永辉超市",
|
||||||
|
"label": "yonghuichaoshi",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "叮咚买菜",
|
||||||
|
"label": "dingdongmaicai",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "山姆超市",
|
||||||
|
"label": "shanmuchaoshi",
|
||||||
|
"classify": "购物"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "高德地图",
|
||||||
|
"label": "gaodeditu",
|
||||||
|
"classify": "出行"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "哈啰",
|
||||||
|
"label": "haluo",
|
||||||
|
"classify": "出行"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "滴滴出行",
|
||||||
|
"label": "didichuxing",
|
||||||
|
"classify": "出行"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "花小猪",
|
||||||
|
"label": "huaxiaozhu",
|
||||||
|
"classify": "出行"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "猫眼电影",
|
||||||
|
"label": "maoyandianying",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "淘票票",
|
||||||
|
"label": "taopiaopiao",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "电竞网吧",
|
||||||
|
"label": "dianjingwangba",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "虎牙直播",
|
||||||
|
"label": "huyazhibo",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "王者荣耀",
|
||||||
|
"label": "wangzherongyao",
|
||||||
|
"classify": "游戏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "金铲铲",
|
||||||
|
"label": "jinchanchan",
|
||||||
|
"classify": "游戏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "原神",
|
||||||
|
"label": "yuanshen",
|
||||||
|
"classify": "游戏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "燕云十六声",
|
||||||
|
"label": "yanyunshiliusheng",
|
||||||
|
"classify": "游戏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "无畏契约",
|
||||||
|
"label": "wuweiqiyue",
|
||||||
|
"classify": "游戏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "三角洲行动",
|
||||||
|
"label": "sanjiaozhou",
|
||||||
|
"classify": "游戏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "哔哩哔哩",
|
||||||
|
"label": "bilibili",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "腾讯视频",
|
||||||
|
"label": "tengxunshipin",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "爱奇艺",
|
||||||
|
"label": "aiqiyi",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "优酷",
|
||||||
|
"label": "youku",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "芒果TV",
|
||||||
|
"label": "mangguoTV",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "抖音",
|
||||||
|
"label": "douyin",
|
||||||
|
"classify": "娱乐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "招商银行",
|
||||||
|
"label": "zhaoshangyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "建设银行",
|
||||||
|
"label": "jiansheyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "工商银行",
|
||||||
|
"label": "gongshangyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "农业银行",
|
||||||
|
"label": "nongyeyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "储蓄银行",
|
||||||
|
"label": "chuxvyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "交通银行",
|
||||||
|
"label": "jiaotongyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "中国银行",
|
||||||
|
"label": "zhongguoyinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "浦发银行",
|
||||||
|
"label": "pufayinhang",
|
||||||
|
"classify": "银行卡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "云上贵州",
|
||||||
|
"label": "yunshangguizhou",
|
||||||
|
"classify": "其它"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"IncomeBillItem": [
|
||||||
|
{
|
||||||
|
"name": "微信红包",
|
||||||
|
"label": "hongbao"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "退款",
|
||||||
|
"label": "tuikuan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二维码收款",
|
||||||
|
"label": "qianbao"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "转账",
|
||||||
|
"label": "zhuanzhang"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
## 2.2.5(2024-07-30)
|
||||||
|
* 修复 当 checkRange=true 时,拖动四个伸缩角放大图片时还可能会超出或未到边界的问题
|
||||||
|
* 修复 当 checkRange=false 时,图片旋转时会放大图片适应裁剪尺寸的问题
|
||||||
|
* 修复 当 checkRange=true 时,图片旋转 90° 或 270° 进行缩放可能会无法拖动图片的问题
|
||||||
|
## 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,855 @@
|
||||||
|
/**
|
||||||
|
* 图片编辑器-手势监听
|
||||||
|
* 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
|
||||||
|
/** 容错值 */
|
||||||
|
var fault = 0.000001;
|
||||||
|
/**
|
||||||
|
* 获取a、b两数中的最小正数
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function minimum(a, b) {
|
||||||
|
if (a > 0 && b < 0) return a;
|
||||||
|
if (a < 0 && b > 0) return b;
|
||||||
|
if (a > 0 && b > 0) return Math.min(a, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在容错访问内获取n近似值
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
function num(n) {
|
||||||
|
var m = parseFloat((n).toFixed(6));
|
||||||
|
return m === fault || m === -fault ? 0 : m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否等于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalsByFault(a, b) {
|
||||||
|
return Math.abs(a - b) <= fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否小于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function lessThanByFault(a, b) {
|
||||||
|
var c = a - b;
|
||||||
|
return c < 0 ? c < -fault : c < fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证并获取有效最大值
|
||||||
|
* @param v
|
||||||
|
* @param max
|
||||||
|
* @param isInclude
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param rate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validMax(v, max, isInclude, x, y, rate) {
|
||||||
|
if(typeof max === 'number') {
|
||||||
|
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||||
|
var n = num(max * rate);
|
||||||
|
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||||
|
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 样式对象转字符串
|
||||||
|
* @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 num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
* @param {Object} xReverse x是否反向
|
||||||
|
* @param {Object} yReverse y是否反向
|
||||||
|
*/
|
||||||
|
function checkRotateRange(e, xReverse, yReverse) {
|
||||||
|
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||||
|
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
*/
|
||||||
|
function checkRange(e) {
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||||
|
if (area.width === area.height) {
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||||
|
if (img.width < area.height || img.height < area.width) {
|
||||||
|
if (area.width < area.height && img.width < img.height) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||||
|
: checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
if (area.height < area.width && img.height < img.width) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||||
|
: checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height >= area.width && img.width >= area.height) {
|
||||||
|
return checkRotateRange(e, false, false);
|
||||||
|
}
|
||||||
|
if (isInclude) {
|
||||||
|
return area.height < area.width
|
||||||
|
? checkRotateRange(e, true, true)
|
||||||
|
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||||
|
}
|
||||||
|
if (img.height < area.width && !img.width < area.height) {
|
||||||
|
return checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
if (!img.height < area.width && img.width < area.height) {
|
||||||
|
return checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
return checkRotateRange(e, img.height < area.height, 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 = num(img.oldWidth * scale);
|
||||||
|
img.height = num(img.oldHeight * scale);
|
||||||
|
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||||
|
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||||
|
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||||
|
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||||
|
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||||
|
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||||
|
e.y = num((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 = num(area.width * (1 - area.minScale));
|
||||||
|
var maxY = num(area.height * (1 - area.minScale));
|
||||||
|
// console.log(x, y, maxX, maxY, offset, area)
|
||||||
|
touches[0] = point;
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||||
|
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||||
|
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||||
|
var isInclude = xCompare && yCompare;
|
||||||
|
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||||
|
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||||
|
switch(activeAngle) {
|
||||||
|
case 1: // 左上角
|
||||||
|
x = num(x + areaOffset.left);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x >= 0 && y >= 0) { // 有效滑动
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // 右上角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x <= 0 && y >= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // 左下角
|
||||||
|
x += num(x + areaOffset.left);
|
||||||
|
y += num(y + areaOffset.bottom);
|
||||||
|
if(x >= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - m - offset.y - w);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // 右下角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.bottom);
|
||||||
|
if(x <= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var h = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - offset.y - h - m);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(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 && area.checkRange) {
|
||||||
|
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||||
|
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,743 @@
|
||||||
|
<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.toFixed(2);
|
||||||
|
this.oldHeight = +this.scaleHeight.toFixed(2);
|
||||||
|
},
|
||||||
|
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: $uni-bg-color-grey;
|
||||||
|
|
||||||
|
.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: $uni-color-primary;
|
||||||
|
padding: 0 $uni-spacing-row-lg;
|
||||||
|
line-height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-btn {
|
||||||
|
color: $uni-color-primary;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100rpx;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: auto $uni-spacing-row-lg auto auto;
|
||||||
|
background-color: $uni-color-primary;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,727 @@
|
||||||
|
/**
|
||||||
|
* 图片编辑器-手势监听
|
||||||
|
* 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 };
|
||||||
|
/** 容错值 */
|
||||||
|
var fault = 0.000001;
|
||||||
|
/**
|
||||||
|
* 获取a、b两数中的最小正数
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function minimum(a, b) {
|
||||||
|
if (a > 0 && b < 0) return a;
|
||||||
|
if (a < 0 && b > 0) return b;
|
||||||
|
if (a > 0 && b > 0) return Math.min(a, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在容错访问内获取n近似值
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
function num(n) {
|
||||||
|
var m = parseFloat((n).toFixed(6));
|
||||||
|
return m === fault || m === -fault ? 0 : m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否等于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalsByFault(a, b) {
|
||||||
|
return Math.abs(a - b) <= fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否小于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function lessThanByFault(a, b) {
|
||||||
|
var c = a - b;
|
||||||
|
return c < 0 ? c < -fault : c < fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证并获取有效最大值
|
||||||
|
* @param v
|
||||||
|
* @param max
|
||||||
|
* @param isInclude
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param rate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validMax(v, max, isInclude, x, y, rate) {
|
||||||
|
if(typeof max === 'number') {
|
||||||
|
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||||
|
var n = num(max * rate);
|
||||||
|
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||||
|
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 计算两点间距
|
||||||
|
* @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 num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
* @param {Object} xReverse x是否反向
|
||||||
|
* @param {Object} yReverse y是否反向
|
||||||
|
*/
|
||||||
|
function checkRotateRange(e, xReverse, yReverse) {
|
||||||
|
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||||
|
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
*/
|
||||||
|
function checkRange(e) {
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||||
|
if (area.width === area.height) {
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||||
|
if (img.width < area.height || img.height < area.width) {
|
||||||
|
if (area.width < area.height && img.width < img.height) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||||
|
: checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
if (area.height < area.width && img.height < img.width) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||||
|
: checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height >= area.width && img.width >= area.height) {
|
||||||
|
return checkRotateRange(e, false, false);
|
||||||
|
}
|
||||||
|
if (isInclude) {
|
||||||
|
return area.height < area.width
|
||||||
|
? checkRotateRange(e, true, true)
|
||||||
|
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||||
|
}
|
||||||
|
if (img.height < area.width && !img.width < area.height) {
|
||||||
|
return checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
if (!img.height < area.width && img.width < area.height) {
|
||||||
|
return checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
return checkRotateRange(e, img.height < area.height, 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 = num(img.oldWidth * scale);
|
||||||
|
img.height = num(img.oldHeight * scale);
|
||||||
|
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||||
|
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||||
|
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||||
|
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||||
|
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||||
|
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||||
|
e.y = num((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 && area.checkRange) {
|
||||||
|
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||||
|
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 = num(area.width * (1 - area.minScale));
|
||||||
|
var maxY = num(area.height * (1 - area.minScale));
|
||||||
|
// console.log(x, y, maxX, maxY, offset, area)
|
||||||
|
touches[0] = point;
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||||
|
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||||
|
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||||
|
var isInclude = xCompare && yCompare;
|
||||||
|
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||||
|
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||||
|
switch(activeAngle) {
|
||||||
|
case 1: // 左上角
|
||||||
|
x = num(x + areaOffset.left);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x >= 0 && y >= 0) { // 有效滑动
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // 右上角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x <= 0 && y >= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||||
|
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
|
||||||
|
// : false;
|
||||||
|
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
|
||||||
|
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
|
||||||
|
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // 左下角
|
||||||
|
x += num(x + areaOffset.left);
|
||||||
|
y += num(y + areaOffset.bottom);
|
||||||
|
if(x >= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - m - offset.y - w);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // 右下角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.bottom);
|
||||||
|
if(x <= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var h = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - offset.y - h - m);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(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: '',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
"id": "qf-image-cropper",
|
||||||
|
"displayName": "图片裁剪插件",
|
||||||
|
"version": "2.2.5",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
# 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域名白名单才能生效。
|
||||||
|
5.如果组件无法正常渲染且使用了 `v-if` 时,可尝试将 `v-if` 替换为 `v-show`
|
||||||
|
6.如果App端导入组件后无法正常渲染,请尝试重新运行
|
||||||
|
|
@ -403,12 +403,38 @@ export const randomUtil = {
|
||||||
* @returns {string} 格式化后的时间
|
* @returns {string} 格式化后的时间
|
||||||
*/
|
*/
|
||||||
randomTime(startTime, endTime, format = 'YYYY-MM-DD HH:mm:ss') {
|
randomTime(startTime, endTime, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
const start = new Date(startTime).getTime();
|
let end;
|
||||||
const end = new Date(endTime).getTime();
|
if (endTime) {
|
||||||
|
end = new Date(endTime).getTime();
|
||||||
|
} else {
|
||||||
|
end = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
let start;
|
||||||
|
if (startTime) {
|
||||||
|
start = new Date(startTime).getTime();
|
||||||
|
} else {
|
||||||
|
// 默认为结束时间往前推3个月
|
||||||
|
const date = new Date(end);
|
||||||
|
date.setMonth(date.getMonth() - 3);
|
||||||
|
start = date.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
const randomTimestamp = Math.floor(Math.random() * (end - start + 1)) + start;
|
const randomTimestamp = Math.floor(Math.random() * (end - start + 1)) + start;
|
||||||
return dateUtil.format(randomTimestamp, format);
|
return dateUtil.format(randomTimestamp, format);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机金额
|
||||||
|
* @param {number} min - 最小值
|
||||||
|
* @param {number} max - 最大值
|
||||||
|
* @returns {string} 随机金额
|
||||||
|
*/
|
||||||
|
randomMoney(min = 0, max = 5000) {
|
||||||
|
const num = Math.random() * (max - min) + min;
|
||||||
|
return num.toFixed(2);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 随机生成指定位数订单号
|
* 随机生成指定位数订单号
|
||||||
* @param {number} length - 订单号位数
|
* @param {number} length - 订单号位数
|
||||||
|
|
|
||||||