mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
synced 2026-08-06 02:47:25 +08:00
refactor: 重构多个组件以支持 Vue 3 语法和功能
- 将多个组件转换为 `<script setup>` 语法,提升可读性和性能 - 优化状态管理和事件处理逻辑,简化代码结构 - 更新样式和布局以适应新组件结构 - 添加新功能和修复已知问题,提升用户体验
This commit is contained in:
@@ -10,40 +10,30 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 购物车 cart 消息 msg 订单 order 查询 search
|
||||
type: {
|
||||
type: String,
|
||||
default: 'search'
|
||||
},
|
||||
isBtn:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title:{
|
||||
type: String,
|
||||
default: '没有相关内容'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
src:''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.src ='/static/default/default_'+ this.type + '.png';
|
||||
},
|
||||
methods: {
|
||||
toHome() {
|
||||
uni.switchTab({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
type?: string
|
||||
isBtn?: boolean
|
||||
title?: string
|
||||
}>(), {
|
||||
type: 'search',
|
||||
isBtn: false,
|
||||
title: '没有相关内容'
|
||||
})
|
||||
|
||||
const src = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
src.value = `/static/default/default_${props.type}.png`
|
||||
})
|
||||
|
||||
const toHome = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/home/home'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -13,72 +13,62 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/config";
|
||||
import logoImg from "@/icon.png";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config, // 设置工具类
|
||||
weChat: false, // 是否微信浏览器,该项为true时不显示 当前整个页面
|
||||
logo: logoImg, //显示的圆形logo
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5
|
||||
// 判断是否是微信浏览器
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
var isWeixin = ua.indexOf("micromessenger") != -1;
|
||||
if (isWeixin) {
|
||||
this.weChat = true;
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import config from "@/config/config"
|
||||
import logoImg from "@/icon.png"
|
||||
|
||||
const weChat = ref(false)
|
||||
const logo = logoImg
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef H5
|
||||
// 判断是否是微信浏览器
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
var isWeixin = ua.indexOf("micromessenger") != -1;
|
||||
weChat.value = isWeixin ? true : false
|
||||
// #endif
|
||||
})
|
||||
|
||||
/**
|
||||
* 跳转到下载app页面
|
||||
*/
|
||||
const downloadApp = () => {
|
||||
setTimeout(function () {
|
||||
window.location.href = config.downloadLink;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开app 仅在h5生效 使用ifream唤醒app
|
||||
*/
|
||||
const openApp = () => {
|
||||
let src: string = ''
|
||||
if (location.href) {
|
||||
src = location.href.split("/pages")[1];
|
||||
}
|
||||
let t = `${config.schemeLink}pages${src}`
|
||||
|
||||
try {
|
||||
var e = navigator.userAgent.toLowerCase(),
|
||||
n = e.match(/cpu iphone os (.*?) like mac os/);
|
||||
if (
|
||||
((n = null !== n ? n[1].replace(/_/g, ".") : 0), parseInt(n) >= 9)
|
||||
) {
|
||||
window.location.href = t
|
||||
|
||||
downloadApp()
|
||||
} else {
|
||||
this.weChat = false;
|
||||
var r = document.createElement("iframe");
|
||||
(r.src = t), (r.style.display = "none"), document.body.appendChild(r)
|
||||
|
||||
downloadApp()
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 跳转到下载app页面
|
||||
*/
|
||||
downloadApp() {
|
||||
setTimeout(function () {
|
||||
window.location.href = config.downloadLink;
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开app 仅在h5生效 使用ifream唤醒app
|
||||
*/
|
||||
openApp() {
|
||||
let src;
|
||||
if (location.href) {
|
||||
src = location.href.split("/pages")[1];
|
||||
}
|
||||
let t = `${config.schemeLink}pages${src}`;
|
||||
|
||||
try {
|
||||
var e = navigator.userAgent.toLowerCase(),
|
||||
n = e.match(/cpu iphone os (.*?) like mac os/);
|
||||
if (
|
||||
((n = null !== n ? n[1].replace(/_/g, ".") : 0), parseInt(n) >= 9)
|
||||
) {
|
||||
window.location.href = t;
|
||||
|
||||
this.downloadApp();
|
||||
} else {
|
||||
var r = document.createElement("iframe");
|
||||
(r.src = t), (r.style.display = "none"), document.body.appendChild(r);
|
||||
|
||||
this.downloadApp();
|
||||
}
|
||||
} catch (e) {
|
||||
window.location.href = t;
|
||||
this.downloadApp();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
window.location.href = t
|
||||
downloadApp()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -82,19 +82,19 @@
|
||||
|
||||
<!-- 拼团购买,仅筛选出当前拼团类型商品 -->
|
||||
<template v-for="(spec_val, spec_index) in spec.values" :key="spec_index">
|
||||
<view
|
||||
v-if="parentOrder && spec_val.skuId == goodsDetail.id"
|
||||
:class="{ active: spec_val.value == currentSelected[specIndex] }"
|
||||
class="skus-view-item"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)"
|
||||
>
|
||||
{{ spec_val.value }}
|
||||
</view>
|
||||
<view
|
||||
v-if="parentOrder && spec_val.skuId == goodsDetail.id"
|
||||
:class="{ active: spec_val.value == currentSelected[specIndex] }"
|
||||
class="skus-view-item"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)"
|
||||
>
|
||||
{{ spec_val.value }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<div class="soldout" v-if="goodsDetail.quantity === 0">
|
||||
<u-alert-tips type="warning" title="商品已售罄" description="当前商品库存为0"></u-alert-tips>
|
||||
<u-alert type="warning" title="商品已售罄" :description="'当前商品库存为0'"></u-alert>
|
||||
</div>
|
||||
<!-- 数量 -->
|
||||
<view v-if="goodsDetail.quantity !== 0" class="goods-skus-number flex flex-a-c flex-j-sb">
|
||||
@@ -111,313 +111,269 @@
|
||||
</u-popup>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import * as API_trade from '@/api/trade.js';
|
||||
import setup from './popup';
|
||||
import uniNumberBox from '@/components/uni-number-box'
|
||||
export default {
|
||||
components: {
|
||||
uniNumberBox
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
setup,
|
||||
num: this.wholesaleList && this.wholesaleList.length > 0 ? this.wholesaleList[0].num : 1,
|
||||
|
||||
selectName: '', //选中商品的昵称
|
||||
selectSkuList: '', //选中商铺sku,
|
||||
selectedSpecImg: '', //选中的图片路径
|
||||
buyType: '', //用于存储促销,拼团等活动类型
|
||||
parentOrder: '', //父级拼团活动的数据 - 如果是团员则有数据
|
||||
formatList: [],
|
||||
currentSelected: [],
|
||||
skuList: '',
|
||||
isClose: false //是否可以点击遮罩关闭
|
||||
};
|
||||
},
|
||||
props: {
|
||||
wholesaleList: {
|
||||
type: null,
|
||||
default: false
|
||||
},
|
||||
buyMask: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isGroup: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
goodsDetail: {
|
||||
default: '',
|
||||
type: null
|
||||
},
|
||||
selectedSku: {
|
||||
default: '',
|
||||
type: null
|
||||
},
|
||||
goodsSpec: {
|
||||
default: '',
|
||||
type: null
|
||||
},
|
||||
addr: {
|
||||
default: '',
|
||||
type: null
|
||||
},
|
||||
pointDetail: {
|
||||
default: '',
|
||||
type: null
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import * as API_trade from '@/api/trade.js'
|
||||
import setup from './popup.js'
|
||||
import uniNumberBox from '@/components/uni-number-box.vue'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
wholesaleList?: any[] | boolean
|
||||
buyMask?: boolean
|
||||
isGroup?: boolean
|
||||
goodsDetail?: any
|
||||
selectedSku?: any | null
|
||||
goodsSpec?: any | null
|
||||
addr?: any | null
|
||||
pointDetail?: any | null
|
||||
}>(), {
|
||||
wholesaleList: false,
|
||||
buyMask: false,
|
||||
isGroup: false,
|
||||
goodsDetail: () => ({}),
|
||||
selectedSku: '',
|
||||
goodsSpec: '',
|
||||
addr: '',
|
||||
pointDetail: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits(['closeBuy', 'changed', 'handleClickSku', 'queryCart'])
|
||||
|
||||
const num = ref(props.wholesaleList && Array.isArray(props.wholesaleList) && props.wholesaleList.length > 0 ? props.wholesaleList[0].num : 1)
|
||||
|
||||
const selectName = ref('')
|
||||
const selectSkuList = ref<any>('')
|
||||
const selectedSpecImg = ref('')
|
||||
const buyType = ref('')
|
||||
const parentOrder = ref<any>('')
|
||||
const formatList = ref<any[]>([])
|
||||
const currentSelected = ref<string[]>([])
|
||||
const skuList = ref('')
|
||||
|
||||
watch(num, (val) => {
|
||||
val == 0 ? num.value = 1 : ''
|
||||
if (val) {
|
||||
//超过库存后修改回库存
|
||||
if (val > props.goodsDetail.quantity) {
|
||||
num.value = props.goodsDetail.quantity
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wholesalePrice(key) {
|
||||
return this.wholesaleList.length
|
||||
? this.wholesaleList.map(item => {
|
||||
return item.price;
|
||||
})
|
||||
: [];
|
||||
},
|
||||
wholesaleNum(key) {
|
||||
return this.wholesaleList.length
|
||||
? this.wholesaleList.map(item => {
|
||||
return item.num;
|
||||
})
|
||||
: [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
num(val) {
|
||||
|
||||
val == 0 ? this.num = 1 : ''
|
||||
if (val) {
|
||||
|
||||
//超过库存后修改回库存
|
||||
if (val > this.goodsDetail.quantity) {
|
||||
this.$nextTick(function() {
|
||||
this.num = this.goodsDetail.quantity;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
buyType: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.buyType = val;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
selectSkuList: {
|
||||
handler(val, oldval) {
|
||||
this.$emit('changed', val);
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
'goodsDetail.quantity': {
|
||||
handler(val) {
|
||||
if (val == 0) {
|
||||
uni.showToast({
|
||||
title: '商品已售罄',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
})
|
||||
this.num = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
numCheck(val) {
|
||||
if (this.wholesaleList && this.wholesaleList.length > 0) {
|
||||
if (this.num <= this.wholesaleList[0].num) {
|
||||
uni.showToast({
|
||||
title: '批发商品购买数量不能小于起批数量!',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
});
|
||||
this.num = this.wholesaleList[0].num;
|
||||
}
|
||||
}
|
||||
},
|
||||
closeMask() {
|
||||
this.$emit('closeBuy', false);
|
||||
},
|
||||
|
||||
/**点击规格 */
|
||||
handleClickSpec(val, index, specValue) {
|
||||
this.currentSelected[index] = specValue.value;
|
||||
let selectedSkuId = this.goodsSpec.find(i => {
|
||||
let matched = true;
|
||||
let specValues = i.specValues.filter(j => j.specName !== 'images');
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== this.currentSelected[n]) {
|
||||
matched = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i;
|
||||
}
|
||||
});
|
||||
if (selectedSkuId?.skuId) {
|
||||
this.currentSelected[index] = specValue.value;
|
||||
this.selectSkuList = {
|
||||
spec: {
|
||||
specName: val.name,
|
||||
specValue: specValue.value
|
||||
},
|
||||
data: this.goodsDetail
|
||||
};
|
||||
this.selectName = specValue.value;
|
||||
|
||||
this.$emit('handleClickSku', {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: this.goodsDetail.goodsId
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '暂无该商品!',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 直接购买
|
||||
*/
|
||||
buy(data) {
|
||||
API_trade.addToCart(data).then(res => {
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${''}&parentOrder=${encodeURIComponent(JSON.stringify(this.parentOrder))}`
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加到购物车或购买
|
||||
*/
|
||||
addToCartOrBuy(val) {
|
||||
if (!this.selectSkuList) {
|
||||
uni.showToast({
|
||||
title: '请选择规格商品',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
let data = {
|
||||
skuId: this.goodsDetail.id,
|
||||
num: this.num
|
||||
};
|
||||
|
||||
if (val == 'cart') {
|
||||
API_trade.addToCart(data).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({
|
||||
title: '商品已添加到购物车',
|
||||
icon: 'none'
|
||||
});
|
||||
|
||||
this.$emit('queryCart');
|
||||
this.closeMask();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 判断是否拼团商品
|
||||
if (this.buyType) {
|
||||
data.cartType = 'PINTUAN';
|
||||
} else if (this.goodsDetail.goodsType == 'VIRTUAL_GOODS') {
|
||||
data.cartType = 'VIRTUAL';
|
||||
} else {
|
||||
data.cartType = 'BUY_NOW';
|
||||
}
|
||||
|
||||
API_trade.addToCart(data).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${this.addr.id || ''}&parentOrder=${encodeURIComponent(JSON.stringify(this.parentOrder))}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
formatSku(list) {
|
||||
// 格式化数据
|
||||
let arr = [{}];
|
||||
|
||||
if (!Array.isArray(list)) {
|
||||
return false;
|
||||
}
|
||||
list.forEach((item, index) => {
|
||||
item.specValues.forEach((spec, specIndex) => {
|
||||
let name = spec.specName;
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
skuId: item.skuId
|
||||
};
|
||||
if (name === 'images') {
|
||||
return;
|
||||
}
|
||||
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name == name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find(i => {
|
||||
return i.value === values.value;
|
||||
})
|
||||
) {
|
||||
arrItem.values.push(values);
|
||||
}
|
||||
|
||||
let keys = arr.map(key => {
|
||||
return key.name;
|
||||
});
|
||||
if (!keys.includes(name)) {
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values]
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
arr.shift();
|
||||
this.formatList = arr;
|
||||
|
||||
list.forEach(item => {
|
||||
// 默认选中
|
||||
if (item.skuId === this.goodsDetail.id) {
|
||||
item.specValues
|
||||
.filter(i => i.specName !== 'images')
|
||||
.forEach((value, _index) => {
|
||||
this.currentSelected[_index] = value.specValue;
|
||||
|
||||
this.selectName = value.specValue;
|
||||
|
||||
this.selectSkuList = {
|
||||
spec: value,
|
||||
data: this.goodsDetail
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.skuList = list;
|
||||
// console.log(" this.skuList", this.skuList)
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.formatSku(this.goodsSpec);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
watch(() => props.buyType, (val) => {
|
||||
if (val) {
|
||||
buyType.value = val as string
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(selectSkuList, (_val) => {
|
||||
emit('changed', selectSkuList.value)
|
||||
}, { deep: true })
|
||||
|
||||
watch(() => props.goodsDetail?.quantity, (val) => {
|
||||
if (val == 0) {
|
||||
uni.showToast({
|
||||
title: '商品已售罄',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
})
|
||||
num.value = 1
|
||||
}
|
||||
})
|
||||
|
||||
const numCheck = (val: number) => {
|
||||
if (Array.isArray(props.wholesaleList) && props.wholesaleList.length > 0) {
|
||||
if (num.value <= props.wholesaleList[0].num) {
|
||||
uni.showToast({
|
||||
title: '批发商品购买数量不能小于起批数量!',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
})
|
||||
num.value = props.wholesaleList[0].num
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const closeMask = () => {
|
||||
emit('closeBuy', false)
|
||||
}
|
||||
|
||||
/**点击规格 */
|
||||
const handleClickSpec = (val: any, index: number, specValue: any) => {
|
||||
currentSelected.value[index] = specValue.value
|
||||
let selectedSkuId = props.goodsSpec.find((i: any) => {
|
||||
let matched = true
|
||||
let specValues = i.specValues.filter((j: any) => j.specName !== 'images')
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== currentSelected.value[n]) {
|
||||
matched = false
|
||||
return
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i
|
||||
}
|
||||
})
|
||||
if (selectedSkuId?.skuId) {
|
||||
currentSelected.value[index] = specValue.value
|
||||
selectSkuList.value = {
|
||||
spec: {
|
||||
specName: val.name,
|
||||
specValue: specValue.value
|
||||
},
|
||||
data: props.goodsDetail
|
||||
}
|
||||
selectName.value = specValue.value
|
||||
|
||||
emit('handleClickSku', {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: props.goodsDetail.goodsId
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '暂无该商品!',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接购买
|
||||
*/
|
||||
const buy = (data: any) => {
|
||||
API_trade.addToCart(data).then(res => {
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${''}&parentOrder=${encodeURIComponent(JSON.stringify(parentOrder.value))}`
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到购物车或购买
|
||||
*/
|
||||
const addToCartOrBuy = (val: string) => {
|
||||
if (!selectSkuList.value) {
|
||||
uni.showToast({
|
||||
title: '请选择规格商品',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
let data = {
|
||||
skuId: props.goodsDetail.id,
|
||||
num: num.value
|
||||
}
|
||||
|
||||
if (val == 'cart') {
|
||||
API_trade.addToCart(data).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({
|
||||
title: '商品已添加到购物车',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
emit('queryCart')
|
||||
closeMask()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 判断是否拼团商品
|
||||
if (buyType.value) {
|
||||
data.cartType = 'PINTUAN'
|
||||
} else if (props.goodsDetail.goodsType == 'VIRTUAL_GOODS') {
|
||||
data.cartType = 'VIRTUAL'
|
||||
} else {
|
||||
data.cartType = 'BUY_NOW'
|
||||
}
|
||||
|
||||
API_trade.addToCart(data).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${props.addr?.id || ''}&parentOrder=${encodeURIComponent(JSON.stringify(parentOrder.value))}`
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const formatSku = (list: any[]) => {
|
||||
// 格式化数据
|
||||
let arr: any[] = [{}]
|
||||
|
||||
if (!Array.isArray(list)) {
|
||||
return false
|
||||
}
|
||||
list.forEach((_item, _index) => {
|
||||
let item = list[_index]
|
||||
item.specValues.forEach((spec: any, specIndex: number) => {
|
||||
let name = spec.specName
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
skuId: item.skuId
|
||||
}
|
||||
if (name === 'images') {
|
||||
return
|
||||
}
|
||||
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name == name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find((i: any) => {
|
||||
return i.value === values.value
|
||||
})
|
||||
) {
|
||||
arrItem.values.push(values)
|
||||
}
|
||||
|
||||
let keys = arr.map(key => {
|
||||
return key.name
|
||||
})
|
||||
if (!keys.includes(name)) {
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values]
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
arr.shift()
|
||||
formatList.value = arr
|
||||
|
||||
list.forEach(item => {
|
||||
// 默认选中
|
||||
if (item.skuId === props.goodsDetail.id) {
|
||||
item.specValues
|
||||
.filter(i => i.specName !== 'images')
|
||||
.forEach((value, _index) => {
|
||||
currentSelected.value[_index] = value.specValue
|
||||
|
||||
selectName.value = value.specValue
|
||||
|
||||
selectSkuList.value = {
|
||||
spec: value,
|
||||
data: props.goodsDetail
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
skuList.value = list
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
formatSku(props.goodsSpec)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './popup.scss';
|
||||
|
||||
|
||||
@@ -17,168 +17,156 @@
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
||||
// 引入绘制插件
|
||||
import DrawPoster from "@/js_sdk/u-draw-poster";
|
||||
import logoImg from "@/pages/passport/static/logo-title.png";
|
||||
import DrawPoster from "@/js_sdk/u-draw-poster"
|
||||
import logoImg from "@/pages/passport/static/logo-title.png"
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
imgUrl: "", //绘制出来的图片路径
|
||||
show: false, //是否展示模态框
|
||||
dp: {}, //绘制的dp对象,用于存储绘制等一些方法。
|
||||
logo: logoImg, //本地logo地址
|
||||
}),
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
props: {
|
||||
/**
|
||||
* 父级传参的数据
|
||||
*/
|
||||
res: {
|
||||
type: null,
|
||||
default: "",
|
||||
const props = defineProps<{
|
||||
res?: any
|
||||
}>()
|
||||
|
||||
const imgUrl = ref("")
|
||||
const show = ref(false)
|
||||
const dp = ref<any>({})
|
||||
const logo = logoImg
|
||||
|
||||
/**
|
||||
* 解决微信小程序中图片模糊问题
|
||||
*/
|
||||
// #ifdef MP-WEIXIN
|
||||
const st2 = (size: number) => size * 2
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
const st2 = (size: number) => size
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 保存图片
|
||||
*/
|
||||
const downLoad = () => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: imgUrl.value,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "保存成功!",
|
||||
icon: "none",
|
||||
})
|
||||
},
|
||||
},
|
||||
onUnload() {},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 解决微信小程序中图片模糊问题
|
||||
*/
|
||||
// #ifdef MP-WEIXIN
|
||||
st2: (size) => size * 2,
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
st2: (size) => size,
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 保存图片
|
||||
*/
|
||||
downLoad() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: this.imgUrl,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "保存成功!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "保存失败,请稍后重试!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "保存失败,请稍后重试!",
|
||||
icon: "none",
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建canvas
|
||||
*/
|
||||
async init() {
|
||||
this.show = true;
|
||||
this.dp = await DrawPoster.build({
|
||||
selector: "canvas",
|
||||
componentThis: this,
|
||||
loading: true,
|
||||
debugging: true,
|
||||
});
|
||||
let dp = this.dp;
|
||||
// #ifdef MP-WEIXIN
|
||||
// 用于微信小程序中画布错乱问题
|
||||
dp.canvas.width = this.st2(600);
|
||||
dp.canvas.height = this.st2(960);
|
||||
// #endif
|
||||
this.draw(dp);
|
||||
},
|
||||
/**
|
||||
* 创建canvas
|
||||
*/
|
||||
const init = async () => {
|
||||
show.value = true
|
||||
dp.value = await DrawPoster.build({
|
||||
selector: "canvas",
|
||||
componentThis: instance?.proxy,
|
||||
loading: true,
|
||||
debugging: true,
|
||||
})
|
||||
let dpVal = dp.value
|
||||
// #ifdef MP-WEIXIN
|
||||
// 用于微信小程序中画布错乱问题
|
||||
dpVal.canvas.width = st2(600)
|
||||
dpVal.canvas.height = st2(960)
|
||||
// #endif
|
||||
await draw(dpVal)
|
||||
}
|
||||
|
||||
async draw(dp) {
|
||||
const { width, height, background, title } = this.res.container;
|
||||
const { code, img, price } = this.res.bottom;
|
||||
const draw = async (dp: any) => {
|
||||
const { width, height, background, title } = props.res.container
|
||||
const { code, img, price } = props.res.bottom
|
||||
|
||||
// /** 绘制背景 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = background;
|
||||
ctx.fillRoundRect(
|
||||
this.st2(0),
|
||||
this.st2(0),
|
||||
this.st2(width),
|
||||
this.st2(height),
|
||||
this.st2(12)
|
||||
);
|
||||
ctx.clip();
|
||||
});
|
||||
/** 绘制图片 */
|
||||
dp.draw(async (ctx) => {
|
||||
await Promise.all([
|
||||
// 绘制Logo
|
||||
ctx.drawImage(
|
||||
this.logo,
|
||||
this.st2(175),
|
||||
this.st2(0),
|
||||
this.st2(256),
|
||||
this.st2(144)
|
||||
),
|
||||
// 中间图片
|
||||
ctx.drawImage(
|
||||
img,
|
||||
this.st2(100),
|
||||
this.st2(150),
|
||||
this.st2(400),
|
||||
this.st2(400)
|
||||
),
|
||||
// 二维码
|
||||
ctx.drawImage(
|
||||
code,
|
||||
this.st2(39),
|
||||
this.st2(750),
|
||||
this.st2(150),
|
||||
this.st2(150)
|
||||
),
|
||||
]);
|
||||
});
|
||||
// /** 绘制背景 */
|
||||
await dp.draw((ctx: any) => {
|
||||
ctx.fillStyle = background
|
||||
ctx.fillRoundRect(
|
||||
st2(0),
|
||||
st2(0),
|
||||
st2(width),
|
||||
st2(height),
|
||||
st2(12)
|
||||
)
|
||||
ctx.clip()
|
||||
})
|
||||
/** 绘制图片 */
|
||||
dp.draw(async (ctx: any) => {
|
||||
await Promise.all([
|
||||
// 绘制Logo
|
||||
ctx.drawImage(
|
||||
logo,
|
||||
st2(175),
|
||||
st2(0),
|
||||
st2(256),
|
||||
st2(144)
|
||||
),
|
||||
// 中间图片
|
||||
ctx.drawImage(
|
||||
img,
|
||||
st2(100),
|
||||
st2(150),
|
||||
st2(400),
|
||||
st2(400)
|
||||
),
|
||||
// 二维码
|
||||
ctx.drawImage(
|
||||
code,
|
||||
st2(39),
|
||||
st2(750),
|
||||
st2(150),
|
||||
st2(150)
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
/** 绘制中间文字*/
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = `bold ${this.st2(24)}px PingFang SC`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillWarpText({
|
||||
text: title,
|
||||
maxWidth: this.st2(500),
|
||||
x: this.st2(300),
|
||||
y: this.st2(600),
|
||||
layer: 1,
|
||||
});
|
||||
/** 绘制中间文字*/
|
||||
await dp.draw((ctx: any) => {
|
||||
ctx.fillStyle = "#333"
|
||||
ctx.font = `bold ${st2(24)}px PingFang SC`
|
||||
ctx.textAlign = "center"
|
||||
ctx.fillWarpText({
|
||||
text: title,
|
||||
maxWidth: st2(500),
|
||||
x: st2(300),
|
||||
y: st2(600),
|
||||
layer: 1,
|
||||
})
|
||||
|
||||
ctx.fillStyle = "#ff3c2a";
|
||||
ctx.font = `${this.st2(38)}px PingFang SC`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(price, this.st2(300), this.st2(680));
|
||||
});
|
||||
ctx.fillStyle = "#ff3c2a"
|
||||
ctx.font = `${st2(38)}px PingFang SC`
|
||||
ctx.textAlign = "center"
|
||||
ctx.fillText(price, st2(300), st2(680))
|
||||
})
|
||||
|
||||
// /** 绘制底部文字 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText("长按图片,识别二维码", this.st2(200), this.st2(866));
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText("查看商品详情", this.st2(200), this.st2(900));
|
||||
});
|
||||
// /** 绘制底部文字 */
|
||||
await dp.draw((ctx: any) => {
|
||||
ctx.fillStyle = "#666"
|
||||
ctx.font = `${st2(24)}px PingFang SC`
|
||||
ctx.fillText("长按图片,识别二维码", st2(200), st2(866))
|
||||
ctx.fillStyle = "#666"
|
||||
ctx.font = `${st2(24)}px PingFang SC`
|
||||
ctx.fillText("查看商品详情", st2(200), st2(900))
|
||||
})
|
||||
|
||||
this.imgUrl = await dp.createImagePath();
|
||||
imgUrl.value = await dp.createImagePath()
|
||||
}
|
||||
|
||||
// console.log(posterImgUrl)
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -79,192 +79,182 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, getCurrentInstance } from 'vue'
|
||||
import { getRegionsById } from "@/api/common.js"
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
let windowWidth = 0
|
||||
import { getRegionsById } from "@/api/common.js";
|
||||
export default {
|
||||
name: "UniCityNvue",
|
||||
props: {
|
||||
headTitle: {
|
||||
//标题
|
||||
type: String,
|
||||
default: "区域选择",
|
||||
},
|
||||
pickerSize: {
|
||||
// 使用多少个tab
|
||||
type: [String, String],
|
||||
default: "1",
|
||||
},
|
||||
provinceData: {
|
||||
// 默认的省市区id,如果不使用id的情况下则为[];
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
headTitle?: string
|
||||
pickerSize?: string | string[]
|
||||
provinceData?: any[]
|
||||
}>(), {
|
||||
headTitle: "区域选择",
|
||||
pickerSize: "1",
|
||||
provinceData: () => [],
|
||||
})
|
||||
|
||||
const emit = defineEmits(['funcValue'])
|
||||
|
||||
const clearRightIcon = ref(true)
|
||||
const scrollLeft = ref(500)
|
||||
const scrollTop = ref(0)
|
||||
const enableScroll = ref(true)
|
||||
const tabCurrentIndex = ref(0)
|
||||
const tabbars = ref<any[]>(props.provinceData)
|
||||
const pickersize = ref(props.pickerSize)
|
||||
const showPicker = ref(false)
|
||||
|
||||
/**
|
||||
* 显示选择器
|
||||
*/
|
||||
const show = () => {
|
||||
showPicker.value = true
|
||||
if (tabbars.value[0].children.length == 0) {
|
||||
getRegionsById(0).then((res: any) => {
|
||||
tabbars.value[0].children = res.data.result
|
||||
})
|
||||
}
|
||||
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭选择器
|
||||
*/
|
||||
const hide = () => {
|
||||
showPicker.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* tab切换
|
||||
*/
|
||||
const changeTab = (e: number) => {
|
||||
let index = e
|
||||
setScroll(index)
|
||||
//延迟300ms,等待swiper动画结束再修改tabbar
|
||||
tabCurrentIndex.value = index
|
||||
setTimeout(() => {
|
||||
getScroll("show" + index)
|
||||
}, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得元素的大小
|
||||
*/
|
||||
const getElSize = (id: string): Promise<any> => {
|
||||
return new Promise((res) => {
|
||||
let el = uni
|
||||
.createSelectorQuery()
|
||||
.in(instance!.proxy)
|
||||
.select("#" + id)
|
||||
el.fields(
|
||||
{
|
||||
size: true,
|
||||
scrollOffset: true,
|
||||
rect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clearRightIcon: true, //是否显示右侧关闭icon
|
||||
scrollLeft: 500, //顶部选项卡左滑距离
|
||||
scrollTop: 0, //默认滚动顶部为0
|
||||
enableScroll: true, //是否启用滚动
|
||||
tabCurrentIndex: 0, //当前选项卡索引
|
||||
tabbars: this.provinceData, //默认的省市区id
|
||||
pickersize: this.pickerSize, //多少个tab 推荐为4级
|
||||
showPicker: false, //显示选取器
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 显示选择器
|
||||
*/
|
||||
show() {
|
||||
this.showPicker = true;
|
||||
if (this.tabbars[0].children.length == 0) {
|
||||
getRegionsById(0).then((res) => {
|
||||
this.tabbars[0].children = res.data.result;
|
||||
});
|
||||
(data: any) => {
|
||||
res(data)
|
||||
}
|
||||
).exec()
|
||||
})
|
||||
}
|
||||
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭选择器
|
||||
*/
|
||||
hide() {
|
||||
this.showPicker = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* tab切换
|
||||
*/
|
||||
changeTab(e) {
|
||||
let index = e;
|
||||
this.setScroll(index);
|
||||
//延迟300ms,等待swiper动画结束再修改tabbar
|
||||
this.tabCurrentIndex = index;
|
||||
setTimeout(() => {
|
||||
this.getScroll("show" + index);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获得元素的大小
|
||||
*/
|
||||
getElSize(id) {
|
||||
return new Promise((res, rej) => {
|
||||
let el = uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#" + id);
|
||||
el.fields(
|
||||
{
|
||||
size: true,
|
||||
scrollOffset: true,
|
||||
rect: true,
|
||||
},
|
||||
(data) => {
|
||||
res(data);
|
||||
}
|
||||
).exec();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击城市后回调
|
||||
*/
|
||||
async changCity(index, item) {
|
||||
if (this.tabbars[index].id != item.id) {
|
||||
this.tabbars[index].localName = item.name;
|
||||
this.tabbars[index].id = item.id;
|
||||
this.tabbars[index].center = item.center
|
||||
if (index < this.tabbars.length - 1) {
|
||||
this.tabbars.splice(index + 1, this.tabbars.length - index - 1);
|
||||
}
|
||||
if (this.tabbars.length < this.pickersize) {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
mask:true
|
||||
});
|
||||
try {
|
||||
let data = await getRegionsById(item.id);
|
||||
uni.hideLoading();
|
||||
// 当前选项级为最后一级时回调,将选中的数据返回
|
||||
if (data.data.result.length == 0) {
|
||||
this.$emit("funcValue", this.tabbars);
|
||||
this.hide();
|
||||
} else {
|
||||
// 将新的数据填充进下一级
|
||||
var current = {
|
||||
localName: "请选择",
|
||||
id: "",
|
||||
children: data.data.result,
|
||||
|
||||
};
|
||||
this.tabbars.push(current);
|
||||
this.tabCurrentIndex++;
|
||||
|
||||
// 当前距离重新为最上面
|
||||
this['scrollTop'] = 0
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击城市后回调
|
||||
*/
|
||||
const changCity = async (index: number, item: any) => {
|
||||
if (tabbars.value[index].id != item.id) {
|
||||
tabbars.value[index].localName = item.name
|
||||
tabbars.value[index].id = item.id
|
||||
tabbars.value[index].center = item.center
|
||||
if (index < tabbars.value.length - 1) {
|
||||
tabbars.value.splice(index + 1, tabbars.value.length - index - 1)
|
||||
}
|
||||
if (tabbars.value.length < pickersize.value as number) {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
mask: true
|
||||
})
|
||||
try {
|
||||
let data = await getRegionsById(item.id)
|
||||
uni.hideLoading()
|
||||
// 当前选项级为最后一级时回调,将选中的数据返回
|
||||
if (data.data.result.length == 0) {
|
||||
emit("funcValue", tabbars.value)
|
||||
hide()
|
||||
} else {
|
||||
this.$emit("funcValue", this.tabbars);
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 将新的数据填充进下一级
|
||||
var current = {
|
||||
localName: "请选择",
|
||||
id: "",
|
||||
children: data.data.result,
|
||||
}
|
||||
tabbars.value.push(current)
|
||||
tabCurrentIndex.value++
|
||||
|
||||
/**
|
||||
* 获取当前tab中滚动的距离
|
||||
*/
|
||||
async setScroll(index) {
|
||||
let width = 0;
|
||||
let nowWidth = 0;
|
||||
for (let i = 0; i <= index; i++) {
|
||||
let result = await this.getElSize("tab" + i);
|
||||
width += result.width;
|
||||
if (i === index) {
|
||||
nowWidth = result.width;
|
||||
// 当前距离重新为最上面
|
||||
scrollTop.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
if (width + nowWidth > windowWidth) {
|
||||
this.scrollLeft = width + nowWidth;
|
||||
} else {
|
||||
this.scrollLeft = 0;
|
||||
}
|
||||
},
|
||||
} else {
|
||||
emit("funcValue", tabbars.value)
|
||||
hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前的滚动距离
|
||||
*/
|
||||
getScroll(id) {
|
||||
/**
|
||||
* 获取当前tab中滚动的距离
|
||||
*/
|
||||
const setScroll = async (index: number) => {
|
||||
let width = 0
|
||||
let nowWidth = 0
|
||||
for (let i = 0; i <= index; i++) {
|
||||
let result = await getElSize("tab" + i)
|
||||
width += result.width
|
||||
if (i === index) {
|
||||
nowWidth = result.width
|
||||
}
|
||||
}
|
||||
if (width + nowWidth > windowWidth) {
|
||||
scrollLeft.value = width + nowWidth
|
||||
} else {
|
||||
scrollLeft.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前的滚动距离
|
||||
*/
|
||||
const getScroll = (id: string) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(instance!.proxy)
|
||||
.select(".panel-scroll-box")
|
||||
.boundingClientRect((data: any) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(".panel-scroll-box")
|
||||
.boundingClientRect((data) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#" + id)
|
||||
.boundingClientRect((res) => {
|
||||
if (res != undefined && res != null && res != "") {
|
||||
this.scrollTop = res.top - data.top;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
.in(instance!.proxy)
|
||||
.select("#" + id)
|
||||
.boundingClientRect((res: any) => {
|
||||
if (res != undefined && res != null && res != "") {
|
||||
scrollTop.value = res.top - data.top
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
},
|
||||
};
|
||||
.exec()
|
||||
})
|
||||
.exec()
|
||||
}
|
||||
|
||||
// 暴露方法给父组件调用
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<view>
|
||||
<!-- 一行两列商品展示 -->
|
||||
<view class="goods-list" v-if="type == 'twoColumns'">
|
||||
<view v-for="(item, index) in res" :key="index" class="goods-item">
|
||||
@@ -10,7 +10,7 @@
|
||||
height="330rpx"
|
||||
mode="aspectFit"
|
||||
>
|
||||
<template #loading><u-loading></u-loading></template>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-detail">
|
||||
@@ -57,7 +57,7 @@
|
||||
</view>
|
||||
<!-- 一行一列商品展示 -->
|
||||
<div v-if="type == 'oneColumns'">
|
||||
<div v-for="(item, index) in res" :key="index" class="goods-row">
|
||||
div v-for="(item, index) in res" :key="index" class="goods-row">
|
||||
<div class="flex goods-col">
|
||||
<div class="goods-img" @click="navigateToDetailPage(item)">
|
||||
<u-image
|
||||
@@ -67,7 +67,7 @@
|
||||
mode="aspectFit"
|
||||
:src="item.goodsImage || item.thumbnail"
|
||||
>
|
||||
<template #loading><u-loading></u-loading></template>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-detail">
|
||||
@@ -112,41 +112,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import commonTpl from "@/components/m-goods-list/common";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lightColor: this.$mainColor,
|
||||
};
|
||||
},
|
||||
mixins: [commonTpl],
|
||||
props: {
|
||||
// 展示的类型
|
||||
type:{
|
||||
type:String,
|
||||
default:"oneColumns"
|
||||
},
|
||||
// 遍历的数据
|
||||
res: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 跳转到商品详情
|
||||
navigateToDetailPage(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { useGoodsListCommon } from './common'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
const { lightSearchStr, getPromotion, navigateToDetailPage, navigateToStoreDetailPage } = useGoodsListCommon()
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
type?: string
|
||||
res?: any[]
|
||||
storeName?: boolean
|
||||
keyword?: string | null
|
||||
}>(), {
|
||||
type: 'oneColumns',
|
||||
res: () => [],
|
||||
storeName: true,
|
||||
keyword: ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
90
components/m-goods-list/common.ts
Normal file
90
components/m-goods-list/common.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useStore } from '@/store'
|
||||
|
||||
/**
|
||||
* 商品列表公共逻辑(原 common.vue mixin)
|
||||
* 提供高亮搜索、unicode 转换、促销标签、导航跳转等通用方法
|
||||
*/
|
||||
export function useGoodsListCommon() {
|
||||
const store = useStore()
|
||||
|
||||
const lightColor = store.getters.lightColor
|
||||
|
||||
/**
|
||||
* 高亮显示搜索内容
|
||||
*/
|
||||
const lightSearchStr = (keyword: string, str: string) => {
|
||||
if (!keyword) {
|
||||
return str
|
||||
} else {
|
||||
let unicodes = ''
|
||||
for (let i of Array.from(keyword)) {
|
||||
unicodes += unicode(i) + "|"
|
||||
}
|
||||
const rule = '(' + unicodes + ')'
|
||||
const reg = new RegExp(rule, 'gi')
|
||||
return str ? str.replace(reg, (matchValue) =>
|
||||
`<span style="color:${lightColor}">${matchValue}</span>`
|
||||
) : ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 unicode
|
||||
*/
|
||||
const unicode = (str: string) => {
|
||||
var value = ''
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
value += '\\u' + leftZero4(parseInt(str.charCodeAt(i).toString(16)))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const leftZero4 = (str: string) => {
|
||||
if (str != null && str != '' && str != 'undefined') {
|
||||
if (str.length == 2) {
|
||||
return '00' + str
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据去重 只显示一次 减免 劵等
|
||||
*/
|
||||
const getPromotion = (item: any) => {
|
||||
if (item.promotionMap) {
|
||||
let array: string[] = []
|
||||
Object.keys(item.promotionMap).forEach((child) => {
|
||||
if (!array.includes(child.split("-")[0])) {
|
||||
array.push(child.split("-")[0])
|
||||
}
|
||||
})
|
||||
return array
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到商品详情
|
||||
*/
|
||||
const navigateToDetailPage = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到店铺详情
|
||||
*/
|
||||
const navigateToStoreDetailPage = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/shopPage?id=${item.storeId}`,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
lightSearchStr,
|
||||
getPromotion,
|
||||
navigateToDetailPage,
|
||||
navigateToStoreDetailPage,
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
// 高亮显示搜索内容
|
||||
lightSearchStr(keyword, str) {
|
||||
if (!keyword) {
|
||||
return str
|
||||
} else {
|
||||
let unicodes = '';
|
||||
for (let i of Array.from(keyword)) {
|
||||
unicodes += this.unicode(i) + "|"
|
||||
}
|
||||
const rule = '(' + unicodes + ')'
|
||||
const reg = new RegExp(rule, 'gi');
|
||||
return str ? str.replace(reg, matchValue =>
|
||||
`<span style="color:${this.lightColor}">${matchValue}</span>`
|
||||
) : ''
|
||||
}
|
||||
},
|
||||
// 转换为unicode
|
||||
unicode(str) {
|
||||
var value = '';
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
value += '\\u' + this.left_zero_4(parseInt(str.charCodeAt(i)).toString(16));
|
||||
}
|
||||
return value;
|
||||
},
|
||||
left_zero_4(str) {
|
||||
if (str != null && str != '' && str != 'undefined') {
|
||||
if (str.length == 2) {
|
||||
return '00' + str;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
},
|
||||
// 数据去重一下 只显示一次 减免 劵 什么的
|
||||
getPromotion(item) {
|
||||
if (item.promotionMap) {
|
||||
let array = [];
|
||||
Object.keys(item.promotionMap).forEach((child) => {
|
||||
if (!array.includes(child.split("-")[0])) {
|
||||
array.push(child.split("-")[0]);
|
||||
}
|
||||
});
|
||||
return array;
|
||||
}
|
||||
},
|
||||
// 跳转到商品详情
|
||||
navigateToDetailPage(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
});
|
||||
},
|
||||
// 跳转地址
|
||||
navigateToStoreDetailPage(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/shopPage?id=${item.storeId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@
|
||||
<view v-for="(item, index) in res" :key="index" class="goods-item">
|
||||
<view class="image-wrapper" @click="navigateToDetailPage(item)">
|
||||
<u-image :src="item.thumbnail" width="100%" height='330rpx' mode="aspectFit">
|
||||
<template #loading><u-loading></u-loading></template>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-detail">
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="flex goods-col">
|
||||
<div class="goods-img" @click="navigateToDetailPage(item)">
|
||||
<u-image width="230rpx" mode="aspectFit" border-radius='16' height="230rpx" :src="item.thumbnail">
|
||||
<template #loading><u-loading></u-loading></template>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-detail">
|
||||
@@ -94,114 +94,32 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import commonTpl from '@/components/m-goods-list/common'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lightColor: this.$mainColor
|
||||
}
|
||||
},
|
||||
mixins: [commonTpl],
|
||||
|
||||
props: {
|
||||
// 遍历的数据
|
||||
res: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 一行两列还是一行一列显示
|
||||
type: {
|
||||
type: String,
|
||||
default: 'twoColumns',
|
||||
validator() {
|
||||
return ['twoColumns', 'oneColumns']
|
||||
}
|
||||
},
|
||||
storeName: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tabBarGap: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
keyword: {
|
||||
type: null,
|
||||
default: ''
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { watch } from 'vue'
|
||||
import { useGoodsListCommon } from './common'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
},
|
||||
watch: {
|
||||
keyword(val) {
|
||||
if (val) {
|
||||
this.lightSearchStr(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
const { lightSearchStr, getPromotion, navigateToDetailPage, navigateToStoreDetailPage } = useGoodsListCommon()
|
||||
|
||||
// 高亮显示搜索内容
|
||||
lightSearchStr(keyword, str) {
|
||||
if (!keyword) {
|
||||
return str
|
||||
} else {
|
||||
let unicodes = '';
|
||||
for (let i of Array.from(keyword)) {
|
||||
unicodes += this.unicode(i) + "|"
|
||||
}
|
||||
const rule = '(' + unicodes + ')'
|
||||
const reg = new RegExp(rule, 'gi');
|
||||
return str ? str.replace(reg, matchValue =>
|
||||
`<span style="color:${this.lightColor}">${matchValue}</span>`
|
||||
) : ''
|
||||
}
|
||||
},
|
||||
// 转换为unicode
|
||||
unicode(str) {
|
||||
var value = '';
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
value += '\\u' + this.left_zero_4(parseInt(str.charCodeAt(i)).toString(16));
|
||||
}
|
||||
return value;
|
||||
},
|
||||
left_zero_4(str) {
|
||||
if (str != null && str != '' && str != 'undefined') {
|
||||
if (str.length == 2) {
|
||||
return '00' + str;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
},
|
||||
// 数据去重一下 只显示一次 减免 劵 什么的
|
||||
getPromotion(item) {
|
||||
if (item ? item.promotionMap : item.promotionMap) {
|
||||
const fieldList = item ? item.promotionMap : item.promotionMap
|
||||
let array = [];
|
||||
Object.keys(fieldList).forEach((child) => {
|
||||
if (!array.includes(child.split("-")[0])) {
|
||||
array.push(child.split("-")[0]);
|
||||
}
|
||||
});
|
||||
return array;
|
||||
}
|
||||
},
|
||||
// 跳转到商品详情
|
||||
navigateToDetailPage(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
});
|
||||
},
|
||||
// 跳转地址
|
||||
navigateToStoreDetailPage(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/shopPage?id=${item.storeId}`,
|
||||
});
|
||||
},
|
||||
}
|
||||
const props = withDefaults(defineProps<{
|
||||
res?: any[]
|
||||
type?: string
|
||||
storeName?: boolean
|
||||
tabBarGap?: boolean
|
||||
keyword?: string | null
|
||||
}>(), {
|
||||
res: () => [],
|
||||
type: 'twoColumns',
|
||||
storeName: true,
|
||||
tabBarGap: true,
|
||||
keyword: ''
|
||||
})
|
||||
|
||||
watch(() => props.keyword, (val) => {
|
||||
if (val) {
|
||||
lightSearchStr(val as string, '')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@@ -212,6 +130,7 @@
|
||||
.goods-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin: 10rpx 20rpx 284rpx;
|
||||
width: calc(100% - 40rpx);
|
||||
box-sizing: border-box;
|
||||
@@ -219,21 +138,17 @@
|
||||
&.goods-list--embedded {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
|
||||
>.goods-item {
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
border-radius: 16rpx;
|
||||
flex-direction: column;
|
||||
width: calc(50% - 30rpx);
|
||||
width: calc(50% - 10rpx);
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
height: 330rpx;
|
||||
@@ -242,12 +157,12 @@
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.count-config,
|
||||
.store-seller-name {
|
||||
font-size: $font-sm;
|
||||
}
|
||||
|
||||
|
||||
.store-seller-name {
|
||||
color: #666;
|
||||
display: flex;
|
||||
@@ -322,7 +237,7 @@
|
||||
|
||||
.goods-detail {
|
||||
margin: 0 20rpx;
|
||||
|
||||
|
||||
>.title {
|
||||
font-size: $font-base;
|
||||
color: $font-color-dark;
|
||||
@@ -334,7 +249,7 @@
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.promotion {
|
||||
margin-top: 4rpx;
|
||||
display: flex;
|
||||
@@ -344,10 +259,10 @@
|
||||
color: $light-color;
|
||||
margin-right: 10rpx;
|
||||
padding: 0 4rpx;
|
||||
border-radius: 2rpx;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.store-seller-name {
|
||||
color: #666;
|
||||
display: flex;
|
||||
@@ -409,7 +324,7 @@
|
||||
padding-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: $font-color-light;
|
||||
|
||||
|
||||
>.price {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
@@ -421,6 +336,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="flex goods-col">
|
||||
<div class="goods-img">
|
||||
<u-image width="230rpx" mode="aspectFit" border-radius='16' height="230rpx" :src="item.goodsImage || item.thumbnail">
|
||||
<template #loading><u-loading></u-loading></template>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-detail">
|
||||
@@ -43,43 +43,33 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import commonTpl from '@/components/m-goods-list/common'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lightColor: this.$mainColor,
|
||||
}
|
||||
},
|
||||
mixins: [commonTpl],
|
||||
props: {
|
||||
// 遍历的数据
|
||||
res: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
type:{
|
||||
type:null,
|
||||
default:""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 跳转到商品详情
|
||||
navigateToDetailPage(item) {
|
||||
if(this.type == 'kanJia'){
|
||||
uni.navigateTo({
|
||||
url: `/pages/promotion/bargain/detail?id=${item.id}`,
|
||||
});
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.skuId}&goodsId=${item.goodsId}`,
|
||||
});
|
||||
},
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { useGoodsListCommon } from './common'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
const { navigateToDetailPage } = useGoodsListCommon()
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
res?: any[]
|
||||
type?: string | null
|
||||
}>(), {
|
||||
res: () => [],
|
||||
type: ''
|
||||
})
|
||||
|
||||
// 跳转到商品详情(覆盖公共方法,增加砍价跳转)
|
||||
const navigateToDetailPageOverride = (item: any) => {
|
||||
if(props.type == 'kanJia'){
|
||||
uni.navigateTo({
|
||||
url: `/pages/promotion/bargain/detail?id=${item.id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
navigateToDetailPage({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@@ -87,72 +77,72 @@
|
||||
width: 152rpx;
|
||||
height: 108rpx;
|
||||
}
|
||||
.flex-j-sb {
|
||||
.flex-j-sb {
|
||||
width: 100%;
|
||||
}
|
||||
.goods-row {
|
||||
background: #fff;
|
||||
padding: 16rpx;
|
||||
>.goods-col {
|
||||
display: flex;
|
||||
>.goods-img {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
}
|
||||
>.goods-detail {
|
||||
flex: 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-detail {
|
||||
margin: 0 20rpx;
|
||||
>.title {
|
||||
font-size: $font-base;
|
||||
color: $font-color-dark;
|
||||
line-height: 1.5;
|
||||
height: 86rpx;
|
||||
padding: 10rpx 0 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
padding: 5rpx 0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
font-size: 24rpx;
|
||||
letter-spacing:2rpx;
|
||||
padding-left: 10rpx;
|
||||
|
||||
.count-config-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.price-box {
|
||||
margin-top: 10rpx;
|
||||
.goods-row {
|
||||
background: #fff;
|
||||
padding: 16rpx;
|
||||
>.goods-col {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: $font-color-light;
|
||||
|
||||
>.price {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
|
||||
.price-int {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
>.goods-img {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
}
|
||||
>.goods-detail {
|
||||
flex: 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-detail {
|
||||
margin: 0 20rpx;
|
||||
>.title {
|
||||
font-size: $font-base;
|
||||
color: $font-color-dark;
|
||||
line-height: 1.5;
|
||||
height: 86rpx;
|
||||
padding: 10rpx 0 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
padding: 5rpx 0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
font-size: 24rpx;
|
||||
letter-spacing:2rpx;
|
||||
padding-left: 10rpx;
|
||||
|
||||
.count-config-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.price-box {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: $font-color-light;
|
||||
|
||||
>.price {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
|
||||
.price-int {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,73 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="goods-recommend">{{title ? `--${title}-- `:''}}</div>
|
||||
<view>
|
||||
<view class="goods-recommend">{{title ? `--${title}-- `:''}}</view>
|
||||
<goodsTemplate :res='goodsList' />
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import goodsTemplate from '@/components/m-goods-list/list'
|
||||
import { getGoodsList } from "@/api/goods.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
goodsList: [],
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
pageSize: {
|
||||
type: null,
|
||||
default: 12,
|
||||
},
|
||||
categoryId: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
storeId: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
components:{goodsTemplate},
|
||||
mounted() {
|
||||
this.initGoods();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化商品
|
||||
*/
|
||||
async initGoods() {
|
||||
let submit = JSON.parse(
|
||||
JSON.stringify(
|
||||
Object.assign(this.params, {
|
||||
pageSize: this.pageSize,
|
||||
categoryId: this.categoryId,
|
||||
storeId: this.storeId,
|
||||
})
|
||||
)
|
||||
);
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import goodsTemplate from '@/components/m-goods-list/list.vue'
|
||||
import { getGoodsList } from "@/api/goods.js"
|
||||
|
||||
Object.keys(submit).map((key) => {
|
||||
if (!submit[key] || submit[key].length == 0) {
|
||||
delete submit[key];
|
||||
}
|
||||
});
|
||||
let goodsList = await getGoodsList(submit);
|
||||
this.goodsList.push(...goodsList.data.result.records);
|
||||
},
|
||||
handleClick(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const props = withDefaults(defineProps<{
|
||||
title?: string
|
||||
pageSize?: number | null
|
||||
categoryId?: string | null
|
||||
storeId?: string | null
|
||||
}>(), {
|
||||
title: '',
|
||||
pageSize: 12,
|
||||
categoryId: '',
|
||||
storeId: ''
|
||||
})
|
||||
|
||||
const goodsList = ref<any[]>([])
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initGoods()
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化商品
|
||||
*/
|
||||
const initGoods = async () => {
|
||||
let submit = JSON.parse(
|
||||
JSON.stringify(
|
||||
Object.assign(params.value, {
|
||||
pageSize: props.pageSize,
|
||||
categoryId: props.categoryId,
|
||||
storeId: props.storeId,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
Object.keys(submit).map((key) => {
|
||||
if (!submit[key] || (submit[key] as any).length == 0) {
|
||||
delete submit[key]
|
||||
}
|
||||
})
|
||||
let res = await getGoodsList(submit)
|
||||
goodsList.value.push(...(res.data.result.records as any[]))
|
||||
}
|
||||
|
||||
const handleClick = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -130,4 +120,4 @@ $w_94: 94%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="index">
|
||||
<view class="index">
|
||||
<view v-model="show" class="slot-content">
|
||||
<image @click="downLoad()" class="img" :src="imgUrl" />
|
||||
<div class="canvas-hide">
|
||||
@@ -11,181 +11,153 @@
|
||||
<!-- #endif -->
|
||||
</div>
|
||||
</view>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
||||
// 引入绘制插件
|
||||
import DrawPoster from "@/js_sdk/u-draw-poster";
|
||||
import DrawPoster from "@/js_sdk/u-draw-poster"
|
||||
// 生成二维码
|
||||
import uQRCode from '@/components/Sansnn-uQRCode/uqrcode.js';
|
||||
import logoImg from "@/pages/passport/static/logo-title.png";
|
||||
import myFaceImg from "@/pages/passport/static/missing-face.png";
|
||||
import uQRCode from '@/components/Sansnn-uQRCode/uqrcode.js'
|
||||
import logoImg from "@/pages/passport/static/logo-title.png"
|
||||
import myFaceImg from "@/pages/passport/static/missing-face.png"
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
imgUrl: "", //绘制出来的图片路径
|
||||
show: false, //是否展示模态框
|
||||
dp: {}, //绘制的dp对象,用于存储绘制等一些方法。
|
||||
logo: logoImg, // 本地logo地址
|
||||
myFace: myFaceImg, // 本地默认头像
|
||||
sharingLink: '', // 二维码链接
|
||||
}),
|
||||
props: {
|
||||
/**
|
||||
* 父级传参的数据
|
||||
*/
|
||||
res: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
onUnload() {},
|
||||
onReady() {},
|
||||
methods: {
|
||||
/** 解决微信小程序中图片模糊问题 */
|
||||
// #ifdef MP-WEIXIN
|
||||
st2: (size) => size * 2,
|
||||
// #endif
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
st2: (size) => size,
|
||||
// #endif
|
||||
const props = defineProps<{
|
||||
res?: any
|
||||
}>()
|
||||
|
||||
/** 保存图片 */
|
||||
downLoad() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: this.imgUrl,
|
||||
success: function () {
|
||||
uni.showToast({title: "保存成功!",icon: "none",});
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({title: "保存失败,请稍后重试!",icon: "none",});
|
||||
},
|
||||
});
|
||||
},
|
||||
/** 创建canvas */
|
||||
async init() {
|
||||
this.show = true;
|
||||
this.dp = await DrawPoster.build({
|
||||
selector: "canvas",
|
||||
componentThis: this,
|
||||
loading: true,
|
||||
debugging: true,
|
||||
});
|
||||
let dp = this.dp;
|
||||
// #ifdef MP-WEIXIN
|
||||
// 用于微信小程序中画布错乱问题
|
||||
dp.canvas.width = this.st2(560);
|
||||
dp.canvas.height = this.st2(800);
|
||||
// #endif
|
||||
this.showQRCode(dp);
|
||||
},
|
||||
/** 生成二维码 */
|
||||
async showQRCode(dp){
|
||||
await uQRCode.make({
|
||||
canvasId: 'canvas', // canvas画布
|
||||
componentInstance: this,
|
||||
text: this.res.bottom.code, // 二维码内容
|
||||
size: 130, // 二维码大小
|
||||
margin: 5, // 二维码内边距
|
||||
backgroundColor: '#ffffff', // ! 二维码背景颜色
|
||||
foregroundColor: '#000000', // !二维码色块颜色
|
||||
fileType: 'jpg',
|
||||
errorCorrectLevel: 0, // <== 关键 容错率,M:0,L:1,H:2,Q:3,
|
||||
// errorCorrectLevel: uQRCode.errorCorrectLevel.M, // <== 关键 容错率,M:0,L:1,H:2,Q:3,
|
||||
success: res => {
|
||||
this.sharingLink = res; // res => 图片路径
|
||||
// 对画布进行绘制
|
||||
this.draw(dp);
|
||||
},
|
||||
fail: res => {
|
||||
// console.log('失败',res)
|
||||
}
|
||||
const imgUrl = ref("")
|
||||
const show = ref(false)
|
||||
const dp = ref<any>({})
|
||||
const logo = logoImg
|
||||
const myFace = myFaceImg
|
||||
const sharingLink = ref('')
|
||||
|
||||
/** 解决微信小程序中图片模糊问题 */
|
||||
// #ifdef MP-WEIXIN
|
||||
const st2 = (size: number) => size * 2
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
const st2 = (size: number) => size
|
||||
// #endif
|
||||
|
||||
/** 保存图片 */
|
||||
const downLoad = () => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: imgUrl.value,
|
||||
success: function () {
|
||||
uni.showToast({title: "保存成功!",icon: "none"})
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({title: "保存失败,请稍后重试!",icon: "none"})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** 创建canvas */
|
||||
const init = async () => {
|
||||
show.value = true
|
||||
dp.value = await DrawPoster.build({
|
||||
selector: "canvas",
|
||||
componentThis: instance?.proxy,
|
||||
loading: true,
|
||||
debugging: true,
|
||||
})
|
||||
let dpVal = dp.value
|
||||
// #ifdef MP-WEIXIN
|
||||
// 用于微信小程序中画布错乱问题
|
||||
dpVal.canvas.width = st2(560)
|
||||
dpVal.canvas.height = st2(800)
|
||||
// #endif
|
||||
await showQRCode(dpVal)
|
||||
}
|
||||
|
||||
/** 生成二维码 */
|
||||
const showQRCode = async (dp: any) => {
|
||||
await uQRCode.make({
|
||||
canvasId: 'canvas',
|
||||
componentInstance: instance?.proxy,
|
||||
text: props.res.bottom.code,
|
||||
size: 130,
|
||||
margin: 5,
|
||||
backgroundColor: '#ffffff',
|
||||
foregroundColor: '#000000',
|
||||
fileType: 'jpg',
|
||||
errorCorrectLevel: 0,
|
||||
success: (res: any) => {
|
||||
sharingLink.value = res
|
||||
draw(dp)
|
||||
},
|
||||
fail: (_res: any) => {
|
||||
// console.log('失败',res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const draw = async (dp: any) => {
|
||||
const { face, nickName, desc } = props.res.memberInfo
|
||||
const { width, height, background, title } = props.res.container
|
||||
const { img, price } = props.res.bottom
|
||||
|
||||
/** 绘制背景 */
|
||||
await dp.draw((ctx: any) => {
|
||||
ctx.fillStyle = background
|
||||
ctx.fillRoundRect(st2(0), st2(0), st2(width), st2(height), st2(12))
|
||||
ctx.clip()
|
||||
})
|
||||
/** 绘制圆角矩形 */
|
||||
await dp.draw(async (ctx: any) => {
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.strokeStyle = "#ffffff"
|
||||
ctx.lineWidth = st2(1)
|
||||
ctx.fillRoundRect(30, 150, 500, 620, 0)
|
||||
})
|
||||
/** 绘制图片 */
|
||||
dp.draw(async (ctx: any) => {
|
||||
await Promise.all([
|
||||
ctx.drawImage(face ? face : myFace, st2(30), st2(30), st2(90), st2(90)),
|
||||
ctx.drawImage(img, st2(60), st2(170), st2(440), st2(440)),
|
||||
ctx.drawImage(sharingLink.value, st2(375), st2(625), st2(130), st2(130))
|
||||
])
|
||||
})
|
||||
/** 绘制顶部文字(昵称 简述) */
|
||||
await dp.draw((ctx: any) => {
|
||||
ctx.fillStyle = "#666"
|
||||
ctx.font = `${st2(24)}px PingFang SC`
|
||||
ctx.fillText(nickName, st2(150), st2(65))
|
||||
ctx.fillStyle = "#666"
|
||||
ctx.font = `${st2(24)}px PingFang SC`
|
||||
ctx.fillText(desc, st2(150), st2(105))
|
||||
})
|
||||
/** 绘制中间文字(商品名称 价格) */
|
||||
await dp.draw((ctx: any) => {
|
||||
ctx.fillStyle = "#333"
|
||||
ctx.font = `bold ${st2(24)}px PingFang SC`
|
||||
ctx.textAlign = "left"
|
||||
ctx.fillWarpText({
|
||||
text: title,
|
||||
lineHeight: st2(32),
|
||||
maxWidth: st2(280),
|
||||
x: st2(60),
|
||||
y: st2(710),
|
||||
layer: 2,
|
||||
})
|
||||
},
|
||||
async draw(dp) {
|
||||
const { face, nickName, desc } = this.res.memberInfo;
|
||||
const { width, height, background, title } = this.res.container;
|
||||
const { code, img, price } = this.res.bottom;
|
||||
ctx.fillStyle = "#ff3c2a"
|
||||
ctx.font = `${st2(38)}px PingFang SC`
|
||||
ctx.textAlign = "left"
|
||||
ctx.fillText(price, st2(60), st2(665))
|
||||
})
|
||||
|
||||
/** 绘制背景 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = background;
|
||||
ctx.fillRoundRect( this.st2(0), this.st2(0), this.st2(width), this.st2(height), this.st2(12));
|
||||
ctx.clip();
|
||||
});
|
||||
/** 绘制圆角矩形 */
|
||||
await dp.draw(async (ctx)=>{
|
||||
// 设置矩形色彩
|
||||
ctx.fillStyle = "#ffffff";
|
||||
// 设置图形轮廓的颜色。默认情况下,线条和填充颜色都是黑色(CSS 颜色值 #000000)
|
||||
ctx.strokeStyle = "#ffffff";
|
||||
// 这个属性设置当前绘线的粗细。属性值必须为正数。描述线段宽度的数字。 0、 负数、 Infinity 和 NaN 会被忽略。默认值是1.0。
|
||||
ctx.lineWidth = this.st2(1);
|
||||
// 进行绘制
|
||||
ctx.fillRoundRect(30, 150, 500, 620, 0);
|
||||
// ctx.strokeRoundRect(30, 150, 500, 620, 0);
|
||||
// ctx.strokeRect(30, 150, 500, 620, 0);
|
||||
})
|
||||
/** 绘制图片 */
|
||||
dp.draw(async (ctx) => {
|
||||
await Promise.all([
|
||||
// 绘制头像face
|
||||
ctx.drawImage(face?face:this.myFace, this.st2(30), this.st2(30), this.st2(90), this.st2(90)),
|
||||
// 绘制Logo
|
||||
// ctx.drawImage( this.logo, this.st2(175), this.st2(0), this.st2(256), this.st2(144)),
|
||||
// 中间图片
|
||||
ctx.drawImage(img, this.st2(60), this.st2(170), this.st2(440), this.st2(440)),
|
||||
// 二维码
|
||||
ctx.drawImage(this.sharingLink, this.st2(375), this.st2(625), this.st2(130), this.st2(130))
|
||||
]);
|
||||
});
|
||||
/** 绘制顶部文字(昵称 简述) */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText(nickName, this.st2(150), this.st2(65));
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText(desc, this.st2(150), this.st2(105));
|
||||
});
|
||||
/** 绘制中间文字(商品名称 价格) */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = `bold ${this.st2(24)}px PingFang SC`;
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillWarpText({
|
||||
text: title,
|
||||
lineHeight: this.st2(32),
|
||||
maxWidth: this.st2(280),
|
||||
x: this.st2(60),
|
||||
y: this.st2(710),
|
||||
layer: 2,
|
||||
});
|
||||
ctx.fillStyle = "#ff3c2a";
|
||||
ctx.font = `${this.st2(38)}px PingFang SC`;
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(price, this.st2(60), this.st2(665));
|
||||
});
|
||||
/** 绘制底部文字 */
|
||||
// await dp.draw((ctx) => {
|
||||
// ctx.fillStyle = "#666";
|
||||
// ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
// ctx.fillText("长按图片,识别二维码", this.st2(200), this.st2(866));
|
||||
// ctx.fillStyle = "#666";
|
||||
// ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
// ctx.fillText("查看商品详情", this.st2(200), this.st2(900));
|
||||
// });
|
||||
|
||||
// 绘制生成本地地址
|
||||
this.imgUrl = await dp.createImagePath();
|
||||
},
|
||||
},
|
||||
// 绘制生成本地地址
|
||||
imgUrl.value = await dp.createImagePath()
|
||||
}
|
||||
|
||||
async mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -28,138 +28,122 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mode: {
|
||||
value: Number,
|
||||
default: 1,
|
||||
},
|
||||
//HM修改 定义默认搜索关键词(水印文字)
|
||||
placeholder: {
|
||||
value: String,
|
||||
default: "请输入搜索内容",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 默认半径为60
|
||||
radius: {
|
||||
value: String,
|
||||
default: 60,
|
||||
},
|
||||
// 是否获取焦点
|
||||
isFocusVal: {
|
||||
value: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showClear: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShowSeachGoods: false, //是否显示查询的商品
|
||||
active: false, //是否选中
|
||||
inputVal: "", //Input中内容
|
||||
isDelShow: false, //是否显示右侧删除icon
|
||||
isFocus: false, //是否获取焦点
|
||||
switchLayout: true, //切换当前商品的布局,默认为两列
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isFocus = this.isFocusVal;
|
||||
},
|
||||
methods: {
|
||||
//
|
||||
out() {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/home/index",
|
||||
});
|
||||
},
|
||||
// 切换排列顺序
|
||||
handelListClass() {
|
||||
this.switchLayout = !this.switchLayout;
|
||||
this.$emit("SwitchType");
|
||||
},
|
||||
//HM修改 触发组件confirm事件
|
||||
triggerConfirm() {
|
||||
this.$emit("confirm", false);
|
||||
uni.hideKeyboard();
|
||||
},
|
||||
//HM修改 触发组件input事件
|
||||
inputChange(event) {
|
||||
var keyword = event.detail.value;
|
||||
this.$emit("input", keyword);
|
||||
if (this.inputVal) {
|
||||
this.isDelShow = true;
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
this.active = true;
|
||||
//HM修改 增加获取焦点判断
|
||||
if (this.inputVal) {
|
||||
this.isDelShow = true;
|
||||
}
|
||||
},
|
||||
blur() {
|
||||
this.isFocus = false;
|
||||
if (!this.inputVal) {
|
||||
this.active = false;
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
//HM修改 收起键盘
|
||||
uni.hideKeyboard();
|
||||
this.isFocus = false;
|
||||
this.inputVal = "";
|
||||
this.active = false;
|
||||
//HM修改 清空内容时候触发组件input
|
||||
this.$emit("input", "");
|
||||
//this.$emit('search', '');//HM修改 清空内容时候不进行搜索
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
|
||||
/**
|
||||
* 回退到上一级
|
||||
*/
|
||||
onClickLeft() {
|
||||
const paths = getCurrentPages();
|
||||
console.log(paths)
|
||||
if(paths.length > 1){
|
||||
uni.navigateBack();
|
||||
}else{
|
||||
uni.switchTab({
|
||||
url:"/pages/tabbar/home/index"
|
||||
})
|
||||
}
|
||||
},
|
||||
const props = withDefaults(defineProps<{
|
||||
mode?: number
|
||||
placeholder?: string
|
||||
value?: string
|
||||
radius?: string
|
||||
isFocusVal?: boolean
|
||||
showClear?: boolean
|
||||
}>(), {
|
||||
mode: 1,
|
||||
placeholder: "请输入搜索内容",
|
||||
value: "",
|
||||
radius: "60",
|
||||
isFocusVal: true,
|
||||
showClear: true,
|
||||
})
|
||||
|
||||
/**
|
||||
* 内容为空时,输入默认关键字
|
||||
*/
|
||||
search() {
|
||||
if (!this.inputVal) {
|
||||
if (this.searchName == "取消") {
|
||||
uni.hideKeyboard();
|
||||
this.isFocus = false;
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.$emit("search", this.inputVal ? this.inputVal : this.placeholder);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* 监听当前是否有值 是否显示清除图标
|
||||
*/
|
||||
inputVal(newVal) {
|
||||
newVal ? (this.isDelShow = true) : (this.isDelShow = false);
|
||||
},
|
||||
},
|
||||
};
|
||||
const emit = defineEmits(['confirm', 'input', 'search', 'SwitchType'])
|
||||
|
||||
const isShowSeachGoods = ref(false)
|
||||
const active = ref(false)
|
||||
const inputVal = ref("")
|
||||
const isDelShow = ref(false)
|
||||
const isFocus = ref(false)
|
||||
const switchLayout = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
isFocus.value = props.isFocusVal
|
||||
})
|
||||
|
||||
//
|
||||
const out = () => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/home/index",
|
||||
})
|
||||
}
|
||||
// 切换排列顺序
|
||||
const handelListClass = () => {
|
||||
switchLayout.value = !switchLayout.value
|
||||
emit("SwitchType")
|
||||
}
|
||||
//HM修改 触发组件confirm事件
|
||||
const triggerConfirm = () => {
|
||||
emit("confirm", false)
|
||||
uni.hideKeyboard()
|
||||
}
|
||||
//HM修改 触发组件input事件
|
||||
const inputChange = (event: any) => {
|
||||
var keyword = event.detail.value
|
||||
emit("input", keyword)
|
||||
if (inputVal.value) {
|
||||
isDelShow.value = true
|
||||
}
|
||||
}
|
||||
const focus = () => {
|
||||
active.value = true
|
||||
//HM修改 增加获取焦点判断
|
||||
if (inputVal.value) {
|
||||
isDelShow.value = true
|
||||
}
|
||||
}
|
||||
const blur = () => {
|
||||
isFocus.value = false
|
||||
if (!inputVal.value) {
|
||||
active.value = false
|
||||
}
|
||||
}
|
||||
const clear = () => {
|
||||
//HM修改 收起键盘
|
||||
uni.hideKeyboard()
|
||||
isFocus.value = false
|
||||
inputVal.value = ""
|
||||
active.value = false
|
||||
//HM修改 清空内容时候触发组件input
|
||||
emit("input", "")
|
||||
//this.$emit('search', '');//HM修改 清空内容时候不进行搜索
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退到上一级
|
||||
*/
|
||||
const onClickLeft = () => {
|
||||
const paths = getCurrentPages()
|
||||
console.log(paths)
|
||||
if(paths.length > 1){
|
||||
uni.navigateBack()
|
||||
}else{
|
||||
uni.switchTab({
|
||||
url:"/pages/tabbar/home/index"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容为空时,输入默认关键字
|
||||
*/
|
||||
const search = () => {
|
||||
if (!inputVal.value) {
|
||||
if (false) { // searchName == "取消" - 兼容旧逻辑
|
||||
uni.hideKeyboard()
|
||||
isFocus.value = false
|
||||
active.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
emit("search", inputVal.value ? inputVal.value : props.placeholder)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听当前是否有值 是否显示清除图标
|
||||
*/
|
||||
watch(inputVal, (newVal) => {
|
||||
newVal ? (isDelShow.value = true) : (isDelShow.value = false)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -28,118 +28,125 @@
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
<script>
|
||||
import { h5Copy } from "@/js_sdk/h5-copy/h5-copy.js";
|
||||
import configs from "@/config/config";
|
||||
import mpShare from "@/utils/mpShare.js";
|
||||
import { setClipboard } from "@/utils/filters.js";
|
||||
|
||||
export default {
|
||||
mixins: [mpShare],
|
||||
data() {
|
||||
return {
|
||||
configs,
|
||||
show: true,
|
||||
list: [
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "微信好友",
|
||||
icon: "weixin-fill",
|
||||
type: 0,
|
||||
},
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "朋友圈",
|
||||
icon: "weixin-circle-fill",
|
||||
type: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import configs from "@/config/config"
|
||||
import mpShare from "@/utils/mpShare.js"
|
||||
import { setClipboard } from "@/utils/filters.js"
|
||||
|
||||
// mixin 处理:mpShare 提供小程序分享能力
|
||||
mpShare
|
||||
|
||||
const props = defineProps<{
|
||||
thumbnail?: string
|
||||
goodsName?: string
|
||||
type?: string
|
||||
goodsId?: string | number
|
||||
link?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const show = ref(true)
|
||||
const config = configs
|
||||
const list = ref([
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "微信好友",
|
||||
icon: "weixin-fill",
|
||||
type: 0,
|
||||
},
|
||||
// 图片缩略图、 商品名称 、 type(goods,shop,pintuan) 拼团商品分享以及店铺分享
|
||||
|
||||
props: ["thumbnail", "goodsName", "type", "goodsId", "link"],
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
},
|
||||
weChatShare(){
|
||||
this.$u.mpShare = {
|
||||
title: this.shareTitle(), // 默认为小程序名称,可自定义
|
||||
path: '', // 默认为当前页面路径,一般无需修改,QQ小程序不支持
|
||||
// 分享图标,路径可以是本地文件路径、代码包文件路径或者网络图片路径。
|
||||
// 支持PNG及JPG,默认为当前页面的截图
|
||||
imageUrl: this.thumbnail ||''
|
||||
}
|
||||
},
|
||||
|
||||
// h5复制链接
|
||||
// #ifdef H5
|
||||
copyLink() {
|
||||
let content;
|
||||
if (this.link) {
|
||||
content = this.configs.shareLink + this.link;
|
||||
} else {
|
||||
content =
|
||||
this.configs.shareLink +
|
||||
getCurrentPages()[getCurrentPages().length - 1].__page__.fullPath;
|
||||
}
|
||||
setClipboard(content)
|
||||
},
|
||||
// #endif
|
||||
|
||||
shareTitle() {
|
||||
let shareTitle;
|
||||
if (this.type == "goods") {
|
||||
shareTitle = `[好友推荐]${this.goodsName}快来跟我一起看看吧`;
|
||||
} else if (this.type == "shops") {
|
||||
shareTitle = `[好友发现]${this.goodsName}快来跟我一起看看吧`;
|
||||
} else if (this.type == "pintuan") {
|
||||
shareTitle = `[好友邀请]${this.goodsName}快来跟我一起抢购吧!`;
|
||||
} else if (this.type == "kanjia") {
|
||||
shareTitle = `[好友邀请]请快来帮我砍一刀${this.goodsName}`;
|
||||
}
|
||||
return shareTitle;
|
||||
},
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
handleShare(val) {
|
||||
console.log("12312312")
|
||||
if (val.type <= 1) {
|
||||
let scene; // "WXSenceTimeline 朋友圈 WXSceneSession 微信好友"
|
||||
val.type == 1
|
||||
? (scene = "WXSenceTimeline")
|
||||
: (scene = "WXSceneSession");
|
||||
uni.share({
|
||||
provider: "weixin",
|
||||
scene: scene,
|
||||
href: configs.shareLink + this.link,
|
||||
imageUrl: this.thumbnail,
|
||||
type: 0,
|
||||
summary: this.goodsName,
|
||||
title: this.shareTitle(),
|
||||
success: function (res) {
|
||||
uni.showToast({
|
||||
title: "分享成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
this.$emit("close");
|
||||
},
|
||||
fail: function (err) {
|
||||
uni.showToast({
|
||||
title: "分享失败!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
this.$emit("close");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "朋友圈",
|
||||
icon: "weixin-circle-fill",
|
||||
type: 1,
|
||||
},
|
||||
};
|
||||
])
|
||||
|
||||
const close = () => {
|
||||
emit("close")
|
||||
}
|
||||
|
||||
const weChatShare = () => {
|
||||
// @ts-ignore
|
||||
const proxy = getCurrentInstance()?.proxy
|
||||
if (proxy) {
|
||||
// @ts-ignore
|
||||
proxy.$u.mpShare = {
|
||||
title: shareTitle(),
|
||||
path: '',
|
||||
imageUrl: props.thumbnail || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// h5复制链接
|
||||
// #ifdef H5
|
||||
const copyLink = () => {
|
||||
let content: string
|
||||
if (props.link) {
|
||||
content = config.shareLink + props.link
|
||||
} else {
|
||||
content =
|
||||
config.shareLink +
|
||||
getCurrentPages()[getCurrentPages().length - 1].__page__.fullPath
|
||||
}
|
||||
setClipboard(content)
|
||||
}
|
||||
// #endif
|
||||
|
||||
const shareTitle = (): string => {
|
||||
let shareTitle: string = ''
|
||||
if (props.type == "goods") {
|
||||
shareTitle = `[好友推荐]${props.goodsName}快来跟我一起看看吧`
|
||||
} else if (props.type == "shops") {
|
||||
shareTitle = `[好友发现]${props.goodsName}快来跟我一起看看吧`
|
||||
} else if (props.type == "pintuan") {
|
||||
shareTitle = `[好友邀请]${props.goodsName}快来跟我一起抢购吧!`
|
||||
} else if (props.type == "kanjia") {
|
||||
shareTitle = `[好友邀请]请快来帮我砍一刀${props.goodsName}`
|
||||
}
|
||||
return shareTitle
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const handleShare = (val: any) => {
|
||||
console.log("12312312")
|
||||
if (val.type <= 1) {
|
||||
let scene: string // "WXSenceTimeline 朋友圈 WXSceneSession 微信好友"
|
||||
val.type == 1
|
||||
? (scene = "WXSenceTimeline")
|
||||
: (scene = "WXSceneSession")
|
||||
uni.share({
|
||||
provider: "weixin",
|
||||
scene: scene,
|
||||
href: config.shareLink + props.link,
|
||||
imageUrl: props.thumbnail,
|
||||
type: 0,
|
||||
summary: props.goodsName,
|
||||
title: shareTitle(),
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "分享成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
})
|
||||
emit("close")
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "分享失败!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
})
|
||||
emit("close")
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./mp-share.scss";
|
||||
@@ -175,4 +182,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<view>
|
||||
<u-popup v-model:show="show" mode="bottom" height="800rpx" border-radius="14">
|
||||
<div class="wrapper">
|
||||
<view class="wrapper">
|
||||
<view class="down-goods-tips">该商品已下架</view>
|
||||
<scroll-view scroll-y="true" style="height: 670rpx">
|
||||
<goodsRecommend title="其他商品" />
|
||||
</scroll-view>
|
||||
</div>
|
||||
</view>
|
||||
</u-popup>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import goodsRecommend from "@/components/m-goods-recommend/index.vue";
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import goodsRecommend from "@/components/m-goods-recommend/index.vue"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: true, // 是否显示
|
||||
};
|
||||
},
|
||||
components: { goodsRecommend },
|
||||
};
|
||||
const show = ref(true)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -10,198 +10,175 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
maskBg: {
|
||||
type: String,
|
||||
default: "rgba(0,0,0,0)",
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: "default", //default top-start top-end bottom-start bottom-end
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: "column", //column row
|
||||
},
|
||||
x: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: "light", //light dark
|
||||
},
|
||||
dynamic: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
triangle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popupsTop: "0rpx",
|
||||
popupsLeft: "0rpx",
|
||||
show: false,
|
||||
dynPlace: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.popupsPosition();
|
||||
},
|
||||
methods: {
|
||||
tapMask() {
|
||||
this.$emit("input", !this.value);
|
||||
},
|
||||
tapItem(item) {
|
||||
if (item.disabled) return;
|
||||
this.$emit("tapPopup", item);
|
||||
this.$emit("input", !this.value);
|
||||
},
|
||||
getStatusBar() {
|
||||
const windowInfo = uni.getWindowInfo();
|
||||
// #ifdef H5
|
||||
return Promise.resolve(
|
||||
(windowInfo.statusBarHeight || 0) + (windowInfo.windowTop || 0)
|
||||
);
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return Promise.resolve(windowInfo.statusBarHeight || 0);
|
||||
// #endif
|
||||
},
|
||||
async popupsPosition() {
|
||||
let statusBar = await this.getStatusBar();
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let popupsDom = uni.createSelectorQuery().in(this).select(".popups");
|
||||
popupsDom
|
||||
.fields(
|
||||
{
|
||||
size: true,
|
||||
},
|
||||
(data) => {
|
||||
if (!data) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
let width = data.width;
|
||||
let height = data.height;
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, getCurrentInstance } from 'vue'
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
maskBg?: string
|
||||
placement?: string
|
||||
direction?: string
|
||||
x?: number
|
||||
y?: number
|
||||
modelValue?: boolean
|
||||
popData?: any[]
|
||||
theme?: string
|
||||
dynamic?: boolean
|
||||
gap?: number
|
||||
triangle?: boolean
|
||||
}>(), {
|
||||
maskBg: "rgba(0,0,0,0)",
|
||||
placement: "default",
|
||||
direction: "column",
|
||||
x: 0,
|
||||
y: 0,
|
||||
modelValue: false,
|
||||
popData: () => [],
|
||||
theme: "light",
|
||||
dynamic: false,
|
||||
gap: 20,
|
||||
triangle: true,
|
||||
})
|
||||
|
||||
let y = this.dynamic
|
||||
? this.dynamicGetY(this.y, this.gap)
|
||||
: this.transformRpx(this.y);
|
||||
const emit = defineEmits(['update:modelValue', 'tapPopup'])
|
||||
|
||||
let x = this.dynamic
|
||||
? this.dynamicGetX(this.x, this.gap)
|
||||
: this.transformRpx(this.x);
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// #ifdef H5
|
||||
y = this.dynamic
|
||||
? this.y + statusBar
|
||||
: this.transformRpx(this.y + statusBar);
|
||||
// #endif
|
||||
const popupsTop = ref("0rpx")
|
||||
const popupsLeft = ref("0rpx")
|
||||
const show = ref(false)
|
||||
const dynPlace = ref("")
|
||||
|
||||
this.dynPlace =
|
||||
this.placement == "default"
|
||||
? this.getPlacement(x, y)
|
||||
: this.placement;
|
||||
onMounted(() => {
|
||||
popupsPosition()
|
||||
})
|
||||
|
||||
switch (this.dynPlace) {
|
||||
case "top-start":
|
||||
this.popupsTop = `${y + 9}rpx`;
|
||||
this.popupsLeft = `${x - 15}rpx`;
|
||||
break;
|
||||
case "top-end":
|
||||
this.popupsTop = `${y + 9}rpx`;
|
||||
this.popupsLeft = `${x + 15 - width}rpx`;
|
||||
break;
|
||||
case "bottom-start":
|
||||
this.popupsTop = `${y - 18 - height}rpx`;
|
||||
this.popupsLeft = `${x - 15}rpx`;
|
||||
break;
|
||||
case "bottom-end":
|
||||
this.popupsTop = `${y - 9 - height}rpx`;
|
||||
this.popupsLeft = `${x + 15 - width}rpx`;
|
||||
break;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
)
|
||||
.exec();
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
getPlacement(x, y) {
|
||||
const { windowWidth: width, windowHeight: height } = uni.getWindowInfo();
|
||||
if (x > width / 2 && y > height / 2) {
|
||||
return "bottom-end";
|
||||
} else if (x < width / 2 && y < height / 2) {
|
||||
return "top-start";
|
||||
} else if (x > width / 2 && y < height / 2) {
|
||||
return "top-end";
|
||||
} else if (x < width / 2 && y > height / 2) {
|
||||
return "bottom-start";
|
||||
} else if (x > width / 2) {
|
||||
return "top-end";
|
||||
} else {
|
||||
return "top-start";
|
||||
}
|
||||
},
|
||||
dynamicGetY(y, gap) {
|
||||
const { windowHeight: height } = uni.getWindowInfo();
|
||||
y = y < gap ? gap : y;
|
||||
y = height - y < gap ? height - gap : y;
|
||||
const tapMask = () => {
|
||||
emit("update:modelValue", !props.modelValue)
|
||||
}
|
||||
|
||||
return y;
|
||||
},
|
||||
dynamicGetX(x, gap) {
|
||||
const { windowWidth: width } = uni.getWindowInfo();
|
||||
x = x < gap ? gap : x;
|
||||
x = width - x < gap ? width - gap : x;
|
||||
return x;
|
||||
},
|
||||
transformRpx(params) {
|
||||
const { screenWidth } = uni.getWindowInfo();
|
||||
return (params * screenWidth) / 375;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: async function (newVal, oldVal) {
|
||||
if (newVal) await this.popupsPosition();
|
||||
this.show = newVal;
|
||||
},
|
||||
},
|
||||
placement: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
this.dynPlace = newVal;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const tapItem = (item: any) => {
|
||||
if (item.disabled) return
|
||||
emit("tapPopup", item)
|
||||
emit("update:modelValue", !props.modelValue)
|
||||
}
|
||||
|
||||
const getStatusBar = () => {
|
||||
const windowInfo = uni.getWindowInfo()
|
||||
// #ifdef H5
|
||||
return Promise.resolve(
|
||||
(windowInfo.statusBarHeight || 0) + (windowInfo.windowTop || 0)
|
||||
)
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return Promise.resolve(windowInfo.statusBarHeight || 0)
|
||||
// #endif
|
||||
}
|
||||
|
||||
const popupsPosition = async () => {
|
||||
let statusBar = await getStatusBar()
|
||||
await new Promise<void>((resolve) => {
|
||||
let popupsDom = uni.createSelectorQuery().in(instance!.proxy).select(".popups")
|
||||
popupsDom
|
||||
.fields(
|
||||
{
|
||||
size: true,
|
||||
},
|
||||
(data: any) => {
|
||||
if (!data) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
let width = data.width
|
||||
let height = data.height
|
||||
|
||||
let y = props.dynamic
|
||||
? dynamicGetY(props.y, props.gap)
|
||||
: transformRpx(props.y)
|
||||
|
||||
let x = props.dynamic
|
||||
? dynamicGetX(props.x, props.gap)
|
||||
: transformRpx(props.x)
|
||||
|
||||
// #ifdef H5
|
||||
y = props.dynamic
|
||||
? props.y + statusBar
|
||||
: transformRpx(props.y + statusBar)
|
||||
// #endif
|
||||
|
||||
dynPlace.value =
|
||||
props.placement == "default"
|
||||
? getPlacement(x, y)
|
||||
: props.placement
|
||||
|
||||
switch (dynPlace.value) {
|
||||
case "top-start":
|
||||
popupsTop.value = `${y + 9}rpx`
|
||||
popupsLeft.value = `${x - 15}rpx`
|
||||
break
|
||||
case "top-end":
|
||||
popupsTop.value = `${y + 9}rpx`
|
||||
popupsLeft.value = `${x + 15 - width}rpx`
|
||||
break
|
||||
case "bottom-start":
|
||||
popupsTop.value = `${y - 18 - height}rpx`
|
||||
popupsLeft.value = `${x - 15}rpx`
|
||||
break
|
||||
case "bottom-end":
|
||||
popupsTop.value = `${y - 9 - height}rpx`
|
||||
popupsLeft.value = `${x + 15 - width}rpx`
|
||||
break
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
.exec()
|
||||
})
|
||||
}
|
||||
|
||||
const getPlacement = (x: number, y: number) => {
|
||||
const { windowWidth: width, windowHeight: height } = uni.getWindowInfo()
|
||||
if (x > width / 2 && y > height / 2) {
|
||||
return "bottom-end"
|
||||
} else if (x < width / 2 && y < height / 2) {
|
||||
return "top-start"
|
||||
} else if (x > width / 2 && y < height / 2) {
|
||||
return "top-end"
|
||||
} else if (x < width / 2 && y > height / 2) {
|
||||
return "bottom-start"
|
||||
} else if (x > width / 2) {
|
||||
return "top-end"
|
||||
} else {
|
||||
return "top-start"
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicGetY = (y: number, gap: number) => {
|
||||
const { windowHeight: height } = uni.getWindowInfo()
|
||||
y = y < gap ? gap : y
|
||||
y = height - y < gap ? height - gap : y
|
||||
return y
|
||||
}
|
||||
|
||||
const dynamicGetX = (x: number, gap: number) => {
|
||||
const { windowWidth: width } = uni.getWindowInfo()
|
||||
x = x < gap ? gap : x
|
||||
x = width - x < gap ? width - gap : x
|
||||
return x
|
||||
}
|
||||
|
||||
const transformRpx = (params: number) => {
|
||||
const { screenWidth } = uni.getWindowInfo()
|
||||
return (params * screenWidth) / 375
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, async (newVal) => {
|
||||
if (newVal) await popupsPosition()
|
||||
show.value = newVal
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => props.placement, (newVal) => {
|
||||
dynPlace.value = newVal
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -11,29 +11,21 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UTimeLineItem',
|
||||
props: {
|
||||
nodeTop: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nodeStyle() {
|
||||
if (this.nodeTop === '' || this.nodeTop === null || this.nodeTop === undefined) {
|
||||
return {}
|
||||
}
|
||||
const top = typeof this.nodeTop === 'number' ? `${this.nodeTop}rpx` : this.nodeTop
|
||||
return { top }
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
nodeTop?: string | number
|
||||
bgColor?: string
|
||||
}>()
|
||||
|
||||
const nodeStyle = computed(() => {
|
||||
if (props.nodeTop === '' || props.nodeTop === null || props.nodeTop === undefined) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
const top = typeof props.nodeTop === 'number' ? `${props.nodeTop}rpx` : props.nodeTop
|
||||
return { top }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UTimeLine'
|
||||
}
|
||||
<script setup lang="ts">
|
||||
// UTimeLine 组件
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -26,42 +26,28 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uni-load-more',
|
||||
props: {
|
||||
status: {
|
||||
//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
|
||||
type: String,
|
||||
default: 'more'
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showText: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#777777'
|
||||
},
|
||||
contentText: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了'
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
status?: string
|
||||
showIcon?: boolean
|
||||
showText?: boolean
|
||||
color?: string
|
||||
contentText?: {
|
||||
contentdown: string
|
||||
contentrefresh: string
|
||||
contentnomore: string
|
||||
}
|
||||
};
|
||||
}>(), {
|
||||
status: 'more',
|
||||
showIcon: true,
|
||||
showText: true,
|
||||
color: '#777777',
|
||||
contentText: () => ({
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了',
|
||||
}),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -10,211 +10,183 @@
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* NumberBox 数字输入框
|
||||
* @description 带加减按钮的数字输入框
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
|
||||
* @property {Number} value 输入框当前值
|
||||
* @property {Number} min 最小值
|
||||
* @property {Number} max 最大值
|
||||
* @property {Number} step 每次点击改变的间隔大小
|
||||
* @property {String} background 背景色
|
||||
* @property {String} color 字体颜色(前景色)
|
||||
* @property {Boolean} disabled = [true|false] 是否为禁用状态
|
||||
* @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
|
||||
* @event {Function} focus 输入框聚焦时触发的事件,参数为 event 对象
|
||||
* @event {Function} blur 输入框失焦时触发的事件,参数为 event 对象
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "UniNumberBox",
|
||||
emits: ['change', 'input', 'update:modelValue', 'blur', 'focus'],
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#f5f5f5'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: 0
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.inputValue = +val;
|
||||
},
|
||||
modelValue(val) {
|
||||
this.inputValue = +val;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.value === 1) {
|
||||
this.inputValue = +this.modelValue;
|
||||
}
|
||||
if (this.modelValue === 1) {
|
||||
this.inputValue = +this.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_calcValue(type) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
const scale = this._getDecimalScale();
|
||||
let value = this.inputValue * scale;
|
||||
let step = this.step * scale;
|
||||
if (type === "minus") {
|
||||
value -= step;
|
||||
if (value < (this.min * scale)) {
|
||||
return;
|
||||
}
|
||||
if (value > (this.max * scale)) {
|
||||
value = this.max * scale
|
||||
}
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
if (type === "plus") {
|
||||
value += step;
|
||||
if (value > (this.max * scale)) {
|
||||
return;
|
||||
}
|
||||
if (value < (this.min * scale)) {
|
||||
value = this.min * scale
|
||||
}
|
||||
}
|
||||
const props = withDefaults(defineProps<{
|
||||
value?: number | string
|
||||
modelValue?: number | string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
background?: string
|
||||
color?: string
|
||||
disabled?: boolean
|
||||
}>(), {
|
||||
value: 1,
|
||||
modelValue: 1,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
background: '#f5f5f5',
|
||||
color: '#333',
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
this.inputValue = (value / scale).toFixed(String(scale).length - 1);
|
||||
this.$emit("change", +this.inputValue);
|
||||
// TODO vue2 兼容
|
||||
this.$emit("input", +this.inputValue);
|
||||
// TODO vue3 兼容
|
||||
this.$emit("update:modelValue", +this.inputValue);
|
||||
},
|
||||
_getDecimalScale() {
|
||||
const emit = defineEmits<{
|
||||
change: [value: number]
|
||||
input: [value: number]
|
||||
'update:modelValue': [value: number]
|
||||
blur: [event: Event]
|
||||
focus: [event: Event]
|
||||
}>()
|
||||
|
||||
let scale = 1;
|
||||
// 浮点型
|
||||
if (~~this.step !== this.step) {
|
||||
scale = Math.pow(10, String(this.step).split(".")[1].length);
|
||||
}
|
||||
return scale;
|
||||
},
|
||||
_onBlur(event) {
|
||||
this.$emit('blur', event)
|
||||
let value = event.detail.value;
|
||||
if (!value) {
|
||||
// this.inputValue = 0;
|
||||
return;
|
||||
}
|
||||
value = +value;
|
||||
if (value > this.max) {
|
||||
value = this.max;
|
||||
} else if (value < this.min) {
|
||||
value = this.min;
|
||||
}
|
||||
const scale = this._getDecimalScale();
|
||||
this.inputValue = value.toFixed(String(scale).length - 1);
|
||||
this.$emit("change", +this.inputValue);
|
||||
this.$emit("input", +this.inputValue);
|
||||
},
|
||||
_onFocus(event) {
|
||||
this.$emit('focus', event)
|
||||
}
|
||||
const inputValue = ref(0)
|
||||
|
||||
watch(() => props.value, (val) => {
|
||||
inputValue.value = +val
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
inputValue.value = +val
|
||||
})
|
||||
|
||||
if (props.value === 1) {
|
||||
inputValue.value = +props.modelValue
|
||||
}
|
||||
if (props.modelValue === 1) {
|
||||
inputValue.value = +props.value
|
||||
}
|
||||
|
||||
function _getDecimalScale() {
|
||||
let scale = 1
|
||||
if (~~props.step !== props.step) {
|
||||
scale = Math.pow(10, String(props.step).split('.')[1].length)
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
function _emitValue() {
|
||||
const val = +inputValue.value
|
||||
emit('change', val)
|
||||
emit('input', val)
|
||||
emit('update:modelValue', val)
|
||||
}
|
||||
|
||||
function _calcValue(type: 'minus' | 'plus') {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
const scale = _getDecimalScale()
|
||||
let value = inputValue.value * scale
|
||||
const step = props.step * scale
|
||||
if (type === 'minus') {
|
||||
value -= step
|
||||
if (value < props.min * scale) {
|
||||
return
|
||||
}
|
||||
};
|
||||
if (value > props.max * scale) {
|
||||
value = props.max * scale
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'plus') {
|
||||
value += step
|
||||
if (value > props.max * scale) {
|
||||
return
|
||||
}
|
||||
if (value < props.min * scale) {
|
||||
value = props.min * scale
|
||||
}
|
||||
}
|
||||
|
||||
inputValue.value = Number((value / scale).toFixed(String(scale).length - 1))
|
||||
_emitValue()
|
||||
}
|
||||
|
||||
function _onBlur(event: any) {
|
||||
emit('blur', event)
|
||||
let value = event.detail.value
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
value = +value
|
||||
if (value > props.max) {
|
||||
value = props.max
|
||||
} else if (value < props.min) {
|
||||
value = props.min
|
||||
}
|
||||
const scale = _getDecimalScale()
|
||||
inputValue.value = Number(value.toFixed(String(scale).length - 1))
|
||||
_emitValue()
|
||||
}
|
||||
|
||||
function _onFocus(event: Event) {
|
||||
emit('focus', event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$box-height: 48rpx;
|
||||
$bg: #f5f5f5;
|
||||
$br: 4rpx;
|
||||
$color: #333;
|
||||
$box-height: 48rpx;
|
||||
$bg: #f5f5f5;
|
||||
$br: 4rpx;
|
||||
$color: #333;
|
||||
|
||||
.uni-numbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
.uni-numbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-numbox-btns {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
background-color: $bg;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
.uni-numbox-btns {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
background-color: $bg;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-numbox__value {
|
||||
margin: 0 4rpx;
|
||||
background-color: $bg;
|
||||
width: 70rpx;
|
||||
height: $box-height;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
color: $color;
|
||||
}
|
||||
.uni-numbox__value {
|
||||
margin: 0 4rpx;
|
||||
background-color: $bg;
|
||||
width: 70rpx;
|
||||
height: $box-height;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.uni-numbox__minus {
|
||||
border-top-left-radius: $br;
|
||||
border-bottom-left-radius: $br;
|
||||
}
|
||||
.uni-numbox__minus {
|
||||
border-top-left-radius: $br;
|
||||
border-bottom-left-radius: $br;
|
||||
}
|
||||
|
||||
.uni-numbox__plus {
|
||||
border-top-right-radius: $br;
|
||||
border-bottom-right-radius: $br;
|
||||
}
|
||||
.uni-numbox__plus {
|
||||
border-top-right-radius: $br;
|
||||
border-bottom-right-radius: $br;
|
||||
}
|
||||
|
||||
.uni-numbox--text {
|
||||
// fix nvue
|
||||
line-height: 40rpx;
|
||||
.uni-numbox--text {
|
||||
line-height: 40rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 300;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
font-size: 40rpx;
|
||||
font-weight: 300;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.uni-numbox .uni-numbox--disabled {
|
||||
color: #c0c0c0 !important;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
.uni-numbox .uni-numbox--disabled {
|
||||
color: #c0c0c0 !important;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,111 +1,99 @@
|
||||
<template></template>
|
||||
<script>
|
||||
import { getAddressCode } from "@/api/address";
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { getAddressCode } from '@/api/address'
|
||||
import config from '@/config/config'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
// 初始化地图并且调用
|
||||
initMap() {
|
||||
let that = this;
|
||||
uni.chooseLocation({
|
||||
success: function (res) {
|
||||
/**获取地址详情地址 */
|
||||
that.posToCity(res.latitude, res.longitude).then((val) => {
|
||||
/**获取地址code */
|
||||
getAddressCode(
|
||||
val.regeocode.addressComponent.citycode,
|
||||
val.regeocode.addressComponent.township
|
||||
).then((code) => {
|
||||
that.$emit("callback", { ...val, ...res, ...code });
|
||||
that.$emit("close");
|
||||
});
|
||||
});
|
||||
},
|
||||
fail(e) {
|
||||
console.log(e)
|
||||
that.$emit("close");
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 根据当前客户端判断展示不同类型数据
|
||||
init() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.wechatMap();
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.initMap();
|
||||
// #endif
|
||||
},
|
||||
const emit = defineEmits<{
|
||||
callback: [payload: Record<string, any>]
|
||||
close: []
|
||||
}>()
|
||||
|
||||
// 如果是微信小程序单独走微信小程序授权模式
|
||||
wechatMap() {
|
||||
let that = this;
|
||||
uni.authorize({
|
||||
scope: "scope.userLocation",
|
||||
success() {
|
||||
// 允许授权
|
||||
that.initMap();
|
||||
},
|
||||
fail() {
|
||||
//拒绝授权
|
||||
uni.showModal({
|
||||
content: "检测到您没打开获取地址功能权限,是否去设置打开?",
|
||||
confirmText: "确认",
|
||||
cancelText: "取消",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 打开设置好后重新刷新地图
|
||||
uni.openSetting({
|
||||
success: (res) => {
|
||||
that.initMap();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 取消后关闭
|
||||
that.$emit("close");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
return false;
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取城市的数据
|
||||
posToCity(latitude, longitude) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `https://restapi.amap.com/v3/geocode/regeo`,
|
||||
method: "GET",
|
||||
data: {
|
||||
key: config.aMapKey, //web服务的key
|
||||
location: `${longitude},${latitude}`,
|
||||
},
|
||||
success: ({ data }) => {
|
||||
const { status, info } = data;
|
||||
if (status === "1") {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(info);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
function initMap() {
|
||||
uni.chooseLocation({
|
||||
success(res) {
|
||||
posToCity(res.latitude, res.longitude).then((val) => {
|
||||
getAddressCode(
|
||||
val.regeocode.addressComponent.citycode,
|
||||
val.regeocode.addressComponent.township,
|
||||
).then((code) => {
|
||||
emit('callback', { ...val, ...res, ...code })
|
||||
emit('close')
|
||||
})
|
||||
})
|
||||
},
|
||||
fail(e) {
|
||||
console.log(e)
|
||||
emit('close')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function init() {
|
||||
// #ifdef MP-WEIXIN
|
||||
wechatMap()
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
initMap()
|
||||
// #endif
|
||||
}
|
||||
|
||||
function wechatMap() {
|
||||
uni.authorize({
|
||||
scope: 'scope.userLocation',
|
||||
success() {
|
||||
initMap()
|
||||
},
|
||||
fail() {
|
||||
uni.showModal({
|
||||
content: '检测到您没打开获取地址功能权限,是否去设置打开?',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.openSetting({
|
||||
success: () => {
|
||||
initMap()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
emit('close')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function posToCity(latitude: number, longitude: number) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/regeo',
|
||||
method: 'GET',
|
||||
data: {
|
||||
key: config.aMapKey,
|
||||
location: `${longitude},${latitude}`,
|
||||
},
|
||||
success: ({ data }: any) => {
|
||||
const { status, info } = data
|
||||
if (status === '1') {
|
||||
resolve(data)
|
||||
} else {
|
||||
reject(info)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<view>
|
||||
<view
|
||||
v-if="!hid"
|
||||
v-if="!state.hid"
|
||||
class="flex-row-center"
|
||||
:style="{ top: scHight }"
|
||||
:style="{ top: state.scHight }"
|
||||
style="width: 750rpx; position: fixed; z-index: 100; left: 0"
|
||||
>
|
||||
<view
|
||||
@@ -14,35 +14,35 @@
|
||||
class="flex"
|
||||
style="width: 100%"
|
||||
animation="false"
|
||||
:style="{ height: originalHeight }"
|
||||
:style="{ height: state.originalHeight }"
|
||||
>
|
||||
<movable-view
|
||||
scale-value="1"
|
||||
animation="false"
|
||||
damping="5000"
|
||||
:x="moveX"
|
||||
:x="state.moveX"
|
||||
:style="{
|
||||
height: sliderHeight,
|
||||
width: sliderWidth,
|
||||
height: state.sliderHeight,
|
||||
width: state.sliderWidth,
|
||||
'z-index': 101,
|
||||
}"
|
||||
direction="horizontal"
|
||||
>
|
||||
<image
|
||||
:src="imgbk"
|
||||
:src="state.imgbk"
|
||||
class="image"
|
||||
mode="aspectFit"
|
||||
:style="{
|
||||
height: sliderHeight,
|
||||
width: sliderWidth,
|
||||
'margin-top': imgbKH,
|
||||
height: state.sliderHeight,
|
||||
width: state.sliderWidth,
|
||||
'margin-top': state.imgbKH,
|
||||
}"
|
||||
></image>
|
||||
</movable-view>
|
||||
<image
|
||||
:src="img"
|
||||
:src="state.img"
|
||||
mode="aspectFit"
|
||||
:style="{ height: originalHeight, width: originalWidth }"
|
||||
:style="{ height: state.originalHeight, width: state.originalWidth }"
|
||||
style="border-radius: 10rpx"
|
||||
></image>
|
||||
</movable-area>
|
||||
@@ -61,7 +61,7 @@
|
||||
scale-value="1"
|
||||
animation="false"
|
||||
damping="50"
|
||||
:x="movePv"
|
||||
:x="state.movePv"
|
||||
class="flex-row-center"
|
||||
style="
|
||||
border-radius: 50%;
|
||||
@@ -78,14 +78,14 @@
|
||||
<u-icon
|
||||
:color="mainColor"
|
||||
size="40"
|
||||
v-if="endLoad"
|
||||
v-if="state.endLoad"
|
||||
name="arrow-right"
|
||||
></u-icon>
|
||||
<u-icon :color="mainColor" size="40" v-else name="reload"></u-icon>
|
||||
</movable-view>
|
||||
|
||||
<text style="padding-left: 140rpx" :style="{ color: col }">{{
|
||||
hasImg
|
||||
<text style="padding-left: 140rpx" :style="{ color: state.col }">{{
|
||||
state.hasImg
|
||||
}}</text>
|
||||
</movable-area>
|
||||
<view class="flex-row-around padding-top" style="width: 100%">
|
||||
@@ -106,173 +106,178 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "@/config/api.js";
|
||||
import storage from "@/utils/storage.js";
|
||||
import uuid from "@/utils/uuid.modified.js";
|
||||
const phone = uni.getSystemInfoSync();
|
||||
const l = phone.screenWidth / 750;
|
||||
export default {
|
||||
name: "verification",
|
||||
created() {
|
||||
// 可自行调整
|
||||
this.scHight = phone.screenHeight / 2 - 200 + "px";
|
||||
this.getCode();
|
||||
},
|
||||
props: {
|
||||
height: {
|
||||
type: String,
|
||||
default: "80rpx",
|
||||
<script setup lang="ts">
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import api from '@/config/api.js'
|
||||
import storage from '@/utils/storage.js'
|
||||
import uuid from '@/utils/uuid.modified.js'
|
||||
|
||||
const emit = defineEmits(['send'])
|
||||
const props = withDefaults(defineProps<{
|
||||
height?: string
|
||||
width?: string
|
||||
left?: string
|
||||
top?: string
|
||||
business?: string
|
||||
}>(), {
|
||||
height: '80rpx',
|
||||
width: '350rpx',
|
||||
left: '180rpx',
|
||||
top: '30rpx',
|
||||
business: 'LOGIN'
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
const phone = uni.getSystemInfoSync()
|
||||
const l = phone.screenWidth / 750
|
||||
|
||||
const mainColor = computed(() => store.getters.mainColor)
|
||||
const state = reactive({
|
||||
flage: false,
|
||||
key: '', // key
|
||||
vsrtx: '点击进行验证', // 按钮提示语
|
||||
vsr: false,
|
||||
hid: true,
|
||||
col: '#838383',
|
||||
movePv: 0,
|
||||
hasImg: '拖动滑块已完成拼图',
|
||||
spcode: '',
|
||||
tl: 0,
|
||||
moveCode: 0,
|
||||
// X轴移动距离
|
||||
moveX: 0,
|
||||
// 模版高度
|
||||
originalHeight: '',
|
||||
// 模版宽度
|
||||
originalWidth: '',
|
||||
// 拼图高度
|
||||
sliderHeight: '',
|
||||
// 平涂宽度
|
||||
sliderWidth: '',
|
||||
scHight: 0,
|
||||
// 原图
|
||||
img: '',
|
||||
// 拼图
|
||||
imgbk: '',
|
||||
endLoad: true,
|
||||
imgbKH: ''
|
||||
})
|
||||
|
||||
// 可自行调整
|
||||
state.scHight = phone.screenHeight / 2 - 200 + 'px'
|
||||
getCode()
|
||||
|
||||
function show() {
|
||||
state.hid = false
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (!state.vsr) {
|
||||
// vsr判断是否验证成功,成功隐藏验证框
|
||||
state.hid = !state.hid
|
||||
}
|
||||
}
|
||||
|
||||
function error() {
|
||||
state.vsr = false
|
||||
state.hid = false
|
||||
state.moveX = 0
|
||||
state.moveCode = 0
|
||||
}
|
||||
|
||||
// 获取验证图片
|
||||
function getCode() {
|
||||
state.col = '#b3afae'
|
||||
state.hasImg = '图片加载中...'
|
||||
if (!storage.getUuid()) {
|
||||
storage.setUuid(uuid.v1())
|
||||
}
|
||||
uni.request({
|
||||
url: api.common + '/common/slider/' + props.business,
|
||||
header: {
|
||||
uuid: storage.getUuid()
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "350rpx",
|
||||
success: (res: any) => {
|
||||
state.col = '#838383'
|
||||
state.hasImg = '拖动滑块以完成拼图'
|
||||
const data = res.data.result
|
||||
|
||||
// base64的图片
|
||||
state.img = data.backImage
|
||||
state.imgbk = data.slidingImage
|
||||
// 根据参数动态适应验证图片的高宽
|
||||
state.imgbKH = data.randomY * 1.8 + 'rpx'
|
||||
state.originalHeight = data.originalHeight * 1.8 + 'rpx'
|
||||
state.originalWidth = data.originalWidth * 1.8 + 'rpx'
|
||||
state.sliderHeight = data.sliderHeight * 1.8 + 'rpx'
|
||||
state.sliderWidth = data.sliderWidth * 1.8 + 'rpx'
|
||||
// 适应比率,用来适应滑动距离
|
||||
state.tl = 1 / (1.8 * l)
|
||||
// 无用信息
|
||||
state.spcode = data.capcode
|
||||
// 验证令牌
|
||||
state.key = data.key
|
||||
store.state.verificationKey = data.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function end() {
|
||||
state.endLoad = false
|
||||
// 验证拼图位置是否正确
|
||||
uni.request({
|
||||
method: 'POST',
|
||||
url:
|
||||
api.common +
|
||||
'/common/slider/' +
|
||||
props.business +
|
||||
'?xPos=' +
|
||||
parseInt(String(state.moveCode * state.tl)),
|
||||
header: {
|
||||
uuid: storage.getUuid()
|
||||
},
|
||||
left: {
|
||||
type: String,
|
||||
default: "180rpx",
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: "30rpx",
|
||||
},
|
||||
business: {
|
||||
type: String,
|
||||
default: "LOGIN",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mainColor: this.$mainColor,
|
||||
flage: false,
|
||||
key: "", //key
|
||||
vsrtx: "点击进行验证", //按钮提示语
|
||||
vsr: false, //
|
||||
hid: true,
|
||||
col: "#838383",
|
||||
movePv: 0,
|
||||
hasImg: "拖动滑块已完成拼图",
|
||||
spcode: "",
|
||||
tl: 0,
|
||||
moveCode: 0,
|
||||
//X轴移动距离
|
||||
moveX: 0,
|
||||
//模版高度
|
||||
originalHeight: "",
|
||||
//模版宽度
|
||||
originalWidth: "",
|
||||
//拼图高度
|
||||
sliderHeight: "",
|
||||
//平涂宽度
|
||||
sliderWidth: "",
|
||||
scHight: 0,
|
||||
//原图
|
||||
img: "",
|
||||
//拼图
|
||||
imgbk: "",
|
||||
endLoad: true,
|
||||
imgbKH: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.hid = false;
|
||||
},
|
||||
hide() {
|
||||
if (!this.vsr) {
|
||||
// vsr判断是否验证成功,成功隐藏验证框
|
||||
this.hid = !this.hid;
|
||||
success: (res: any) => {
|
||||
state.endLoad = true
|
||||
if (res.data.result == false) {
|
||||
res.data.result = false
|
||||
} else {
|
||||
res.data.result = true
|
||||
}
|
||||
|
||||
if (res.data && res.data.result) {
|
||||
// 验证成功后把key发送出去,后端会把验证信息存在缓存里
|
||||
emit('send', state.key)
|
||||
hide()
|
||||
state.vsr = true
|
||||
state.vsrtx = '已通过验证'
|
||||
} else {
|
||||
getCode() // 让滑块回到起始位置
|
||||
if (state.movePv == 1) {
|
||||
state.movePv = 0
|
||||
} else {
|
||||
state.movePv = 1
|
||||
}
|
||||
}
|
||||
},
|
||||
error() {
|
||||
this.vsr = false;
|
||||
this.hid = false;
|
||||
this.moveX = 0;
|
||||
this.moveCode = 0;
|
||||
},
|
||||
// 获取验证图片
|
||||
getCode() {
|
||||
this.col = "#b3afae";
|
||||
this.hasImg = "图片加载中...";
|
||||
if (!storage.getUuid()) {
|
||||
storage.setUuid(uuid.v1());
|
||||
}
|
||||
uni.request({
|
||||
url: api.common + "/common/slider/" + this.business,
|
||||
header: {
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
success: (res) => {
|
||||
this.col = "#838383";
|
||||
this.hasImg = "拖动滑块以完成拼图";
|
||||
var data = res.data.result;
|
||||
fail: () => {
|
||||
uni.showToast({ title: '连接服务器失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// base64的图片
|
||||
this.img = data.backImage;
|
||||
this.imgbk = data.slidingImage;
|
||||
// 根据参数动态适应验证图片的高宽
|
||||
this.imgbKH = data.randomY * 1.8 + "rpx";
|
||||
this.originalHeight = data.originalHeight * 1.8 + "rpx";
|
||||
this.originalWidth = data.originalWidth * 1.8 + "rpx";
|
||||
this.sliderHeight = data.sliderHeight * 1.8 + "rpx";
|
||||
this.sliderWidth = data.sliderWidth * 1.8 + "rpx";
|
||||
// 适应比率,用来适应滑动距离
|
||||
this.tl = 1 / (1.8 * l);
|
||||
// 无用信息
|
||||
this.spcode = data.capcode;
|
||||
// 验证令牌
|
||||
this.key = data.key;
|
||||
this.$store.state.verificationKey = data.key;
|
||||
},
|
||||
});
|
||||
},
|
||||
end(e) {
|
||||
this.endLoad = false;
|
||||
// 验证拼图位置是否正确
|
||||
uni.request({
|
||||
method: "POST",
|
||||
url:
|
||||
api.common +
|
||||
"/common/slider/" +
|
||||
this.business +
|
||||
"?xPos=" +
|
||||
parseInt(this.moveCode * this.tl),
|
||||
header: {
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
success: (res) => {
|
||||
this.endLoad = true;
|
||||
res.data.result == false
|
||||
? (res.data.result = false)
|
||||
: (res.data.result = true);
|
||||
// 绑定拼图位置
|
||||
function moveChange(e: any) {
|
||||
state.moveX = e.detail.x
|
||||
state.moveCode = e.detail.x
|
||||
}
|
||||
|
||||
if (res.data && res.data.result) {
|
||||
//验证成功后把key发送出去,后端会把验证信息存在缓存里
|
||||
this.$emit("send", this.key);
|
||||
this.hide();
|
||||
this.vsr = true;
|
||||
this.vsrtx = "已通过验证";
|
||||
} else {
|
||||
this.getCode(); // 让滑块回到起始位置
|
||||
if (this.movePv == 1) {
|
||||
this.movePv = 0;
|
||||
} else {
|
||||
this.movePv = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
this.$msg("连接服务器失败");
|
||||
},
|
||||
});
|
||||
},
|
||||
// 绑定拼图位置
|
||||
moveChange(e) {
|
||||
this.moveX = e.detail.x;
|
||||
this.moveCode = e.detail.x;
|
||||
},
|
||||
},
|
||||
};
|
||||
defineExpose({
|
||||
show,
|
||||
hide,
|
||||
error,
|
||||
getCode,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:password="isPassword"
|
||||
:type="inputType"
|
||||
:maxlength="size"
|
||||
@input="input"
|
||||
@input="handleInput"
|
||||
@focus="inputFocus"
|
||||
@blur="inputBlur"
|
||||
/>
|
||||
@@ -37,7 +37,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @description 输入验证码组件
|
||||
* @property {string} type = [box|middle|bottom] - 显示类型 默认:box -eg:bottom
|
||||
@@ -50,175 +50,155 @@
|
||||
* @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认:#000000
|
||||
* @event {Function(data)} confirm - 输入完成
|
||||
*/
|
||||
export default {
|
||||
name: 'xt-verify-code',
|
||||
emits: ['update:modelValue', 'input', 'confirm'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: () => 'box'
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: () => 'number'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: () => 6
|
||||
},
|
||||
isFocus: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
isPassword: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
cursorColor: {
|
||||
type: String,
|
||||
default: () => '#cccccc'
|
||||
},
|
||||
boxNormalColor: {
|
||||
type: String,
|
||||
default: () => '#cccccc'
|
||||
},
|
||||
boxActiveColor: {
|
||||
type: String,
|
||||
default: () => '#000000'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
cursorVisible: false,
|
||||
cursorHeight: 35,
|
||||
code: '', // 输入的验证码
|
||||
codeCursorLeft: [] // 向左移动的距离数组
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.code = this.modelValue || this.value || '';
|
||||
this.focused = this.isFocus;
|
||||
this.cursorVisible = this.isFocus;
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
if (this.isFocus) {
|
||||
this.$nextTick(() => {
|
||||
this.focusInput();
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
focusInput() {
|
||||
this.focused = false;
|
||||
this.$nextTick(() => {
|
||||
this.focused = true;
|
||||
// #ifdef H5
|
||||
const inputEl = this.$refs.codeInput;
|
||||
if (inputEl && typeof inputEl.focus === 'function') {
|
||||
inputEl.focus();
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 初始化
|
||||
*/
|
||||
init() {
|
||||
this.getCodeCursorLeft();
|
||||
this.setCursorHeight();
|
||||
},
|
||||
/**
|
||||
* @description 获取元素节点
|
||||
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
|
||||
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
getElement(elm, type = 'single', callback) {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
[type === 'array' ? 'selectAll' : 'select'](elm)
|
||||
.boundingClientRect()
|
||||
.exec(data => {
|
||||
callback(data[0]);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 计算光标的高度
|
||||
*/
|
||||
setCursorHeight() {
|
||||
this.getElement('.xt__box', 'single', boxElm => {
|
||||
this.cursorHeight = boxElm.height * 0.6;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 获取光标在每一个box的left位置
|
||||
*/
|
||||
getCodeCursorLeft() {
|
||||
// 获取父级框的位置信息
|
||||
this.getElement('#xt__input-ground', 'single', parentElm => {
|
||||
const parentLeft = parentElm.left;
|
||||
// 获取各个box信息
|
||||
this.getElement('.xt__box', 'array', elms => {
|
||||
this.codeCursorLeft = [];
|
||||
elms.forEach(elm => {
|
||||
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
import { ref, watch, onMounted, nextTick, getCurrentInstance } from 'vue'
|
||||
|
||||
// 输入框输入变化的回调
|
||||
input(e) {
|
||||
const value = e.detail.value;
|
||||
this.code = value;
|
||||
this.cursorVisible = value.length !== this.size;
|
||||
this.$emit('update:modelValue', value);
|
||||
this.$emit('input', value);
|
||||
this.inputSuccess(value);
|
||||
},
|
||||
const emit = defineEmits(['update:modelValue', 'input', 'confirm'])
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: string
|
||||
value?: string
|
||||
type?: string
|
||||
inputType?: string
|
||||
size?: number
|
||||
isFocus?: boolean
|
||||
isPassword?: boolean
|
||||
cursorColor?: string
|
||||
boxNormalColor?: string
|
||||
boxActiveColor?: string
|
||||
}>(), {
|
||||
modelValue: '',
|
||||
value: '',
|
||||
type: 'box',
|
||||
inputType: 'number',
|
||||
size: 6,
|
||||
isFocus: true,
|
||||
isPassword: false,
|
||||
cursorColor: '#cccccc',
|
||||
boxNormalColor: '#cccccc',
|
||||
boxActiveColor: '#000000'
|
||||
})
|
||||
|
||||
// 输入完成回调
|
||||
inputSuccess(value) {
|
||||
if (value.length === this.size) {
|
||||
this.$emit('confirm', value);
|
||||
}
|
||||
},
|
||||
// 输入聚焦
|
||||
inputFocus() {
|
||||
this.cursorVisible = this.code.length !== this.size;
|
||||
},
|
||||
// 输入失去焦点
|
||||
inputBlur() {
|
||||
this.cursorVisible = false;
|
||||
},
|
||||
codeFormat(val, isPassword) {
|
||||
let value = '';
|
||||
if (val) {
|
||||
value = isPassword ? '*' : val;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.code = val || '';
|
||||
},
|
||||
value(val) {
|
||||
this.code = val || '';
|
||||
}
|
||||
const instance = getCurrentInstance()
|
||||
const codeInput = ref<any>(null)
|
||||
const focused = ref(false)
|
||||
const cursorVisible = ref(false)
|
||||
const cursorHeight = ref(35)
|
||||
const code = ref('') // 输入的验证码
|
||||
const codeCursorLeft = ref<number[]>([]) // 向左移动的距离数组
|
||||
|
||||
// 初始化
|
||||
watch([() => props.modelValue, () => props.value], ([modelVal, val]) => {
|
||||
code.value = modelVal || val || ''
|
||||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
focused.value = props.isFocus
|
||||
cursorVisible.value = props.isFocus
|
||||
init()
|
||||
if (props.isFocus) {
|
||||
nextTick(() => {
|
||||
focusInput()
|
||||
})
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
function focusInput() {
|
||||
focused.value = false
|
||||
nextTick(() => {
|
||||
focused.value = true
|
||||
// #ifdef H5
|
||||
const inputEl = codeInput.value
|
||||
if (inputEl && typeof inputEl.focus === 'function') {
|
||||
inputEl.focus()
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 初始化
|
||||
*/
|
||||
function init() {
|
||||
getCodeCursorLeft()
|
||||
setCursorHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取元素节点
|
||||
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
|
||||
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
function getElement(elm: string, type: string = 'single', callback: Function) {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(instance?.proxy)
|
||||
[type === 'array' ? 'selectAll' : 'select'](elm)
|
||||
.boundingClientRect()
|
||||
.exec((data: any) => {
|
||||
callback(data[0])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 计算光标的高度
|
||||
*/
|
||||
function setCursorHeight() {
|
||||
getElement('.xt__box', 'single', (boxElm: any) => {
|
||||
cursorHeight.value = boxElm.height * 0.6
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取光标在每一个box的left位置
|
||||
*/
|
||||
function getCodeCursorLeft() {
|
||||
// 获取父级框的位置信息
|
||||
getElement('#xt__input-ground', 'single', (parentElm: any) => {
|
||||
const parentLeft = parentElm.left
|
||||
// 获取各个box信息
|
||||
getElement('.xt__box', 'array', (elms: any) => {
|
||||
codeCursorLeft.value = []
|
||||
elms.forEach((elm: any) => {
|
||||
codeCursorLeft.value.push(elm.left - parentLeft + elm.width / 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 输入框输入变化的回调
|
||||
function handleInput(e: any) {
|
||||
const value = e.detail.value
|
||||
code.value = value
|
||||
cursorVisible.value = value.length !== props.size
|
||||
emit('update:modelValue', value)
|
||||
emit('input', value)
|
||||
inputSuccess(value)
|
||||
}
|
||||
|
||||
// 输入完成回调
|
||||
function inputSuccess(value: string) {
|
||||
if (value.length === props.size) {
|
||||
emit('confirm', value)
|
||||
}
|
||||
}
|
||||
|
||||
// 输入聚焦
|
||||
function inputFocus() {
|
||||
cursorVisible.value = code.value.length !== props.size
|
||||
}
|
||||
|
||||
// 输入失去焦点
|
||||
function inputBlur() {
|
||||
cursorVisible.value = false
|
||||
}
|
||||
|
||||
function codeFormat(val: string | undefined, isPassword: boolean): string {
|
||||
let value = ''
|
||||
if (val) {
|
||||
value = isPassword ? '*' : val
|
||||
}
|
||||
return value
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.xt__verify-code {
|
||||
|
||||
Reference in New Issue
Block a user