1. 特色方功能迭代(固定价格),固定价格目前统一使用药品id(-102),后期会考虑新增特色方的时候自动添加一个特色方药品
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2. 退款的时候可以退回分成
This commit is contained in:
194
apps/web-antd/src/components/float-fab/FloatFab.vue
Normal file
194
apps/web-antd/src/components/float-fab/FloatFab.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* PC 可拖拽吸附悬浮按钮
|
||||
*/
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
|
||||
import { FileTextOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** localStorage 位置缓存 key */
|
||||
storageKey?: string;
|
||||
/** 显示文案 */
|
||||
label?: string;
|
||||
/** 角标数量 */
|
||||
badge?: number;
|
||||
/** 吸附阈值 px */
|
||||
snapThreshold?: number;
|
||||
zIndex?: number;
|
||||
}>(),
|
||||
{
|
||||
storageKey: 'pc_float_fab_pos',
|
||||
label: '传方',
|
||||
badge: 0,
|
||||
snapThreshold: 40,
|
||||
zIndex: 9999,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [];
|
||||
}>();
|
||||
|
||||
const posX = ref(0);
|
||||
const posY = ref(0);
|
||||
const fabSize = 56;
|
||||
const dragging = ref(false);
|
||||
const moved = ref(false);
|
||||
const startX = ref(0);
|
||||
const startY = ref(0);
|
||||
const originX = ref(0);
|
||||
const originY = ref(0);
|
||||
|
||||
const fabStyle = computed(() => ({
|
||||
left: `${posX.value}px`,
|
||||
top: `${posY.value}px`,
|
||||
zIndex: props.zIndex,
|
||||
}));
|
||||
|
||||
const badgeText = computed(() => {
|
||||
if (props.badge <= 0) return '';
|
||||
return props.badge > 99 ? '99+' : String(props.badge);
|
||||
});
|
||||
|
||||
function initPosition() {
|
||||
const cached = localStorage.getItem(props.storageKey);
|
||||
if (cached) {
|
||||
try {
|
||||
const data = JSON.parse(cached);
|
||||
posX.value = data.posX ?? window.innerWidth - fabSize - 24;
|
||||
posY.value = data.posY ?? window.innerHeight * 0.6;
|
||||
} catch {
|
||||
posX.value = window.innerWidth - fabSize - 24;
|
||||
posY.value = window.innerHeight * 0.6;
|
||||
}
|
||||
} else {
|
||||
posX.value = window.innerWidth - fabSize - 24;
|
||||
posY.value = window.innerHeight * 0.6;
|
||||
}
|
||||
clampPos();
|
||||
}
|
||||
|
||||
function savePosition() {
|
||||
localStorage.setItem(
|
||||
props.storageKey,
|
||||
JSON.stringify({ posX: posX.value, posY: posY.value }),
|
||||
);
|
||||
}
|
||||
|
||||
function clampPos() {
|
||||
posX.value = Math.max(8, Math.min(posX.value, window.innerWidth - fabSize - 8));
|
||||
posY.value = Math.max(60, Math.min(posY.value, window.innerHeight - fabSize - 24));
|
||||
}
|
||||
|
||||
/** 松手吸附到最近左/右边缘 */
|
||||
function snapToEdge() {
|
||||
const centerX = posX.value + fabSize / 2;
|
||||
if (centerX < window.innerWidth / 2) {
|
||||
posX.value = props.snapThreshold;
|
||||
} else {
|
||||
posX.value = window.innerWidth - fabSize - props.snapThreshold;
|
||||
}
|
||||
clampPos();
|
||||
savePosition();
|
||||
}
|
||||
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
if (e.button !== 0) return;
|
||||
dragging.value = true;
|
||||
moved.value = false;
|
||||
startX.value = e.clientX;
|
||||
startY.value = e.clientY;
|
||||
originX.value = posX.value;
|
||||
originY.value = posY.value;
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
if (!dragging.value) return;
|
||||
const dx = e.clientX - startX.value;
|
||||
const dy = e.clientY - startY.value;
|
||||
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) moved.value = true;
|
||||
posX.value = originX.value + dx;
|
||||
posY.value = originY.value + dy;
|
||||
clampPos();
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
if (dragging.value && moved.value) {
|
||||
snapToEdge();
|
||||
} else if (!moved.value) {
|
||||
emit('click');
|
||||
}
|
||||
dragging.value = false;
|
||||
}
|
||||
|
||||
onMounted(initPosition);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="float-fab" :style="fabStyle" @mousedown="onMouseDown">
|
||||
<div class="fab-inner">
|
||||
<FileTextOutlined class="fab-icon" />
|
||||
<span v-if="label" class="fab-label">{{ label }}</span>
|
||||
</div>
|
||||
<span v-if="badgeText" class="fab-badge">{{ badgeText }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.float-fab {
|
||||
position: fixed;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
.float-fab:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.fab-inner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6acdbb, #00a88a);
|
||||
box-shadow: 0 4px 16px rgba(0, 168, 138, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
.fab-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
.fab-label {
|
||||
font-size: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.fab-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
min-width: 12px;
|
||||
height: 12px;
|
||||
padding: 0 4px;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user