基础插件修改
This commit is contained in:
parent
d2f735d2f6
commit
28e34c191d
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :index="index">
|
||||
<view class="banner">
|
||||
<cl-banner
|
||||
:indicatorDots="indicatorDots"
|
||||
:list="list"
|
||||
:type="mode"
|
||||
:height="height"
|
||||
:autoplay="autoplay"
|
||||
@select="toPath"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<cl-skeleton height="100%" :loading="!item.pic" :loading-style="style">
|
||||
<image :style="style" mode="fill" :src="item.pic" />
|
||||
</cl-skeleton>
|
||||
</template>
|
||||
</cl-banner>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fix-banner">
|
||||
import { computed, type PropType } from "vue";
|
||||
import { baseProps } from "../../hooks";
|
||||
import type { Form } from "../../types/form";
|
||||
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
indicatorDots: Boolean,
|
||||
list: {
|
||||
type: Array as PropType<Form.Banner[]>,
|
||||
default: function () {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
|
||||
function toPath(index: number) {
|
||||
const link = props.list[index].link;
|
||||
emits("jump", link);
|
||||
}
|
||||
|
||||
const style = computed(() => {
|
||||
const { borderTopLR, borderBottomLR } = props.styleSpacing;
|
||||
return {
|
||||
borderRadius: `${borderTopLR}rpx ${borderTopLR}rpx ${borderBottomLR}rpx ${borderBottomLR}rpx`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.banner {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
|
||||
image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<view
|
||||
:style="{
|
||||
paddingTop: seatHeight + 'px',
|
||||
}"
|
||||
class="layer"
|
||||
>
|
||||
<view
|
||||
:style="{
|
||||
...basePosition,
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
<view :style="{ position: 'absolute', ...baseBackground }"></view>
|
||||
<view class="fix-base-style" :style="baseStyle" :class="[`fix-base-style-${index}`]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fix-base-style">
|
||||
import { computed, watch, ref, nextTick, getCurrentInstance } from "vue";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const { proxy }: any = getCurrentInstance();
|
||||
const { statusBarHeight = 0 } = uni.getSystemInfoSync();
|
||||
|
||||
// 定义属性,简化默认对象的写法
|
||||
const props = defineProps({
|
||||
...baseProps,
|
||||
// 背景跟随子元素的间距圆角
|
||||
flowInner: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
statusBar: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 计算 baseStyle,使用解构优化
|
||||
const baseStyle = computed(() => {
|
||||
const { marginTop, marginBottom, marginLR, padding, borderTopLR, borderBottomLR } =
|
||||
props.styleSpacing;
|
||||
|
||||
const { color } = props.styleColor;
|
||||
|
||||
return {
|
||||
margin: `${marginTop}rpx ${marginLR}rpx ${marginBottom}rpx ${marginLR}rpx`,
|
||||
color: color,
|
||||
padding: `${padding}rpx`,
|
||||
borderRadius: `${borderTopLR}rpx ${borderTopLR}rpx ${borderBottomLR}rpx ${borderBottomLR}rpx`,
|
||||
};
|
||||
});
|
||||
const baseBackground = computed(() => {
|
||||
const { backgroundColor, opacity } = props.styleColor;
|
||||
const { marginTop, marginBottom, marginLR, borderTopLR, borderBottomLR } = props.styleSpacing;
|
||||
let flow: any = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
if (props.flowInner) {
|
||||
flow = {
|
||||
left: `${marginLR}rpx`,
|
||||
top: `${marginTop}rpx`,
|
||||
right: `${marginLR}rpx`,
|
||||
bottom: `${marginBottom}rpx`,
|
||||
borderRadius: `${borderTopLR}rpx ${borderTopLR}rpx ${borderBottomLR}rpx ${borderBottomLR}rpx`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
backgroundColor: backgroundColor,
|
||||
opacity: opacity,
|
||||
zIndex: -1,
|
||||
...flow,
|
||||
};
|
||||
});
|
||||
|
||||
const basePosition = computed(() => {
|
||||
const { top, mode, zIndex } = props.position;
|
||||
let topPos = top;
|
||||
if (props.statusBar) topPos = topPos + statusBarHeight;
|
||||
return {
|
||||
top: `${topPos}px`,
|
||||
position: mode,
|
||||
zIndex: zIndex,
|
||||
};
|
||||
});
|
||||
const seatHeight = ref(0);
|
||||
watch(
|
||||
() => props.position,
|
||||
async (position) => {
|
||||
if (position.isSeat && position.mode === "fixed") {
|
||||
refreshSeat();
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function refreshSeat() {
|
||||
nextTick(() => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(proxy)
|
||||
// #endif
|
||||
.select(`.fix-base-style-${props.index}`)
|
||||
.boundingClientRect((res: any) => {
|
||||
seatHeight.value = res.height;
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layer {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.fix-base-style {
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
221
uni_modules/cool-fixtures/components/fix-coupon/fix-coupon.vue
Normal file
221
uni_modules/cool-fixtures/components/fix-coupon/fix-coupon.vue
Normal file
@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<div class="fix-coupon" v-if="item.id" :style="couponStyle">
|
||||
<div class="coupon-activity" @tap="toGet">
|
||||
<div class="a">
|
||||
<div class="text">
|
||||
<span class="name">{{ item?.title }}</span>
|
||||
<span class="desc">{{ item?.description }}</span>
|
||||
</div>
|
||||
|
||||
<span class="tag" v-if="item">点击领券</span>
|
||||
</div>
|
||||
|
||||
<div class="b">
|
||||
<template v-if="item">
|
||||
<span class="amount">{{ item?.amount || 0 }}</span>
|
||||
<span class="doc">
|
||||
{{ doc }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="c"></div>
|
||||
</div>
|
||||
</div>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-coupon" setup>
|
||||
import { computed, type PropType } from "vue";
|
||||
import { baseProps } from "../../hooks";
|
||||
import { useCool } from "/@/cool";
|
||||
import { useUi } from "/$/cool-ui";
|
||||
const { service } = useCool();
|
||||
const ui = useUi();
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<Eps.MarketCouponInfoEntity>,
|
||||
default: () => {
|
||||
return {
|
||||
id: 0,
|
||||
title: "",
|
||||
description: "",
|
||||
amount: 0,
|
||||
type: 0,
|
||||
condition: "",
|
||||
};
|
||||
},
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#2b2e3d",
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
|
||||
// 领取
|
||||
function toGet() {
|
||||
service.market.coupon.user
|
||||
.receive({
|
||||
couponId: props.item?.id,
|
||||
})
|
||||
.then(() => {
|
||||
ui.showToast("领取成功");
|
||||
})
|
||||
.catch((err) => {
|
||||
ui.showToast(err.message);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 将十六进制颜色转换为 RGB
|
||||
* @param hex - 十六进制颜色代码 (例如: #2b2e3d)
|
||||
* @returns - RGB 颜色字符串 (例如: 43, 46, 61)
|
||||
*/
|
||||
function hexToRgb(hex: string) {
|
||||
// 移除前导 #
|
||||
hex = hex.replace(/^#/, "");
|
||||
|
||||
// 处理 3 位和 6 位十六进制颜色
|
||||
if (hex.length === 4) {
|
||||
hex = hex
|
||||
.split("")
|
||||
.map((char: any, index: number) => (index > 0 ? char + char : char))
|
||||
.join("");
|
||||
}
|
||||
|
||||
const bigint = parseInt(hex, 16);
|
||||
return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255].join(", ");
|
||||
}
|
||||
|
||||
// 使用说明
|
||||
const doc = computed(() => {
|
||||
const { type, condition } = props.item;
|
||||
switch (type) {
|
||||
case 0:
|
||||
return `满${condition?.fullAmount}可用`;
|
||||
}
|
||||
});
|
||||
|
||||
const couponStyle = computed(() => {
|
||||
const { color } = props;
|
||||
const rgbaColor = hexToRgb(color);
|
||||
// 返回样式对象
|
||||
return {
|
||||
"--coupon-color": rgbaColor,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-coupon {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coupon-activity {
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 160rpx;
|
||||
letter-spacing: 1rpx;
|
||||
|
||||
.a {
|
||||
background: linear-gradient(
|
||||
140deg,
|
||||
rgba(var(--coupon-color), 0.75),
|
||||
rgba(var(--coupon-color), 1)
|
||||
);
|
||||
height: 140rpx;
|
||||
width: calc(100% - 250rpx);
|
||||
border-radius: 12rpx;
|
||||
position: absolute;
|
||||
left: 24rpx;
|
||||
bottom: 1rpx;
|
||||
z-index: 2;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 20rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 4rpx;
|
||||
background-color: #eb10ab;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.b {
|
||||
height: 160rpx;
|
||||
width: 220rpx;
|
||||
background-color: #e2e2e2;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
bottom: 0;
|
||||
border-radius: 12rpx;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.amount {
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
color: rgba(var(--coupon-color), 1);
|
||||
|
||||
&::after {
|
||||
content: "元";
|
||||
font-size: 48rpx;
|
||||
position: relative;
|
||||
top: -4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.doc {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.c {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
background-color: #868686;
|
||||
border-radius: 40rpx;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 216rpx;
|
||||
top: 2rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
31
uni_modules/cool-fixtures/components/fix-empty/fix-empty.vue
Normal file
31
uni_modules/cool-fixtures/components/fix-empty/fix-empty.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<view class="fix-empty" :style="{ height: height + 'rpx' }"></view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-empty" setup>
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.fix-empty {
|
||||
width: 750rpx;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :position="position" :index="index">
|
||||
<view class="fix-goods-list">
|
||||
<template v-if="list.length">
|
||||
<view class="inner" :class="[`is-${data.mode}`, data.isShadow ? 'is-shadow-pb' : '']">
|
||||
<view class="grid-item" v-for="(item, index) in list" :key="index" :class="[data.isShadow ? 'is-shadow' : '']"
|
||||
@click="toDetail(item)">
|
||||
<image mode="widthFix" class="icon" :src="item.mainPic" />
|
||||
<view class="content">
|
||||
<view class="title">{{ item.title }}</view>
|
||||
<view class="price-sold">
|
||||
<cl-text type="price" :color="styleColor.color" :size="34" :value="item.price" bold />
|
||||
<cl-text color="info" :size="22" :value="`${item.sold || 0}件已售`" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<cl-empty :fixed="false" text="暂无商品~~"></cl-empty>
|
||||
</template>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-goods-list" setup>
|
||||
import { ref, watch, computed, type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
import { useCool, useStore } from "/@/cool";
|
||||
const { service, router } = useCool();
|
||||
const { user } = useStore();
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Form.Goods>,
|
||||
default: () => {
|
||||
({
|
||||
mode: "mode-1",
|
||||
source: "source-1",
|
||||
attribute: 0,
|
||||
num: 99,
|
||||
gap: 0,
|
||||
isShadow: false,
|
||||
isVoucher: false,
|
||||
type: [],
|
||||
list: [],
|
||||
});
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
|
||||
const list = ref<Array<Eps.GoodsInfoEntity>>([]);
|
||||
// 创建计算属性来返回我们关心的特定属性
|
||||
const watchedData = computed(() => ({
|
||||
source: props.data.source,
|
||||
attribute: props.data.attribute,
|
||||
num: props.data.num,
|
||||
ids: props.data.list.map((e) => e.id),
|
||||
typeIds: props.data.type.map((e) => e.id),
|
||||
}));
|
||||
watch(
|
||||
() => watchedData.value,
|
||||
async (newValue) => {
|
||||
let goods = [];
|
||||
try {
|
||||
goods = await service.goods.info.getGoodsFromFixture({
|
||||
source: newValue.source,
|
||||
attribute: newValue.attribute,
|
||||
num: newValue.num,
|
||||
ids: newValue.ids,
|
||||
typeIds: newValue.typeIds,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
list.value = goods;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
async function toDetail(item : Eps.GoodsInfoEntity) {
|
||||
if (item.openJump && item.jumpInfo.AppID) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
await wx.openEmbeddedMiniProgram({
|
||||
appId: item.jumpInfo.AppID,
|
||||
path: item.jumpInfo.page,
|
||||
allowFullScreen: true,
|
||||
});
|
||||
} catch {
|
||||
// @ts-ignore
|
||||
await wx.navigateToMiniProgram({
|
||||
appId: item.jumpInfo.AppID,
|
||||
path: item.jumpInfo.page,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
router.push({
|
||||
path: "/pages/goods/detail",
|
||||
query: {
|
||||
id: item.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-goods-list {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.is-shadow-pb {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.inner {
|
||||
.grid-item {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.is-shadow {
|
||||
box-shadow:
|
||||
0 3px 5px rgba(0, 0, 0, 0.2),
|
||||
0 6px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
/* 禁止换行 */
|
||||
overflow: hidden;
|
||||
/* 隐藏溢出的内容 */
|
||||
text-overflow: ellipsis;
|
||||
/* 超出部分显示省略号 */
|
||||
max-width: 100%;
|
||||
/* 确保元素最大宽度不超过容器 */
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.price-sold {
|
||||
margin-top: 16rpx;
|
||||
font-size: 28rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.voucher {
|
||||
font-size: 28rpx;
|
||||
margin-top: 16rpx;
|
||||
|
||||
.tips {
|
||||
color: #e6a23c;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.di {
|
||||
margin-left: 20rpx;
|
||||
color: #e6a23c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-mode-1 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.is-mode-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.is-mode-3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20rpx;
|
||||
|
||||
.grid-item {
|
||||
background-color: #fff;
|
||||
border-top-right-radius: 12rpx;
|
||||
border-top-left-radius: 12rpx;
|
||||
border-bottom-right-radius: 12rpx;
|
||||
border-bottom-left-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: 200rpx 1fr;
|
||||
gap: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.is-mode-4 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20rpx;
|
||||
|
||||
.grid-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-top-right-radius: 12rpx;
|
||||
border-top-left-radius: 12rpx;
|
||||
border-bottom-right-radius: 12rpx;
|
||||
border-bottom-left-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :position="position">
|
||||
<view class="fix-hot-image" :class="[`fix-hot-image-${index}`]">
|
||||
<image
|
||||
v-if="data.pic"
|
||||
mode="widthFix"
|
||||
:src="data.pic"
|
||||
class="img hot-image"
|
||||
@load="loadImage"
|
||||
@click="toPath"
|
||||
/>
|
||||
<view
|
||||
v-for="(item, index) in hot"
|
||||
:key="index"
|
||||
class="hot-item"
|
||||
:style="{
|
||||
width: `${item.relativeW}px`,
|
||||
height: `${item.relativeH}px`,
|
||||
top: `${item.relativeY}px`,
|
||||
left: `${item.relativeX}px`,
|
||||
zIndex: item.index,
|
||||
}"
|
||||
@click="hotJump(item)"
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-hot-image" setup>
|
||||
import { type PropType, ref, nextTick, getCurrentInstance } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
const { proxy }: any = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Form.HotImage>,
|
||||
default: () => {
|
||||
return {
|
||||
pic: "",
|
||||
link: {},
|
||||
width: 0,
|
||||
height: 0,
|
||||
attr: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
const hot = ref<Form.Hot[]>(props.data.attr);
|
||||
|
||||
function refreshHot() {
|
||||
nextTick(() => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(proxy)
|
||||
// #endif
|
||||
.select(`.fix-hot-image-${props.index}`)
|
||||
.boundingClientRect((res: any) => {
|
||||
const { width, height } = res;
|
||||
|
||||
// 调整等比例缩放位置 大小
|
||||
const scaleX = width / props.data.width;
|
||||
const scaleY = height / props.data.height;
|
||||
// 调整子元素属性
|
||||
hot.value = props.data.attr.map((element) => {
|
||||
element.relativeX = Math.floor(element.x * scaleX);
|
||||
element.relativeY = Math.floor(element.y * scaleY);
|
||||
element.relativeW = Math.floor(element.w * scaleX);
|
||||
element.relativeH = Math.floor(element.h * scaleY);
|
||||
return element;
|
||||
});
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
function loadImage(event: any) {
|
||||
refreshHot();
|
||||
}
|
||||
|
||||
function hotJump(item: Form.Hot) {
|
||||
emits("jump", item.link);
|
||||
}
|
||||
function toPath() {
|
||||
const link = props.data.link;
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-hot-image {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.hot-item {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
</style>
|
175
uni_modules/cool-fixtures/components/fix-index/fix-index.vue
Normal file
175
uni_modules/cool-fixtures/components/fix-index/fix-index.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<view class="parent">
|
||||
<view class="item" v-for="(item, index) in list" :key="index">
|
||||
<block v-if="item.component.name == 'fix-top-bar'">
|
||||
<fix-top-bar
|
||||
v-bind="item.component.props"
|
||||
:statusBar="statusBar"
|
||||
@jump="jump"
|
||||
></fix-top-bar>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-positioning'">
|
||||
<fix-positioning
|
||||
v-bind="item.component.props"
|
||||
:index="index"
|
||||
@jump="jump"
|
||||
></fix-positioning>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-search'">
|
||||
<fix-search
|
||||
v-bind="item.component.props"
|
||||
:statusBar="statusBar"
|
||||
:index="index"
|
||||
@jump="jump"
|
||||
></fix-search>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-banner'">
|
||||
<fix-banner v-bind="item.component.props" :index="index" @jump="jump"></fix-banner>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-menus'">
|
||||
<fix-menus v-bind="item.component.props" :index="index" @jump="jump"></fix-menus>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-goods-list'">
|
||||
<fix-goods-list v-bind="item.component.props" :index="index"></fix-goods-list>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-picture'">
|
||||
<fix-picture
|
||||
v-bind="item.component.props"
|
||||
:index="index"
|
||||
@jump="jump"
|
||||
></fix-picture>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-hot-image'">
|
||||
<fix-hot-image
|
||||
v-bind="item.component.props"
|
||||
:index="index"
|
||||
@jump="jump"
|
||||
></fix-hot-image>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-coupon'">
|
||||
<fix-coupon v-bind="item.component.props" :index="index"></fix-coupon>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-title'">
|
||||
<fix-title v-bind="item.component.props" :index="index" @jump="jump"></fix-title>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-empty'">
|
||||
<fix-empty v-bind="item.component.props" :index="index"></fix-empty>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-line'">
|
||||
<fix-line v-bind="item.component.props" :index="index"></fix-line>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-rich-text'">
|
||||
<fix-rich-text v-bind="item.component.props" :index="index"></fix-rich-text>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-list-menu'">
|
||||
<fix-list-menu
|
||||
v-bind="item.component.props"
|
||||
:index="index"
|
||||
@jump="jump"
|
||||
></fix-list-menu>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-rubik-cube'">
|
||||
<fix-rubik-cube
|
||||
v-bind="item.component.props"
|
||||
:index="index"
|
||||
@jump="jump"
|
||||
></fix-rubik-cube>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-video'">
|
||||
<fix-video v-bind="item.component.props" :index="index"></fix-video>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-suspension'">
|
||||
<fix-suspension v-bind="item.component.props" @jump="jump"></fix-suspension>
|
||||
</block>
|
||||
<block v-if="item.component.name == 'fix-wechat'">
|
||||
<fix-wechat v-bind="item.component.props"></fix-wechat>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fix-index">
|
||||
import { type PropType } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
const { router } = useCool();
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array as PropType<{ component: { name: string; props: Record<string, any> } }[]>,
|
||||
default: () => [],
|
||||
},
|
||||
statusBar: Boolean,
|
||||
});
|
||||
|
||||
// 将字符串 id=1&t=2 转成对象
|
||||
function strToObj(str: string): Record<string, string> {
|
||||
if (!str) return {};
|
||||
return str.split("&").reduce((acc: { [key: string]: string }, pair) => {
|
||||
const [key, value] = pair.split("=");
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// 统一的跳转函数
|
||||
function jump(link: { name: string; page: string; type: string; appid: string }) {
|
||||
let [path, param = ""] = link.page.split("?");
|
||||
const query = strToObj(param);
|
||||
switch (link.type) {
|
||||
case "goodsType":
|
||||
// 分类
|
||||
router.push({
|
||||
path,
|
||||
query,
|
||||
});
|
||||
break;
|
||||
case "goodsDetails":
|
||||
// 商品
|
||||
router.push({
|
||||
path,
|
||||
query,
|
||||
});
|
||||
break;
|
||||
case "index":
|
||||
// 平台
|
||||
router.push(path);
|
||||
break;
|
||||
case "web":
|
||||
// 网页
|
||||
router.push({
|
||||
path: "/pages/index/web",
|
||||
query: {
|
||||
url: link.page,
|
||||
title: link.name,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case "applet":
|
||||
// 小程序
|
||||
try {
|
||||
// @ts-ignore
|
||||
wx.openEmbeddedMiniProgram({
|
||||
appId: link.appid,
|
||||
path: link.page,
|
||||
allowFullScreen: true,
|
||||
});
|
||||
} catch {
|
||||
// @ts-ignore
|
||||
wx.navigateToMiniProgram({
|
||||
appId: link.appid,
|
||||
path: link.page,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "fixtures":
|
||||
// 自定义
|
||||
router.push({
|
||||
path,
|
||||
query,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
44
uni_modules/cool-fixtures/components/fix-line/fix-line.vue
Normal file
44
uni_modules/cool-fixtures/components/fix-line/fix-line.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :index="index">
|
||||
<view class="fix-line">
|
||||
<view class="inner" :style="innerStyle"></view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-line" setup>
|
||||
import { computed } from "vue";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "solid",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#f5f6fa",
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const innerStyle = computed(() => {
|
||||
return {
|
||||
borderBottom: `${props.height}rpx ${props.mode} ${props.color}`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-line {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<view class="fix-list-menu">
|
||||
<view class="inner" v-if="list.length">
|
||||
<view
|
||||
class="item"
|
||||
:class="[`${isBorder ? 'is-border' : ''}`]"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
@click="toPath(item.link)"
|
||||
>
|
||||
<view class="left">
|
||||
<image v-if="item.icon" :src="item.icon" class="icon" />
|
||||
<view class="text" :style="{ color: item.color }">
|
||||
{{ item.text }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="text">{{ item.text2 }}</text>
|
||||
<cl-icon color="#a8abb2" name="arrow-right"></cl-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-list-menu" setup>
|
||||
import { type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array as PropType<Form.Title[]>,
|
||||
default: () => {
|
||||
return [
|
||||
{
|
||||
text: "",
|
||||
text2: "",
|
||||
color: "",
|
||||
icon: "",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
isBorder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
function toPath(link: Form.Link) {
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-list-menu {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
.inner {
|
||||
.is-border {
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
.is-border:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.item {
|
||||
height: 80rpx;
|
||||
padding: 12rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.left {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
.icon {
|
||||
margin-right: 12rpx;
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
.text {
|
||||
width: 440rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
font-size: 32rpx;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
width: 160rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
.text {
|
||||
color: #a8abb2;
|
||||
font-size: 24rpx;
|
||||
margin-right: 8rpx;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
208
uni_modules/cool-fixtures/components/fix-menus/fix-menus.vue
Normal file
208
uni_modules/cool-fixtures/components/fix-menus/fix-menus.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<view class="fix-menus">
|
||||
<swiper
|
||||
class="inner"
|
||||
:style="{
|
||||
height: height,
|
||||
}"
|
||||
:circular="false"
|
||||
:indicator-dots="false"
|
||||
@change="changeCurrent"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in tabs" :key="index">
|
||||
<view
|
||||
class="row"
|
||||
:class="[`is-${style.rowNum}`]"
|
||||
v-for="(row, r) in item"
|
||||
:key="r"
|
||||
>
|
||||
<view
|
||||
class="item"
|
||||
v-for="(col, c) in row"
|
||||
:key="c"
|
||||
@click="toPath(col.link)"
|
||||
>
|
||||
<view
|
||||
:class="[`is-${style.shape}`, `is-${col.mode}`]"
|
||||
:style="{ backgroundColor: col.backgroundColor }"
|
||||
class="icon-box"
|
||||
>
|
||||
<cl-image :src="col.icon" mode="aspectFill" class="icon">
|
||||
</cl-image>
|
||||
</view>
|
||||
<span
|
||||
v-if="col.mode == 'mode-1'"
|
||||
class="text"
|
||||
:style="{ color: col.color }"
|
||||
>{{ col.text }}</span
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- 自定义指示器 -->
|
||||
<view class="custom-indicator">
|
||||
<view
|
||||
v-for="(item, index) in tabs"
|
||||
:key="index"
|
||||
class="custom-indicator-dot"
|
||||
:class="{ active: current === index }"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fix-menus">
|
||||
import { computed, ref, type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const emits = defineEmits(["jump"]);
|
||||
const props = defineProps({
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
shape: "round",
|
||||
pageNum: 1,
|
||||
rowNum: 3,
|
||||
};
|
||||
},
|
||||
},
|
||||
list: {
|
||||
type: Array as PropType<Form.Menu[]>,
|
||||
default: () => [],
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const current = ref(0);
|
||||
function changeCurrent(event: any) {
|
||||
current.value = event.detail.current;
|
||||
}
|
||||
const height = computed(() => {
|
||||
return 180 * props.style.pageNum + "rpx";
|
||||
});
|
||||
const tabs = computed(() => {
|
||||
return paginateArray(props.list, props.style.pageNum, props.style.rowNum);
|
||||
});
|
||||
|
||||
// 解决 每页1行 每行3个 的问题
|
||||
function paginateArray(data: Form.Menu[], pageNum: number, rowNum: number) {
|
||||
const result: any[] = [];
|
||||
const totalItems = data.length;
|
||||
let pageIndex = 0;
|
||||
|
||||
while (pageIndex < totalItems) {
|
||||
const page: any[] = [];
|
||||
for (let i = 0; i < pageNum && pageIndex < totalItems; i++) {
|
||||
const row: any[] = [];
|
||||
for (let j = 0; j < rowNum && pageIndex < totalItems; j++) {
|
||||
row.push(data[pageIndex]);
|
||||
pageIndex++;
|
||||
}
|
||||
page.push(row);
|
||||
}
|
||||
result.push(page);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function toPath(link: Form.Link) {
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-menus {
|
||||
width: 100%;
|
||||
padding-bottom: 20rpx;
|
||||
.inner {
|
||||
height: 100%;
|
||||
padding: 10rpx;
|
||||
box-sizing: border-box;
|
||||
.row {
|
||||
margin-bottom: 20rpx;
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 160rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.is-round {
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
.is-mode-2 {
|
||||
width: 120rpx !important;
|
||||
height: 120rpx !important;
|
||||
}
|
||||
.icon-box {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
overflow: hidden;
|
||||
.icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
font-size: 26rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.is-3 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); /* 每行3列,每列宽度相等 */
|
||||
gap: 20rpx; /* 间距 */
|
||||
}
|
||||
.is-4 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr); /* 每行3列,每列宽度相等 */
|
||||
gap: 20rpx; /* 间距 */
|
||||
}
|
||||
.is-5 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr); /* 每行3列,每列宽度相等 */
|
||||
gap: 20rpx; /* 间距 */
|
||||
}
|
||||
}
|
||||
}
|
||||
.custom-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.custom-indicator-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
background-color: #686580;
|
||||
border-radius: 50%; /* 默认圆形 */
|
||||
margin: 0 10rpx;
|
||||
transition: all 0.3s ease; /* 过渡效果 */
|
||||
}
|
||||
|
||||
.custom-indicator-dot.active {
|
||||
width: 40rpx; /* 激活状态变成长方形 */
|
||||
height: 12rpx;
|
||||
background: var(--btn-color) !important;
|
||||
border-radius: 20rpx; /* 调整边角的圆度 */
|
||||
transition: all 0.5s ease; /* 激活状态有更慢的动画过渡 */
|
||||
}
|
||||
</style>
|
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<view class="fix-picture">
|
||||
<view class="inner" @click="toPath">
|
||||
<template v-if="data.pic">
|
||||
<image mode="widthFix" :src="data.pic" class="img" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-picture" setup>
|
||||
import { type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Form.Picture>,
|
||||
default: () => {
|
||||
return {
|
||||
pic: "",
|
||||
link: {},
|
||||
};
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
function toPath() {
|
||||
const link = props.data.link;
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-picture {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :index="index">
|
||||
<view class="fix-positioning">
|
||||
<view :class="[`is-${mode}`]" class="header">
|
||||
<view class="item">
|
||||
<view v-if="mode == 'mode-1'">
|
||||
<view>{{ locationData.name }},{{ locationData.time }}</view>
|
||||
<view class="mt-10 text-size-m row-center" @click="moreStore">
|
||||
<cl-icon name="location"></cl-icon>
|
||||
<text class="mx-6">{{ locationData.store }}</text>
|
||||
<text class="text-grey">{{ locationData.distance }}</text>
|
||||
<text class="mx-6">更多门店</text>
|
||||
<cl-icon name="arrow-bottom"></cl-icon>
|
||||
</view>
|
||||
<view></view>
|
||||
</view>
|
||||
<view v-if="mode == 'mode-2'" @click="moreStore">
|
||||
<view class="row-center">
|
||||
<cl-icon name="location"></cl-icon>
|
||||
<text class="mx-6">{{ locationData.store }}</text>
|
||||
<cl-icon name="arrow-right"></cl-icon>
|
||||
</view>
|
||||
<view class="mt-10 text-size-m">
|
||||
<text class="text-grey mx-6">{{ locationData.distance }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="mode == 'mode-3'" @click="moreStore">
|
||||
<view class="row-center">
|
||||
<cl-icon name="location"></cl-icon>
|
||||
<text class="mx-6">{{ locationData.address }}</text>
|
||||
</view>
|
||||
<view class="mt-10 text-size-m">
|
||||
<text class="text-grey">由</text>
|
||||
<text class="text-grey mx-6">{{ locationData.store }}</text>
|
||||
<text class="text-grey">提供</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="is-bg" :style="headerBackground"></view>
|
||||
</view>
|
||||
<view class="banner" v-if="banner.open">
|
||||
<swiper
|
||||
:class="[{ 'is-float': float.open }]"
|
||||
:style="{ height: `${props.banner.height}rpx` }"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in banner.list" :key="index">
|
||||
<image
|
||||
:src="item.pic"
|
||||
:class="[{ 'is-float': float.open }]"
|
||||
class="banner-item"
|
||||
:style="{ height: `${props.banner.height}rpx` }"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="float" v-if="float.open">
|
||||
<view class="left" :style="leftStyle" @click="toPath(float.left.link)">
|
||||
<template v-if="float.left.mode === 'mode-1'">
|
||||
<image class="icon" mode="widthFix" :src="float.left.icon"></image>
|
||||
<view class="mt-20 text">{{ float.left.text }}</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="right" :style="rightStyle" @click="toPath(float.right.link)">
|
||||
<template v-if="float.right.mode === 'mode-1'">
|
||||
<image class="icon" mode="widthFix" :src="float.right.icon"></image>
|
||||
<view class="mt-20 text">{{ float.right.text }}</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-positioning" setup>
|
||||
import { computed, watch, ref, type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
import { useCool, useStore } from "/@/cool";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
banner: {
|
||||
type: Object as PropType<{ open: boolean; height: number; list: Form.Banner[] }>,
|
||||
default: () => {
|
||||
return {
|
||||
open: false,
|
||||
height: 750,
|
||||
list: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
float: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
open: false,
|
||||
left: {
|
||||
text: "",
|
||||
mode: "mode-1",
|
||||
icon: "",
|
||||
color: "#000",
|
||||
backgroundColor: "#FFFFFF",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
},
|
||||
right: {
|
||||
text: "",
|
||||
mode: "mode-1",
|
||||
icon: "",
|
||||
color: "#000",
|
||||
backgroundColor: "#FFFFFF",
|
||||
link: {
|
||||
name: "",
|
||||
appid: "",
|
||||
type: "",
|
||||
page: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const { service, router } = useCool();
|
||||
const { user } = useStore();
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
const headerBackground = computed(() => {
|
||||
return {
|
||||
opacity: props.styleColor.opacity,
|
||||
backgroundColor: props.styleColor.backgroundColor,
|
||||
};
|
||||
});
|
||||
|
||||
const leftStyle = computed(() => {
|
||||
let url = "";
|
||||
if (props.float.left.mode === "mode-2") {
|
||||
url = props.float.left.icon;
|
||||
}
|
||||
return {
|
||||
backgroundColor: props.float.left.backgroundColor,
|
||||
color: props.float.left.color,
|
||||
backgroundImage: `url('${url}')`,
|
||||
};
|
||||
});
|
||||
|
||||
const rightStyle = computed(() => {
|
||||
let url = "";
|
||||
if (props.float.right.mode === "mode-2") {
|
||||
url = props.float.right.icon;
|
||||
}
|
||||
return {
|
||||
backgroundColor: props.float.right.backgroundColor,
|
||||
color: props.float.right.color,
|
||||
backgroundImage: `url('${url}')`,
|
||||
};
|
||||
});
|
||||
const locationData = ref({
|
||||
name: "张大仙",
|
||||
time: "早上好",
|
||||
store: "南村番禺店",
|
||||
distance: "距离你 0.7km",
|
||||
address: "广州番禺南村镇(建奇大厦5号)...",
|
||||
});
|
||||
watch(
|
||||
() => props.mode,
|
||||
async (newValue) => {
|
||||
locationData.value.time = getTimePeriod();
|
||||
getStore();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function distanceInMeters(distanceInKilometers: number) {
|
||||
let dis = "0m";
|
||||
if (distanceInKilometers < 1) {
|
||||
// 如果距离小于1公里,将其转换为米(1公里 = 1000米)
|
||||
dis = Math.floor(distanceInKilometers * 1000) + "m";
|
||||
} else {
|
||||
dis = distanceInKilometers + "km";
|
||||
}
|
||||
|
||||
return "距离你 " + dis;
|
||||
}
|
||||
|
||||
function getTimePeriod() {
|
||||
const now = new Date();
|
||||
const hours = now.getHours();
|
||||
|
||||
if (hours >= 5 && hours < 12) {
|
||||
return "早上好!"; // 5:00 - 11:59
|
||||
} else if (hours >= 12 && hours < 14) {
|
||||
return "中午好!"; // 12:00 - 13:59
|
||||
} else if (hours >= 14 && hours < 18) {
|
||||
return "下午好!"; // 14:00 - 17:59
|
||||
} else if (hours >= 18 && hours < 24) {
|
||||
return "晚上好!"; // 18:00 - 23:59
|
||||
} else {
|
||||
return "深夜好!"; // 00:00 - 4:59
|
||||
}
|
||||
}
|
||||
|
||||
function getStore() {
|
||||
uni.getLocation({
|
||||
type: "wgs84",
|
||||
success(res) {
|
||||
service.store.sample
|
||||
.vicinity({
|
||||
...res,
|
||||
size: 1,
|
||||
page: 1,
|
||||
})
|
||||
.then((res: any) => {
|
||||
if (res.list.length) {
|
||||
const { intact, sampleName, distance } = res.list[0];
|
||||
locationData.value.distance = distanceInMeters(distance);
|
||||
locationData.value.store = sampleName;
|
||||
locationData.value.address = intact;
|
||||
locationData.value.name = user.info?.nickName || "游客";
|
||||
}
|
||||
});
|
||||
},
|
||||
fail(result) {
|
||||
// 第一次拒绝后,下次点击提示打开设置授权弹窗
|
||||
uni.showModal({
|
||||
title: "定位获取失败",
|
||||
content: "附近门店查找失败,是否去设置授权打开?",
|
||||
success(modal: any) {
|
||||
if (modal.confirm) {
|
||||
uni.openSetting({
|
||||
success: (res) => {},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
function moreStore() {
|
||||
router.push("/pages/partnership/sample/vicinity");
|
||||
}
|
||||
|
||||
function toPath(link: Form.Link) {
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-positioning {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 140rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
.header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
min-height: 140rpx;
|
||||
|
||||
.is-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
padding: 20rpx;
|
||||
}
|
||||
}
|
||||
.banner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.banner-item {
|
||||
width: 750rpx;
|
||||
max-width: 100%;
|
||||
min-height: 750rpx;
|
||||
height: 100%;
|
||||
}
|
||||
.float {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
|
||||
.left {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
.right {
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
.left,
|
||||
.right {
|
||||
width: 240rpx;
|
||||
height: 280rpx;
|
||||
border-radius: 12rpx;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.text {
|
||||
font-size: 32rpx;
|
||||
font-family: PingFang SC;
|
||||
font-weight: bold;
|
||||
}
|
||||
.icon {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-mode-4 {
|
||||
display: none;
|
||||
}
|
||||
.row-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.is-float {
|
||||
min-height: 750rpx;
|
||||
:deep(.uni-swiper-wrapper) {
|
||||
overflow: inherit !important;
|
||||
}
|
||||
}
|
||||
.mt-20 {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.mt-10 {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
.text-size-m {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.mx-6 {
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
.text-grey {
|
||||
color: #9e9e9e;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :index="index">
|
||||
<view class="fix-rich-text">
|
||||
<mp-html :content="data"></mp-html>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-rich-text" setup>
|
||||
import { baseProps } from "../../hooks";
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-rich-text {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
:deep(img) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto; /* 保持图像的原始纵横比 */
|
||||
display: block; /* 消除图像下方的默认间隙(如果是内联元素的话)*/
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<div class="fix-rubik-cube">
|
||||
<div
|
||||
class="box"
|
||||
:class="[`is-${data.mode}`]"
|
||||
:style="{
|
||||
gap: `${data.gap}rpx`,
|
||||
'--gap': `${data.gap}rpx`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="item"
|
||||
v-for="(item, index) in data.list"
|
||||
:key="index"
|
||||
:class="[`is-${data.mode}-item-${index}`]"
|
||||
@click="toPath(item.link)"
|
||||
>
|
||||
<image class="icon" :src="item.icon" mode="widthFix"></image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-rubik-cube" setup>
|
||||
import { type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Form.RubikCube>,
|
||||
default: () => {
|
||||
return {
|
||||
mode: "mode-1",
|
||||
gap: 0,
|
||||
list: [
|
||||
{
|
||||
icon: "",
|
||||
tips: "宽度375px",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "",
|
||||
tips: "宽度375px",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
function toPath(link: Form.Link) {
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
display: flex;
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
.icon {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-mode-1 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.is-mode-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.is-mode-3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
.item {
|
||||
&.is-mode-3-item-0 {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
&.is-mode-3-item-1 {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
&.is-mode-3-item-2 {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-mode-4 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
.item {
|
||||
&.is-mode-4-item-0 {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
&.is-mode-4-item-1 {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
&.is-mode-4-item-2 {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:statusBar="statusBar"
|
||||
:flowInner="false"
|
||||
:index="index"
|
||||
>
|
||||
<view class="search-bar" @tap="toSearch">
|
||||
<view class="search-bar__inner" :style="style" :class="[`is-${mode}`]">
|
||||
<cl-icon name="search" :margin="[0, 12, 0, 0]"> </cl-icon>
|
||||
<cl-text :value="placeholder" :color="styleColor.color"></cl-text>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fix-search">
|
||||
import { computed } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { baseProps } from "../../hooks";
|
||||
const { router } = useCool();
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "mode-1",
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#f6f7fa",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入关键字进行搜索",
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
statusBar: Boolean,
|
||||
});
|
||||
|
||||
function toSearch() {
|
||||
router.push("/pages/goods/search");
|
||||
}
|
||||
|
||||
const style = computed(() => {
|
||||
const { borderTopLR, borderBottomLR } = props.styleSpacing;
|
||||
|
||||
const { color } = props.styleColor;
|
||||
|
||||
return {
|
||||
color: color,
|
||||
backgroundColor: props.backgroundColor,
|
||||
borderRadius: `${borderTopLR}rpx ${borderTopLR}rpx ${borderBottomLR}rpx ${borderBottomLR}rpx`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-bar {
|
||||
width: 100%;
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70rpx;
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
box-sizing: border-box;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.is-mode-1 {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.is-mode-2 {
|
||||
justify-content: center;
|
||||
}
|
||||
.is-mode-3 {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="fix-suspension" :style="baseStyle">
|
||||
<view class="menu" :style="innerStyle" @click="toggleMenu">
|
||||
<image class="icon" :src="list[0].icon"></image>
|
||||
</view>
|
||||
<view class="menu-slide">
|
||||
<view
|
||||
v-for="(item, index) in visibleItems"
|
||||
:key="index"
|
||||
class="sub-menu"
|
||||
:style="subMenuStyle(index)"
|
||||
@click="toPath(item.link)"
|
||||
>
|
||||
<image class="icon" :src="item.icon"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-suspension" setup>
|
||||
import { computed, ref, type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "left",
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "rgba(0, 0, 0, 0.8)",
|
||||
},
|
||||
offsetBottom: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
list: {
|
||||
type: Array as PropType<Form.Suspension[]>,
|
||||
default: () => [
|
||||
{
|
||||
icon: "https://tsb-yx.oss-cn-guangzhou.aliyuncs.com/app/mini/float-menu.png",
|
||||
tips: "宽高128px",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
const baseStyle = computed(() => {
|
||||
return {
|
||||
bottom: `${props.offsetBottom}rpx`,
|
||||
[props.mode]: "10px",
|
||||
};
|
||||
});
|
||||
|
||||
const innerStyle = computed(() => {
|
||||
return {
|
||||
background: props.backgroundColor,
|
||||
boxShadow: props.shadow ? "0 4px 10px rgba(0, 0, 0, 0.3)" : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
const isMenuOpen = ref(false);
|
||||
const toggleMenu = () => {
|
||||
if (props.list.length > 1) {
|
||||
isMenuOpen.value = !isMenuOpen.value;
|
||||
} else {
|
||||
toPath(props.list[0].link);
|
||||
}
|
||||
};
|
||||
|
||||
// 控制显示的子按钮
|
||||
const visibleItems = computed(() => {
|
||||
return isMenuOpen.value ? props.list.slice(1) : [];
|
||||
});
|
||||
|
||||
// 子菜单项的样式和动画效果
|
||||
const subMenuStyle = (index: number) => {
|
||||
const offset = 90; // 每个子菜单项的高度
|
||||
return {
|
||||
transition: `transform 0.3s ease, opacity 0.3s ease`,
|
||||
transform: isMenuOpen.value ? `translateY(-${(index + 1) * offset}rpx)` : "translateY(0)",
|
||||
opacity: isMenuOpen.value ? 1 : 0,
|
||||
background: props.backgroundColor,
|
||||
boxShadow: props.shadow ? "0 4px 10px rgba(0, 0, 0, 0.3)" : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
function toPath(link: Form.Link) {
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-suspension {
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
z-index: 400;
|
||||
|
||||
.menu {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.menu-slide {
|
||||
position: absolute; /* 依赖于父元素定位 */
|
||||
bottom: 80rpx; /* 让子菜单从主菜单上方展开 */
|
||||
left: 0; /* 根据需要调整位置 */
|
||||
width: 80rpx; /* 确保子菜单能够展示 */
|
||||
transition: all 0.3s ease;
|
||||
overflow: visible; /* 确保子菜单不被裁剪 */
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
position: absolute;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
transition:
|
||||
transform 0.3s ease,
|
||||
opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
129
uni_modules/cool-fixtures/components/fix-title/fix-title.vue
Normal file
129
uni_modules/cool-fixtures/components/fix-title/fix-title.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<fix-base-style
|
||||
:styleSpacing="styleSpacing"
|
||||
:styleColor="styleColor"
|
||||
:position="position"
|
||||
:index="index"
|
||||
>
|
||||
<view class="fix-title">
|
||||
<view class="inner">
|
||||
<view class="left" @click="toPath(left.link)">
|
||||
<image v-if="left.icon" :src="left.icon" class="icon" />
|
||||
<view class="text" :style="{ color: left.color }" :class="[`is-${mode}`]">
|
||||
{{ left.text }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="right" @click="toPath(right.link)">
|
||||
<text class="text" :style="{ color: right.color }">{{ right.text }}</text>
|
||||
<image v-if="right.icon" :src="right.icon" class="icon" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-title" setup>
|
||||
import { type PropType } from "vue";
|
||||
import type { Form } from "../../types/form";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "left",
|
||||
},
|
||||
|
||||
left: {
|
||||
type: Object as PropType<Form.Title>,
|
||||
default: () => {
|
||||
return {
|
||||
text: "标题内容",
|
||||
color: "#000",
|
||||
icon: "",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
right: {
|
||||
type: Object as PropType<Form.Title>,
|
||||
default: () => {
|
||||
return {
|
||||
text: "查看",
|
||||
color: "#a8abb2",
|
||||
icon: "",
|
||||
link: {
|
||||
name: "",
|
||||
type: "",
|
||||
appid: "",
|
||||
page: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
const emits = defineEmits(["jump"]);
|
||||
|
||||
function toPath(link: Form.Link) {
|
||||
emits("jump", link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-title {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.inner {
|
||||
height: 80rpx;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 20rpx;
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
.icon {
|
||||
margin-right: 20rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
.text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.is-left {
|
||||
text-align: left;
|
||||
}
|
||||
.is-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
.icon {
|
||||
margin-left: 20rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
.text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<view class="fix-top-bar" v-if="isShow">
|
||||
<view
|
||||
v-if="fixed"
|
||||
:style="{
|
||||
paddingTop: `${(statusBar ? 0 : statusBarHeight) + 44}px`,
|
||||
}"
|
||||
></view>
|
||||
<cl-topbar
|
||||
:title="title"
|
||||
:color="color"
|
||||
:border="border"
|
||||
:fixed="fixed"
|
||||
:showBack="showBack"
|
||||
:withMp="true"
|
||||
:background-color="backgroundColor"
|
||||
></cl-topbar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fix-top-bar">
|
||||
const { statusBarHeight = 0 } = uni.getSystemInfoSync();
|
||||
const props = defineProps({
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#fff",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "black",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
isShow: Boolean,
|
||||
showBack: Boolean,
|
||||
border: Boolean,
|
||||
fixed: Boolean,
|
||||
statusBar: Boolean,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
65
uni_modules/cool-fixtures/components/fix-video/fix-video.vue
Normal file
65
uni_modules/cool-fixtures/components/fix-video/fix-video.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing" :styleColor="styleColor" :index="index">
|
||||
<div class="fix-video">
|
||||
<div class="inner" v-if="video">
|
||||
<video
|
||||
:src="video"
|
||||
:poster="cover"
|
||||
class="vertical"
|
||||
controls
|
||||
:class="[mode == 'horizontal' ? 'horizontal' : 'vertical']"
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-video" setup>
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: "horizontal",
|
||||
},
|
||||
video: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
cover: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "local",
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
...baseProps,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-video {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block; /* 避免 inline 元素造成的间隙 */
|
||||
object-fit: cover;
|
||||
}
|
||||
.vertical {
|
||||
width: 750rpx;
|
||||
height: 1334rpx; /* 高度根据宽高比自动调整 */
|
||||
object-fit: contain; /* 保持视频的宽高比,不会拉伸 */
|
||||
}
|
||||
.horizontal {
|
||||
width: 750rpx; /* 屏幕宽度适配 */
|
||||
height: calc(750rpx * 9 / 16); /* 适用于 16:9 的宽高比 */
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<fix-base-style :styleSpacing="styleSpacing">
|
||||
<view class="fix-wechat" v-show="is_show">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<official-account @bindload="load" @binderror="error"></official-account>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</fix-base-style>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="fix-wechat" setup>
|
||||
import { ref } from "vue";
|
||||
import { baseProps } from "../../hooks";
|
||||
|
||||
defineProps({ ...baseProps });
|
||||
const is_show = ref(false);
|
||||
function load() {
|
||||
is_show.value = true;
|
||||
}
|
||||
function error() {
|
||||
is_show.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fix-wechat {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
8
uni_modules/cool-fixtures/config.ts
Normal file
8
uni_modules/cool-fixtures/config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { ModuleConfig } from "/@/cool";
|
||||
|
||||
export default (): ModuleConfig => {
|
||||
return {
|
||||
// 描述
|
||||
description: "在coo-uni基础上开发的自定义页面,修改或新增组件非常简单!",
|
||||
};
|
||||
};
|
36
uni_modules/cool-fixtures/hooks/base-props.ts
Normal file
36
uni_modules/cool-fixtures/hooks/base-props.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export const baseProps = {
|
||||
styleSpacing: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
marginLR: 0,
|
||||
padding: 0,
|
||||
borderTopLR: 0,
|
||||
borderBottomLR: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
styleColor: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
color: "#000",
|
||||
backgroundColor: "#FFFFFF",
|
||||
opacity: 1,
|
||||
};
|
||||
},
|
||||
},
|
||||
position: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
mode: "static",
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
isSeat: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
1
uni_modules/cool-fixtures/hooks/index.ts
Normal file
1
uni_modules/cool-fixtures/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./base-props";
|
54
uni_modules/cool-fixtures/pages/detail.vue
Normal file
54
uni_modules/cool-fixtures/pages/detail.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<cl-page :backgroundColor="data.background" :backgroundImage="data.backgroundImage" :statusBar="!!data.statusBar"
|
||||
:statusBarBackground="data.statusBarColor">
|
||||
<fix-index :list="data.data" :statusBar="!!data.statusBar"></fix-index>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { onReady, onPageScroll, onLoad } from "@dcloudio/uni-app";
|
||||
import { useUi } from "/$/cool-ui";
|
||||
defineProps({ id: String });
|
||||
const { service } = useCool();
|
||||
const ui = useUi();
|
||||
|
||||
const data = ref({
|
||||
background: "#f5f6fa",
|
||||
backgroundImage: "",
|
||||
statusBarColor: "",
|
||||
statusBar: 1,
|
||||
name: "",
|
||||
data: [],
|
||||
});
|
||||
|
||||
function getPage(id : string) {
|
||||
service.fixtures.mould
|
||||
.getPage({ id })
|
||||
.then((res : any) => {
|
||||
data.value = res;
|
||||
})
|
||||
.catch((e) => {
|
||||
ui.showToast(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
nextTick().then(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
});
|
||||
}
|
||||
// 加载数据
|
||||
function loadSuccess(query : any) {
|
||||
getPage(query.id);
|
||||
}
|
||||
onReady(() => { });
|
||||
onLoad((query : any) => {
|
||||
uni.showLoading({
|
||||
title: "加载中...",
|
||||
mask: true,
|
||||
});
|
||||
loadSuccess(query)
|
||||
});
|
||||
onPageScroll(() => { });
|
||||
</script>
|
54
uni_modules/cool-fixtures/pages/preview.vue
Normal file
54
uni_modules/cool-fixtures/pages/preview.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<cl-page :backgroundColor="data.background" :backgroundImage="data.backgroundImage" :statusBar="!!data.statusBar"
|
||||
:statusBarBackground="data.statusBarColor">
|
||||
<fix-index :list="data.form" :statusBar="!!data.statusBar"></fix-index>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { onReady, onPageScroll, onLoad } from "@dcloudio/uni-app";
|
||||
import { useUi } from "/$/cool-ui";
|
||||
defineProps({ id: String });
|
||||
const { service } = useCool();
|
||||
const ui = useUi();
|
||||
|
||||
const data = ref({
|
||||
background: "#f5f6fa",
|
||||
backgroundImage: "",
|
||||
statusBarColor: "",
|
||||
statusBar: 1,
|
||||
name: "",
|
||||
form: [],
|
||||
});
|
||||
|
||||
function getPage(id : string) {
|
||||
service.fixtures.mould
|
||||
.getPage({ id })
|
||||
.then((res : any) => {
|
||||
data.value = res;
|
||||
})
|
||||
.catch((e) => {
|
||||
ui.showToast(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
nextTick().then(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
});
|
||||
}
|
||||
// 加载数据
|
||||
function loadSuccess(query : any) {
|
||||
getPage(query.id);
|
||||
}
|
||||
onReady(() => { });
|
||||
onLoad((query : any) => {
|
||||
uni.showLoading({
|
||||
title: "加载中...",
|
||||
mask: true,
|
||||
});
|
||||
loadSuccess(query)
|
||||
});
|
||||
onPageScroll(() => { });
|
||||
</script>
|
23
uni_modules/cool-fixtures/pages_init.json
Normal file
23
uni_modules/cool-fixtures/pages_init.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "uni_modules/cool-fixtures/pages",
|
||||
"pages": [
|
||||
{
|
||||
"path": "detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "自定义页面"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "preview",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "页面预览"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
109
uni_modules/cool-fixtures/types/form.d.ts
vendored
Normal file
109
uni_modules/cool-fixtures/types/form.d.ts
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
export declare namespace Form {
|
||||
interface Menu {
|
||||
text: string,
|
||||
useText: boolean,
|
||||
mode: string,
|
||||
link: Link,
|
||||
icon: string,
|
||||
color: string,
|
||||
backgroundColor: string
|
||||
}
|
||||
interface Link {
|
||||
page: string,
|
||||
appid: string,
|
||||
type: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
interface Banner {
|
||||
pic: string,
|
||||
link: Link
|
||||
}
|
||||
|
||||
interface Spacing {
|
||||
marginTop: number,
|
||||
marginBottom: number,
|
||||
marginLR: number,
|
||||
padding: number,
|
||||
borderTopLR: number,
|
||||
borderBottomLR: number
|
||||
}
|
||||
interface Color {
|
||||
color: string,
|
||||
backgroundColor: string,
|
||||
opacity: number
|
||||
}
|
||||
interface Picture {
|
||||
pic: string,
|
||||
link: Link
|
||||
}
|
||||
interface Title {
|
||||
text: string,
|
||||
text2?: string,
|
||||
color: string,
|
||||
icon: string,
|
||||
link: Link
|
||||
}
|
||||
|
||||
interface Hot {
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
relativeX: number,
|
||||
relativeY: number,
|
||||
relativeW: number,
|
||||
relativeH: number,
|
||||
index: number,
|
||||
link: Link
|
||||
}
|
||||
interface HotImage {
|
||||
pic: string,
|
||||
link: Link,
|
||||
width: number,
|
||||
height: number,
|
||||
attr: Hot[]
|
||||
}
|
||||
interface RubikCubeMode {
|
||||
type: string,
|
||||
label: string,
|
||||
list: {
|
||||
icon: string,
|
||||
tips: string,
|
||||
link: Link
|
||||
}[]
|
||||
}
|
||||
interface RubikCube {
|
||||
mode: string,
|
||||
gap: number,
|
||||
list: {
|
||||
icon: string,
|
||||
tips: string,
|
||||
link: Link
|
||||
}[]
|
||||
}
|
||||
interface Goods {
|
||||
mode: string,
|
||||
source: string,
|
||||
gap: number,
|
||||
num: number,
|
||||
attribute: number,
|
||||
isVoucher: boolean,
|
||||
isShadow: boolean,
|
||||
type: { name: string, pic?: string, id: number }[],
|
||||
list: {
|
||||
mainPic: string,
|
||||
title: string,
|
||||
price: number,
|
||||
sold: number,
|
||||
attribute?: number,
|
||||
id: number,
|
||||
}[]
|
||||
}
|
||||
|
||||
interface Suspension {
|
||||
icon: string;
|
||||
tips: string,
|
||||
link: Link
|
||||
}
|
||||
}
|
@ -5,11 +5,12 @@
|
||||
`theme-${app.theme.name}`,
|
||||
{
|
||||
'is-fullscreen': fullscreen,
|
||||
'is-safe-area-bottom': fullscreen,
|
||||
},
|
||||
]"
|
||||
:style="{
|
||||
padding: parseRpx(padding),
|
||||
height,
|
||||
height
|
||||
}"
|
||||
>
|
||||
<!-- 加载框 -->
|
||||
@ -42,14 +43,16 @@
|
||||
<view
|
||||
class="cl-page__bg"
|
||||
:style="{
|
||||
background,
|
||||
backgroundColor: background,
|
||||
backgroundImage: 'url(' + backgroundImage + ')',
|
||||
}"
|
||||
></view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, getCurrentInstance, onMounted } from "vue";
|
||||
import { useApp, useCool } from "/@/cool";
|
||||
import { onPageScroll } from "@dcloudio/uni-app";
|
||||
import { computed, defineComponent, ref, reactive, getCurrentInstance, onMounted } from "vue";
|
||||
import { useApp, useCool} from "/@/cool";
|
||||
import { parseRpx } from "/@/cool/utils";
|
||||
import { isString } from "lodash-es";
|
||||
|
||||
@ -73,6 +76,11 @@ export default defineComponent({
|
||||
statusBarBackground: String,
|
||||
// 页面背景色
|
||||
backgroundColor: String,
|
||||
// 背景图片
|
||||
backgroundImage: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
@ -89,8 +97,9 @@ export default defineComponent({
|
||||
const { proxy }: any = getCurrentInstance();
|
||||
|
||||
// 是否显示导航栏
|
||||
const statusBar = info?.isCustomNavbar ? props.statusBar : false;
|
||||
|
||||
const statusBar = computed(() => {
|
||||
return info?.isCustomNavbar ? props.statusBar : false;
|
||||
});
|
||||
// 背景色
|
||||
const background = computed(() => {
|
||||
return (
|
||||
@ -112,14 +121,15 @@ export default defineComponent({
|
||||
// #ifdef H5
|
||||
h = windowHeight;
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
h = screenHeight - statusBarHeight;
|
||||
|
||||
h = screenHeight - (statusBar ? statusBarHeight : 0);
|
||||
if (!info?.isCustomNavbar) {
|
||||
h -= 44;
|
||||
h -= statusBarHeight;
|
||||
}
|
||||
|
||||
// #endif
|
||||
if (props.fullscreen) return h + "px";
|
||||
|
||||
return h - (safeAreaInsets?.bottom || 0) + "px";
|
||||
});
|
||||
@ -199,11 +209,11 @@ export default defineComponent({
|
||||
uni.createSelectorQuery()
|
||||
.in(proxy)
|
||||
.select(".cl-page")
|
||||
.boundingClientRect((a) => {
|
||||
.boundingClientRect((a: any) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(proxy)
|
||||
.select(".safe-area-bottom")
|
||||
.boundingClientRect((b) => {
|
||||
.boundingClientRect((b: any) => {
|
||||
const scrollTop = top + (a?.height || 0) - (b?.bottom || 0);
|
||||
|
||||
uni.pageScrollTo({
|
||||
@ -215,13 +225,18 @@ export default defineComponent({
|
||||
})
|
||||
.exec();
|
||||
};
|
||||
const scrollTop = ref(0);
|
||||
|
||||
onPageScroll((e: any) => {
|
||||
scrollTop.value = e.scrollTop;
|
||||
});
|
||||
return {
|
||||
app,
|
||||
background,
|
||||
height,
|
||||
refs,
|
||||
setRefs,
|
||||
scrollTop,
|
||||
loader,
|
||||
parseRpx,
|
||||
statusBar,
|
||||
|
Loading…
Reference in New Issue
Block a user