소스 검색

Merge branch 'feature/task-217' into develop

cc12458 1 일 전
부모
커밋
00455017b8
2개의 변경된 파일74개의 추가작업 그리고 17개의 파일을 삭제
  1. 64 9
      src/modules/report/scheme.page.vue
  2. 10 8
      src/request/model/scheme.model.ts

+ 64 - 9
src/modules/report/scheme.page.vue

@@ -6,6 +6,7 @@ import { getReportSchemeMethod } from '@/request/api/report.api';
 import { useRouteParams }        from '@vueuse/router';
 import { useWatcher }            from 'alova/client';
 import { useRouter } from 'vue-router';
+import QrcodeVue from 'qrcode.vue';
 
 import MiniProgram from '@/components/MiniProgram.vue';
 
@@ -51,18 +52,19 @@ const panelConfig = reactive({
   onError() {},
   onComplete(event?: Event) {},
 });
+const pageHeader = useTemplateRef('page-header');
 const container = useTemplateRef('container');
 const getHeightAndScrollTop = (value?: HTMLElement | string) => {
   const el = typeof value === 'string' ? container.value?.querySelector<HTMLDivElement>(`#${value}`) : value;
   if (!el) return true;
   el.scrollIntoView({ behavior: 'instant', block: 'start' });
-  const rect = el.getBoundingClientRect();
-  const maxHeight = window.innerHeight - rect.top;
-  const height = maxHeight - rect.height;
+  const maxHeight = window.innerHeight - (pageHeader.value?.getBoundingClientRect().height ?? 0);
+  const height = window.innerHeight - el.getBoundingClientRect().bottom - Number.parseFloat(getComputedStyle(el).marginBottom);
   panelConfig.anchors = [0, height, maxHeight];
   panelConfig.height = height;
   panelConfig.fullHeight = maxHeight;
 };
+let lastFrameSrc: string;
 async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
   if (panelConfig.goods === goods) {
     panelConfig.height = panelConfig.anchors[panelConfig.anchors.length - 1];
@@ -70,16 +72,20 @@ async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
   }
 
   if (goods.type === 'link') {
-    const toast = Toast.loading(500);
+    const toast = lastFrameSrc !== goods.value ? Toast.loading(500) : void 0;
+    lastFrameSrc = goods.value;
     panelConfig.goods = goods;
     panelConfig.onComplete = () => {
-      toast.close();
+      toast?.close();
       panelConfig.height = panelConfig.anchors[panelConfig.anchors.length - 1];
     };
     panelConfig.onError = () => {
       Toast.error(`链接加载错误`)
       panelConfig.height = 0;
     };
+    if (!toast) setTimeout(panelConfig.onComplete, 100);
+  } else if (goods.type === 'miniprogram') {
+    return openGoodsScan(goods);
   } else {
     Toast.warning(`暂不支持该操作 (${goods.type})`);
     return;
@@ -89,13 +95,37 @@ async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
     const height = window.innerHeight * 0.8;
     panelConfig.anchors = [0, height];
     panelConfig.height = height;
-    panelConfig.fullHeight = window.innerHeight;
+    panelConfig.fullHeight = window.innerHeight - (pageHeader.value?.getBoundingClientRect().height ?? 0);
   }
 }
+const qrConfig = reactive({
+  size: 300,
+  margin: 2,
+  background: '#0f2925',
+  foreground: '#38ff6e',
+});
+const scanConfig = reactive({
+  show: false,
+  title: '微信扫一扫购买',
+  mode: 'img' as 'img' | 'qr',
+  url: '',
+});
+async function openGoodsScan(goods: SchemeGoodsProps) {
+  if (goods.type === 'link') {
+    scanConfig.mode = 'qr';
+  } else if (goods.type === 'miniprogram') {
+    scanConfig.mode = 'img';
+  } else {
+    Toast.warning(`暂不支持该操作 (${goods.type})`);
+    return;
+  }
+  scanConfig.url = goods.value;
+  scanConfig.show = true;
+}
 </script>
 <template>
   <div>
