基础插件修改

This commit is contained in:
lixin 2025-01-09 16:40:44 +08:00
parent d2f735d2f6
commit 28e34c191d
28 changed files with 2815 additions and 11 deletions

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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) {
// 11 = 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -0,0 +1,8 @@
import type { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
// 描述
description: "在coo-uni基础上开发的自定义页面修改或新增组件非常简单",
};
};

View 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,
};
},
},
}

View File

@ -0,0 +1 @@
export * from "./base-props";

View 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>

View 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>

View 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": "页面预览"
}
}
]
}
]
}

View 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
}
}

View File

@ -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,