-    <div class="page-header flex py-4 px-4">
+    <div ref="page-header" class="page-header flex py-4 px-4">
       <div class="grow shrink-0 h-full min-w-16"></div>
       <div class="grow-[3] shrink mx-2 flex flex-col justify-center overflow-hidden">
         <div class="font-bold text-3xl text-nowrap text-center tracking-wide overflow-ellipsis overflow-hidden">
@@ -145,7 +175,7 @@ async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
     </div>
 
     <PreviewLinkSlot v-slot="{ src, complete, error }">
-      <iframe v-if="src" :src="src" @load="complete!" @error="error!"></iframe>
+      <iframe v-if="src" :src="src" :style="{ maxHeight: panelConfig.height - 30 + 'px' }" @load="complete!" @error="error!"></iframe>
     </PreviewLinkSlot>
     <van-floating-panel
       ref="panel-wrapper-ref"
@@ -157,13 +187,27 @@ async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
     >
       <template #header>
         <div class="van-floating-panel__header !justify-between">
-          <div></div>
+          <div><van-icon class="pl-2" name="qr" @click="panelConfig.height = 0;openGoodsScan(panelConfig.goods)" /></div>
           <div class="van-floating-panel__header-bar"></div>
           <van-icon class="pr-2" name="cross" @click="panelConfig.height = 0" />
         </div>
       </template>
       <ReusePreviewLink v-if="panelConfig.goods?.type === 'link'" :src="panelConfig.goods?.value" :complete="panelConfig.onComplete"></ReusePreviewLink>
     </van-floating-panel>
+    <van-dialog
+      v-model:show="scanConfig.show"
+      :title="scanConfig.title"
+      cancel-button-text="好的"
+      show-cancel-button
+      :show-confirm-button="false"
+      close-on-click-overlay
+    >
+      <div class="scan-content">
+        <qrcode-vue v-if="scanConfig.mode === 'qr'" :value="scanConfig.url" v-bind="qrConfig"></qrcode-vue>
+        <img v-else-if="scanConfig.mode === 'img'" :src="scanConfig.url" alt="二维码">
+        <div v-else>{{ scanConfig.url }}</div>
+      </div>
+    </van-dialog>
   </div>
 </template>
 <style scoped lang="scss">
@@ -198,6 +242,17 @@ async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
   --van-floating-panel-border-radius: 0;
 }
 
+.scan-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  img {
+    padding: 10px 0;
+    object-fit: scale-down;
+  }
+}
+
 .has-link {
   display: flex;
   justify-content: center;

+ 10 - 8
src/request/model/scheme.model.ts

@@ -13,7 +13,7 @@ export function fromSchemeRequest(data: Data) {
             ...fragment(item),
             descriptions: item?.attrs?.map(fragment) ?? [],
             media: fromSchemeMedia(item.items ?? []),
-            goods: item?.buyUrl ? fromSchemeGoods(item) : void 0,
+            goods: fromSchemeGoods(item),
           };
         }) ?? [],
       };
@@ -73,15 +73,17 @@ function fromSchemeMedia(data: Data[]) {
 }
 
 export interface SchemeGoodsProps {
-  type: 'link' | string;
+  type: 'link' | 'miniprogram' | string;
   value: string;
   label?: string;
 }
 
-function fromSchemeGoods(data: Data): SchemeGoodsProps {
-  return {
-    type: { URL: 'link' }[<string>data.buyType] ?? data.buyType,
-    label: data.buyName ?? '去购买',
-    value: data.buyUrl,
-  }
+function fromSchemeGoods(data?: Data): SchemeGoodsProps | void {
+  const result = {
+    type: { URL: 'link' }[<string>data?.buyType] ?? data?.buyType,
+    label: data?.buyName ?? '去购买',
+    value: data?.buyUrl,
+  } satisfies SchemeGoodsProps;
+  if (result.type === 'miniprogram') { result.value = data.miniprogram; }
+  return result.value ? result : void 0;
 }