Переглянути джерело

Merge branch 'master-charge' of http://git.yujin.shuziyunyao.com/yujin/digital-medicine-front into master-charge

lisy 4 місяців тому
батько
коміт
780d40371f

+ 2 - 0
.env.development

@@ -42,3 +42,5 @@ VITE_APP_PACKAGE_SHARE_URL = 'http://dm.share.yujin.shuziyunyao.com/package'
 VITE_APP_SHARE_QR_CODE_URL = 'http://dm.share.yujin.shuziyunyao.com'
 
 VITE_H5_URL = 'https://t.yujin.shuziyunyao.com/'
+# pagetpl地址
+VITE_APP_PAGETPL_URL = 'https://dm.yujin.shuziyunyao.com/trainpage'

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "url": "https://gitee.com/JavaLionLi/plus-ui.git"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@element-plus/icons-vue": "2.3.1",
     "@gausszhou/vue3-drag-resize-rotate": "^3.0.2",
     "@highlightjs/vue-plugin": "2.1.0",

BIN
src/assets/images/bg_music_icon.png


BIN
src/assets/images/has_page_index_icon.png


BIN
src/assets/images/set_index_icon.png


+ 52 - 12
src/components/DragResizeRotate/DragResizeRotate.vue

@@ -1,6 +1,15 @@
 <template>
-    <VueDragResizeRotate :x="drapJson.x" :y="drapJson.y" :w="drapJson.w" :h="drapJson.h" :r="drapJson.r" :parent="true" @resizing="onResize" @resizestop="onResizeStop" @activated="onActivated" @deactivated="onDeactivated" @dragging="onDrag" @dragstop="onDragStop">
-        <div class="w-100% h-100% d-flex a-c j-c">点击事件区域</div>
+    <VueDragResizeRotate :x="drapJson.x" :y="drapJson.y" class-name-handle="my-handle-class-dd handle" class-name-active="my-active-class-dd" class-name="my-class-dd" :w="drapJson.w" :h="drapJson.h" :r="drapJson.r" :parent="true" @resizing="onResize" @resizestop="onResizeStop" @activated="onActivated" @deactivated="onDeactivated" @dragging="onDrag" @dragstop="onDragStop">
+        <slot>
+            <div class="w-100% h-100% d-flex a-c j-c p-rtv">
+                <span>点击事件区域</span>
+                <div class="f-s-18 c-danger delete-icon_box" @click.stop="deleteItem">
+                    <el-icon>
+                        <CircleCloseFilled />
+                    </el-icon>
+                </div>
+            </div>
+        </slot>
     </VueDragResizeRotate>
 </template>
 <script setup lang="ts" name="ptpl-edit-index">
@@ -19,14 +28,12 @@ const emit = defineEmits([
     'dragging',
     'dragstop',
     'resizing',
-    'resizestop'
+    'resizestop',
+    'delete'
 ]);
 const drapJson = ref({
-    x: 0,
-    y: 0,
-    w: 100,
-    h: 100,
-    r: 0,
+     x: 0, y: 0, w: 100, h: 100,
+     r:0,
     ...prop.modelValue
 });
 const onActivated = () => {
@@ -38,18 +45,51 @@ const onDeactivated = () => {
 const onDrag = (left: number, top: number) => {
     emit('dragging', left, top);
 };
-const onDragStop = (left: number, top: number) => {
-    emit('update:modelValue', { ...drapJson.value, x: left, y: top });
-    emit('dragstop', left, top);
+const onDragStop = (x: number, y: number) => {
+    emit('update:modelValue', { ...drapJson.value, x, y });
+    emit('dragstop', x, y);
 };
 const onResize = (x: number, y: number, width: number, height: number) => {
     emit('resizing', x, y, width, height);
 };
 const onResizeStop = (x: number, y: number, width: number, height: number) => {
-    emit('update:modelValue', { ...drapJson.value, x, y, w: width, h: height });
+    emit('update:modelValue', { ...drapJson.value, x, y, w: width, h: height  });
     emit('resizestop', x, y, width, height);
 };
+const deleteItem = () => {
+    emit('deactivated');
+    emit('delete')
+};
 watch(() => prop.modelValue, (newVal) => {
     drapJson.value = { ...drapJson.value, ...newVal };
 });
 </script>
+<style lang="scss" scoped>
+.delete-icon_box {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    width: 24px;
+    height: 24px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.my-active-class-dd {
+    border-color: #74FBE5;
+}
+
+.my-class-dd {
+    touch-action: none;
+    position: absolute;
+    box-sizing: border-box;
+    border: 1px dashed;
+    border-color: #74FBE5;
+}
+
+.my-handle-class-dd {
+    border: 1px solid #74FBE5;
+}
+</style>

+ 82 - 0
src/components/SelectWepArea/SelectWepArea.vue

@@ -0,0 +1,82 @@
+<template>
+    <el-select filterable remote v-model="keywords" reserve-keyword :placeholder="placeholder" :remote-method="remoteMethod" @change="searchKeywords" :loading="loading" clearable style="width: 440px">
+        <el-option v-for="item in options" :key="item.id" :label="item.district + item.address.toString()" :value="item.id" />
+    </el-select>
+</template>
+<script setup lang="ts">
+import { debounce } from 'lodash';
+import AMapLoader from '@amap/amap-jsapi-loader';
+const props = defineProps<{
+    modelValue: any;
+    placeholder?: string;
+}>();
+const emit = defineEmits<{
+    (e: 'update:modelValue', value: string): void;
+}>();
+import { httpRequests } from '@/utils/httpRequests';
+const keywords = ref<string>('');
+const options = ref<any[]>([]);
+const loading = ref<boolean>(false);
+const remoteMethod = debounce((keywords: string) => {
+    if (!keywords) {
+        return;
+    }
+    loading.value = true;
+    let autoComplete = new mapData.AMap.Autocomplete();
+    autoComplete.search(keywords, function (status: string, result: any) {
+        console.log(result.tips);
+        if (!result.tips?.length) {
+            options.value = [];
+            loading.value = false;
+            return;
+        }
+        const tips = result.tips?.filter((item: any) => item.id);
+        options.value = [...tips];
+        loading.value = false;
+    });
+}, 1000);
+const mapData: any = {
+    AMap: null,
+    map: null,
+    marker: null,
+    circle: null
+};
+const initMap = (positions: any[] = []) => {
+    window._AMapSecurityConfig = {
+        securityJsCode: '059c519d3546bc48566ecca0b38f22ae',
+    };
+    AMapLoader.load({
+        key: '26b919a68880ad60637f5cabd6c94a76', // 申请好的Web端开发者Key,首次调用 load 时必填
+        version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+        plugins: [
+            'AMap.PlaceSearch',
+            'AMap.AutoComplete'
+        ] // 需要使用的的插件列表,如比例尺'AMap.Scale'等
+    })
+        .then((AMap) => {
+            mapData.AMap = AMap;
+        })
+        .catch((e) => {
+            console.log(e);
+        });
+};
+const searchKeywords = (val: string) => {
+    console.log(val);
+    const item = options.value.find(i => i.id === val);
+    if (item) {
+        emit('update:modelValue', item);
+    } else {
+        emit('update:modelValue', '');
+    }
+};
+watch(() => props.modelValue, (val) => {
+    if (val && val.name) {
+        keywords.value = (val?.name || '') + (val?.district || '');
+    } else {
+        keywords.value = '';
+    }
+}, { immediate: true });
+onMounted(() => {
+    initMap();
+});
+</script>

+ 1 - 13
src/views/components/H5ModelLook.vue

@@ -1,16 +1,5 @@
 <template>
-    <vxe-modal
-        v-model="dialogVisible"
-        :title="title"
-        resize
-        :show-footer="false"
-        destroy-on-close
-        transfer
-        height="80vh"
-        mask-closable
-        @hide="close"
-        :width="width"
-    >
+    <vxe-modal v-model="dialogVisible" :title="title" resize :show-footer="false" destroy-on-close transfer height="80vh" mask-closable @hide="close" :width="width">
         <template #default>
             <iframe :src="src" class="iframe-wrapper"></iframe>
         </template>
@@ -36,7 +25,6 @@ const close = () => {
 watch(
     () => props.show,
     (val) => {
-        console.log(val);
         dialogVisible.value = val;
     },
     { immediate: true }

+ 3 - 1
src/views/training/models/index.ts

@@ -6,4 +6,6 @@ export { default as MeetingCustom } from './meeting-custom.vue'; // 查看签到
 export { default as registrationInfo } from './registration-info.vue'; 
 export { default as meetingCustomPreview } from './meeting-custom-preview.vue';
 export { default as MeetingTplH5 } from './meeting-tpl-h5.vue';
-export { default as MeetingTplList } from './meeting-tpl-list.vue';
+export { default as MeetingTplList } from './meeting-tpl-list.vue';
+export { default as MeetingTplEvents } from './meeting-tpl-events.vue';
+export { default as SelectMeetingTplPage } from './select-meeting-tpl-page.vue';

+ 134 - 0
src/views/training/models/meeting-tpl-events.vue

@@ -0,0 +1,134 @@
+<template>
+    <div class="pd-16">
+        <div class="f-s-16 c-333 f-w-6 mb-16">设置事件</div>
+        <el-form ref="enentFormRef" :model="form" label-position="left" :rules="rules" label-width="auto">
+            <el-form-item label="触发事件" prop="eventName">
+                <el-select v-model="form.eventName" @change="changeEventName" placeholder="请选择事件类型">
+                    <el-option v-for="item in page_event" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                </el-select>
+            </el-form-item>
+            <template v-if="form.eventName === 'callEvent'">
+                <el-form-item label="电话号码" prop="params.phone">
+                    <el-input v-model="form.params.phone" placeholder="请输入电话号码"></el-input>
+                </el-form-item>
+            </template>
+            <template v-if="form.eventName === 'viewEvent'">
+                <el-form-item label="文件上传" prop="params.file" label-position="top">
+                    <FileUpload v-model="form.params.file" format="object" :fileSize="100"></FileUpload>
+                </el-form-item>
+            </template>
+            <template v-if="form.eventName === 'gotoEvent'">
+                <el-form-item label="页面" prop="params.pageId" label-position="top">
+                    <el-button v-if="!form.params.pageId" type="primary" @click="showSelectMeeting = true">选择页面</el-button>
+                    <div v-if="form.params.pageId" class="pt-10">
+                        <div class="w-160 p-rtv">
+                            <div class="delete-icon-tpl c-s-p c-danger f-s-20" @click="form.params.pageId = null; selectPageInfo = null;">
+                                <el-icon><CircleCloseFilled /></el-icon>
+                            </div>
+                            <div class="h-180 bg-#ccc bg-img-item_view" :style="{ backgroundImage: 'url('+ selectPageInfo?.img +')' }"></div>
+                            <div class="f-s-14 pd2-10-0 btm-text">{{ selectPageInfo?.label }}</div>
+                        </div>
+                    </div>
+                </el-form-item>
+            </template>
+            <template v-if="form.eventName === 'navigateEvent'">
+                <el-form-item label="地址导航" prop="params.wepArea">
+                    <SelectWepArea v-model="form.params.wepArea" placeholder="选择地址"></SelectWepArea>
+                </el-form-item>
+            </template>
+            <template v-if="form.eventName === 'linkEvent'">
+                <el-form-item label="跳转链接" prop="params.linkType">
+                    <el-radio-group v-model="form.params.linkType">
+                        <el-radio label="内链">内部链接</el-radio>
+                        <el-radio label="外链">外链链接</el-radio>
+                    </el-radio-group>
+                </el-form-item>
+                <el-form-item label="链接地址" prop="params.url">
+                    <el-input v-model="form.params.url" placeholder="请输入链接地址"></el-input>
+                </el-form-item>
+            </template>
+            <el-form-item>
+                <div class="flex1 d-flex j-ed pt-40">
+                    <el-button @click="clearForm">清除</el-button>
+                    <el-button @click="save" type="primary">保存</el-button>
+                </div>
+            </el-form-item>
+        </el-form>
+    </div>
+    <SelectMeetingTplPage v-model:show="showSelectMeeting" :list="list" :pageId="form.params.pageId" @success="selectSuccess"></SelectMeetingTplPage>
+</template>
+<script setup lang="ts">
+import { SelectMeetingTplPage } from '.';
+const props = defineProps<{
+    modelValue: any;
+    dict: any;
+    list: any[];
+}>();
+const { page_event } = toRefs<any>(props.dict);
+const enentFormRef = ref<any>(null);
+const showSelectMeeting = ref(false);
+const emit = defineEmits<{
+    (e: 'update:modelValue', value: any): void;
+    (e: 'save', value: any): void;
+}>();
+const rules = ref<any>({
+    eventName: [{ required: true, message: '请选择触发事件', trigger: 'change' }],
+    'params.phone': [
+        { required: true, message: '请输入电话号码', trigger: 'blur' },
+        { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的电话号码', trigger: 'blur' }
+    ],
+    'params.file': [{ required: true, message: '请上传文件', trigger: 'change' }],
+    'params.pageId': [{ required: true, message: '请选择页面', trigger: 'change' }],
+    'params.wepArea': [{ required: true, message: '请选择地址', trigger: 'change' }],
+    'params.linkType': [{ required: true, message: '请选择链接类型', trigger: 'change' }],
+    'params.url': [
+        { required: true, message: '请输入跳转链接', trigger: 'blur' },
+        { type: 'url', message: '请输入正确的链接地址', trigger: 'blur' }
+    ]
+});
+const form = ref<any>(props.modelValue || {});
+const save = async () => {
+    await enentFormRef.value.validate();
+    const value = {  ...form.value, id: form.value?.id || new Date().getTime() + '_btn_drap', isSave: '1' };
+    emit('update:modelValue', value);
+    emit('save', value);
+};
+const selectPageInfo = computed<any>(() => {
+    if (!form.value.params || !form.value.params.pageId) return null;
+    return props.list.find((item) => item.id === form.value.params.pageId) || null;
+});
+const selectSuccess = (pageId: string) => {
+    form.value.params.pageId = pageId;
+};
+
+const changeEventName = (val: string) => {
+    form.value.params = {};
+};
+const clearForm = () => {
+    enentFormRef.value.resetFields();
+};
+watch(
+    () => props.modelValue,
+    (val) => {
+        form.value = val || {};
+    },
+    { deep: true }
+);
+</script>
+<style lang="scss" scoped>
+.bg-img-item_view {
+    background-size: cover;
+    background-position: center;
+    border: 1px solid transparent;
+}
+.delete-icon-tpl {
+    position: absolute;
+    top: -10px;
+    right: -10px;
+    width: 24px;
+    height: 24px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+</style>

+ 70 - 16
src/views/training/models/meeting-tpl-h5.vue

@@ -2,8 +2,13 @@
     <template v-if="form.img">
         <div class="flex1" style="overflow: auto;">
             <TelViewTem @selectArea="selectArea" :enableDraw="enableDraw" bgColor="#f7f7f7" :bgSrc="form.img">
+                <DragResizeRotate v-if="form.bgm" v-model="form.bgmRact" @activated="activatedBgm($event)" @deactivated="deactivatedBgm">
+                    <div class="w-100% h-100% d-flex j-c a-c">
+                        <img src="@/assets/images/bg_music_icon.png" />
+                    </div>
+                </DragResizeRotate>
                 <template v-for="(item, index) in form?.events" :key="index">
-                    <DragResizeRotate v-model="form.events[index]" @activated="activated" @deactivated="deactivated"></DragResizeRotate>
+                    <DragResizeRotate v-model="form.events[index]" @delete="deleteItemEvents($event, index, item)" @activated="activated($event, index)" @deactivated="deactivated"></DragResizeRotate>
                 </template>
             </TelViewTem>
         </div>
@@ -15,40 +20,89 @@
     </template>
 </template>
 <script setup lang="ts">
+import { httpRequests } from '@/utils/httpRequests';
 import { importFileGetUrls } from '@/utils/models';
-
+const emit = defineEmits<{
+    (e: 'update:modelValue', value: any): void,
+    (e: 'activated', value: any): void
+    (e: 'deactivated'): void
+    (e: 'deleteItemEvents', value: any): void,
+    (e: 'muisc', value: any): void
+}>();
 const props = defineProps<{
     modelValue: any
 }>();
-const form = ref<any>(props.modelValue || { img: '', events: []});
+const eventDefault = {
+    id: '',
+    eventName: '',
+    params: {
+        phone: '',
+        url: '',
+    },
+    x: 0,
+    y: 0,
+    w: 100,
+    h: 100,
+};
+const form = ref<any>(props.modelValue || { img: '', events: [] });
 const enableDraw = ref(true);
-const addInfo = () => {
-    form.value.events.push({ id: new Date().getTime() + '_btn_drap' });
-}
+
 const selectArea = (area: any) => {
     if (area.rect.w < 20 || area.rect.h < 20) {
         return;
     }
-    form.value.events.push({ id: new Date().getTime() + '_btn_drap', ...area.rect });
+    form.value.events.push({ ...eventDefault, id: new Date().getTime() + '_btn_drap', ...area.rect });
+}
+const activated = (event, index) => {
+    enableDraw.value = false;
+    emit('activated', form.value.events[index] || { id: '' });
 }
-const activated = () => {
+const activatedBgm = (event) => {
     enableDraw.value = false;
 }
 const deactivated = () => {
     enableDraw.value = true;
+    emit('deactivated');
+}
+const deactivatedBgm = async () => {
+    enableDraw.value = true;
+        const musicParams = {
+        id: form.value.id,
+        bgmRact: form.value.bgmRact || {
+            x: 100,
+            y: 100,
+            w: 50,
+            h: 50
+        },
+        bgm: form.value.bgm || null
+    };
+    const saveRes = await httpRequests.post('/dgtmedicine/trainpage/setBgm', musicParams);
+    if (saveRes && saveRes.code === 200) {
+        // 更新背景音乐事件
+        emit('muisc', form.value);
+    }
+    emit('deactivated');
+}
+const deleteItemEvents = (event: any, index: number, item: any) => {
+    form.value.events.splice(index, 1);
+    emit('deleteItemEvents', item);
+    emit('update:modelValue', form.value);
 }
 const clickInfo = () => {
     console.log(form.value);
 }
 const addImgBg = async () => {
-  const res: any[] = await importFileGetUrls(['png', 'jpg', 'jpeg'], false);
-  if (res.length) {
-     res.forEach(element => {
-        if (element.data?.url) {
-            form.value.img = element.data.url;
-        }
-     });
-  }
+    const res: any[] = await importFileGetUrls(['png', 'jpg', 'jpeg'], false);
+    if (res.length) {
+        res.forEach(element => {
+            if (element.data?.url) {
+                form.value.img = element.data.url;
+            }
+        });
+    }
 }
+watch(() => props.modelValue, (val) => {
+    form.value = val || { img: '', events: [] };
+}, { deep: true });
 </script>
 <style scoped lang="scss"></style>

+ 72 - 37
src/views/training/models/meeting-tpl-list.vue

@@ -1,19 +1,30 @@
 <template>
     <div>
         <template v-for="(item, index) in list" :key="index">
-            <div class="border-bottom pd-10 c-s-p item-hover" @click="clickItem(item)">
+            <div class="border-bottom pd-10 c-s-p item-hover" :class="{ 'checked': selectTplId === item.id }"  @click="clickItem(item)">
                 <div v-if="+item.homeFlag" class="f-s-14 f-w-6 c-333 mb-10">门户页面</div>
-                <div>
-                    <div class="w-140 h-180 bg-#ccc bg-img-item_view" :style="{ backgroundImage: 'url('+ item?.img +')' }"></div>
+                <div class="d-flex">
+                    <div class="w-140 h-180 bg-#ccc bg-img-item_view p-rtv" :style="{ backgroundImage: 'url('+ item?.img +')' }">
+                        <img v-if="+item?.homeFlag" class="has_page_index_icon" src="@/assets/images/has_page_index_icon.png" />
+                        <img @click.stop="clickSetIndex(item)" v-else class="set_index_icon" src="@/assets/images/set_index_icon.png" />
+                    </div>
+                    <div class="flex1 ov-hd d-flex flex-cln j-sb">
+                        <div class="pd-10">
+                            <el-icon v-if="item?.events?.some(item => item.eventName)"><Sunny /></el-icon>
+                        </div>
+                        <div class="f-s-18 c-danger u-s-n c-s-p pd2-0-10" @click.stop="$emit('delete', item)">
+                            <el-icon><DeleteFilled /></el-icon>
+                        </div>
+                    </div>
                 </div>
-                <div class="c-666 f-s-14 pd2-10-0 u-s-n c-s-p d-flex a-c">
-                    <span class="c-666 f-s-14 mr-6">{{ item?.label }}</span>
+                <div @click.stop="changeLabel(item)" class="c-666 f-s-14 pd2-10-0 u-s-n c-s-p d-flex a-c c-666 f-s-14 btm-text">
+                    <span class="mr-6">{{ item?.label }}</span>
                     <el-icon><EditPen /></el-icon>
                 </div>
             </div>
         </template>
         <div class="pd-16 d-flex a-c j-c">
-            <el-button @click.stop="addMeetingTpl" plain type="primary">
+            <el-button @click.stop="addMeetingTpls" plain type="primary">
                 <el-icon><Plus /></el-icon>
                 添加页面
             </el-button>
@@ -23,48 +34,39 @@
 <script setup lang="ts">
 import { httpRequests } from '@/utils/httpRequests';
 const emit = defineEmits<{
-    (e: 'selectItem', value: any): void
+    (e: 'selectItem', value: any): void,
+    (e: 'addMeetingTpls'): void,
+    (e: 'delete', value: any): void,
+    (e: 'changeLabel', value: any): void,
+    // 设置首页
+    (e: 'setIndex', value: any): void,
 }>();
 const props = defineProps<{
-    meetid: string
+    meetid: string,
+    list: any[]
 }>()
 const queryParams = ref<any>({
     pageNum: 1,
     pageSize: 10,
 });
-const loading = ref(false);
-const total = ref(0);
-const list = ref<any>([]);
-const itemsData = ref<any>([]);
-const getList = async () => {
-    loading.value = true;
-    const res: any = await httpRequests.get(`/dgtmedicine/trainpage/listByTrainId/${props.meetid}`,);
-    if (!res || res.code !== 200) return;
-    list.value = res.data;
-    total.value = res.total;
-    loading.value = false;
-    if (list.value.length) {
-        emit('selectItem', list.value[0] || { img: '', events: [] });
-        return;
-    }
+const addMeetingTpls = async () => {
+   emit('addMeetingTpls');
 };
-const handleQuery = () => {
-    queryParams.value.pageNum = 1;
-    getList();
-};
-const addMeetingTpl = async () => {
-    const res: any = await httpRequests.post(`/dgtmedicine/trainpage/add`, {
-        trainId: props.meetid,
-        label: '页面名称',
-    });
-    if (!res || res.code !== 200) return;
-}
+const selectTplId = ref<string>('');
 const clickItem = (item: any) => {
+    selectTplId.value = item.id;
     emit('selectItem', item);
 };
-onMounted(() => {
-    handleQuery();
-});
+// 更换页面名称
+const changeLabel = async (item: any) => {
+   emit('changeLabel', item);
+};
+const clickSetIndex = async (item: any) => {
+    if (+item.homeFlag) {
+        return;
+    }
+    emit('setIndex', item)
+};
 </script>
 <style scoped lang="scss">
 .item-hover:hover {
@@ -73,5 +75,38 @@ onMounted(() => {
 .bg-img-item_view {
     background-size: cover;
     background-position: center;
+    border: 1px solid transparent;
+}
+.item-hover {
+    cursor: pointer;
+    &:hover {
+        .bg-img-item_view {
+            border-color: var(--el-color-primary);
+        }
+        .btm-text {
+            color: var(--el-color-primary);
+        }
+    }
+    &.checked {
+        .bg-img-item_view {
+            border-color: var(--el-color-primary);
+        }
+        .btm-text {
+            font-weight: 600;
+            color: var(--el-color-primary);
+        }
+    }
+}
+.has_page_index_icon {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 40px;
+    height: 40px;
+}
+.set_index_icon {
+    position: absolute;
+    bottom: 10px;
+    right: 10px;
 }
 </style>

+ 102 - 0
src/views/training/models/select-meeting-tpl-page.vue

@@ -0,0 +1,102 @@
+<template>
+    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="width" :z-index="1002">
+        <div class="ov-hd">
+            <el-row :gutter="30">
+                <template v-for="(item, index) in list" :key="index">
+                    <el-col :span="4">
+                        <div class="pd-10 w-160 c-s-p item-hover mb-30" @click="clickItem(item)" :class="{ checked: checkedid === item.id }">
+                            <div class="h-180 bg-#ccc bg-img-item_view" :style="{ backgroundImage: 'url('+ item?.img +')' }"></div>
+                            <div class="f-s-14 pd2-10-0 btm-text">{{ item?.label }}</div>
+                        </div>
+                    </el-col>
+                </template>
+            </el-row>
+        </div>
+        <template #footer>
+            <div class="d-flex a-c j-ed">
+                <el-button @click="cancel">取消</el-button>
+                <el-button @click="save" type="primary">保存</el-button>
+            </div>
+        </template>
+    </vxe-modal>
+</template>
+
+<script setup name="lmmeeting-meeting-add" lang="ts">
+import { cloneDeep } from 'lodash';
+import { onMounted, reactive, ref, watch } from 'vue';
+import { useRouter } from 'vue-router';
+// 需要添加以下导入
+import { propTypes } from '@/utils/propTypes';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const props = defineProps({
+    show: propTypes.bool.def(false),
+    info: propTypes.array.def([]),
+    dict: propTypes.object.def({}),
+    width: propTypes.number.def(1100),
+    title: propTypes.string.def('选择跳转页面'),
+    list: propTypes.any.def([]),
+    pageId: propTypes.string.def('')
+});
+const dialogVisible = ref(false);
+const emit = defineEmits(['update:show', 'close', 'success', 'update:info']);
+const close = () => {
+    emit('update:show', false);
+    emit('close', false);
+};
+const checkedid = ref(props.pageId || '');
+const formRef = ref();
+const cancel = () => {
+    emit('update:show', false);
+    emit('close', false);
+};
+const save = () => {
+    console.log('----');
+    emit('success', checkedid.value);
+    emit('update:show', false);
+};
+const clickItem = (item: any) => {
+    checkedid.value = item.id || '';
+};
+onMounted(() => {});
+watch(
+    () => props.show,
+    (val) => {
+        dialogVisible.value = val;
+    },
+    { immediate: true }
+);
+watch(
+    () => props.pageId,
+    (val) => {
+        checkedid.value = val || '';
+    },
+    { immediate: true }
+);
+</script>
+<style scoped lang="scss">
+.bg-img-item_view {
+    background-size: cover;
+    background-position: center;
+    border: 1px solid transparent;
+}
+.item-hover {
+    cursor: pointer;
+    &:hover {
+        .bg-img-item_view {
+            border-color: var(--el-color-primary);
+        }
+        .btm-text {
+            color: var(--el-color-primary);
+        }
+    }
+    &.checked {
+        .bg-img-item_view {
+            border-color: var(--el-color-primary);
+        }
+        .btm-text {
+            font-weight: 600;
+            color: var(--el-color-primary);
+        }
+    }
+}
+</style>

+ 181 - 11
src/views/training/ptpl/edit/index.vue

@@ -5,38 +5,57 @@
                 <div class="flex1 ov-hd">
                     <span class="f-s-18 c-333 f-w-6">编辑会议宣传门户</span>
                     <el-button @click="router.go(-1)" text type="primary">
-                        <el-icon><Back /></el-icon>
+                        <el-icon>
+                            <Back />
+                        </el-icon>
                         返回上一级
                     </el-button>
                 </div>
                 <div class="d-flex a-c">
+                    <el-button @click="setBgMusic">设置背景音乐</el-button>
                     <el-button @click="router.go(-1)">取消</el-button>
                     <el-button @click="previewTpl" type="primary">预览</el-button>
-                    <el-button type="success">发布</el-button>
+                    <el-button @click="publishTpl" type="success">发布</el-button>
                 </div>
             </div>
             <div class="flex1 ov-hd d-flex">
                 <div class="bg-#f8f8f8 w-200 box-sizing-border over-auto">
-                    <MeetingTplList v-if="meetid" :meetid="meetid" @selectItem="selectItem"></MeetingTplList>
+                    <MeetingTplList v-if="meetid" :meetid="meetid" @selectItem="selectItem" :list="list"
+                        @setIndex="setIndexTpl" @addMeetingTpls="addMeetingTpls" @delete="deleteListTpl"
+                        @changeLabel="changeLabel"></MeetingTplList>
                 </div>
                 <div class="flex1 ov-hd d-flex flex-cln a-c">
-                    <div v-if="form.id" class="w-750 pd2-10-0 f-w-5">
-                        页面名称
-                        <el-icon><EditPen /></el-icon>
+                    <div v-if="form.id" @click="changeLabel(form)" class="w-750 pd2-10-0 f-w-5">
+                        {{ form?.label }}
+                        <el-icon>
+                            <EditPen />
+                        </el-icon>
                     </div>
-                    <MeetingTplH5 v-if="form.id" v-model="form"></MeetingTplH5>
+                    <MeetingTplH5 v-if="form.id" v-model="form" @activated="activated"
+                        @deleteItemEvents="deleteItemEvents" @muisc="setMuisc"></MeetingTplH5>
+                </div>
+                <div class="bg-#f8f8f8 w-240">
+                    <MeetingTplEvents v-if="curEvent" v-model="curEvent" :dict="dict" @save="saveEevent" :list="list">
+                    </MeetingTplEvents>
                 </div>
-                <div class="bg-#f8f8f8 w-240"></div>
             </div>
         </div>
     </div>
+    <H5ModelLook v-if="showPreviewTpl" v-model:show="showPreviewTpl" :src="previewTplStr" title="预览"></H5ModelLook>
 </template>
 <script setup lang="ts" name="ptpl-edit-index">
 import router from '@/router';
-import { MeetingTplH5, MeetingTplList } from '../../models';
+import { MeetingTplH5, MeetingTplList, MeetingTplEvents } from '../../models';
 import { useRoute } from 'vue-router';
 import { httpRequests } from '@/utils/httpRequests';
+import { importFileGetUrls } from '@/utils/models';
+import { H5ModelLook } from '@/views/components';
+
+const VITE_APP_PAGETPL_URL = ref(import.meta.env.VITE_APP_PAGETPL_URL || '');
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const dict = proxy?.useDict('page_event')
+const { page_event } = toRefs<any>(dict);
+const showPreviewTpl = ref(false);
 // 获取地址栏参数
 const meetid = ref<string>('');
 const form = ref<any>({});
@@ -44,13 +63,164 @@ onMounted(() => {
     const route = useRoute();
     meetid.value = (route.query.meetid as string) || '';
 });
+const curEvent = ref<any>(null);
+const activated = (item: any) => {
+    curEvent.value = item;
+};
 const selectItem = (item: any) => {
-    form.value = item;
+    form.value = { ...item };
+};
+const saveEevent = (item: any) => {
+    if (!form.value?.events || !form.value?.events.length) return;
+    const index = form.value.events.findIndex((it: any) => it.id === item.id);
+    if (index === -1) return;
+    form.value.events[index] = item;
+};
+const deleteItemEvents = (item: any) => {
+    if (curEvent.value?.id === item.id) {
+        curEvent.value = null;
+    }
 };
 // 预览
+const previewTplStr = ref<string>('');
 const previewTpl = async () => {
+    if (!form.value?.id) {
+        ElMessage.warning('请先选择一个页面进行预览');
+        return;
+    };
     const res: any = await httpRequests.post(`/dgtmedicine/trainpage/edit`, form.value);
     if (!res || res.code !== 200) return;
-    console.log(res);
+    previewTplStr.value = `${VITE_APP_PAGETPL_URL.value}?id=${form.value.id}`;
+    showPreviewTpl.value = true;
+};
+const list = ref<any>([]);
+const itemsData = ref<any>([]);
+const getList = async () => {
+    const res: any = await httpRequests.get(`/dgtmedicine/trainpage/listByTrainId/${meetid.value}`,);
+    if (!res || res.code !== 200) return;
+    list.value = res.data;
+};
+const addMeetingTpls = async () => {
+    const res: any[] = await importFileGetUrls(['png', 'jpg', 'jpeg'], true);
+    // 过滤掉code不为200的
+    if (!res || !res.length) return;
+    const datas = res.filter(item => item.code === 200).map(item => item.data);
+    const promises = datas.map(item => {
+        return httpRequests.post('/dgtmedicine/trainpage/add', {
+            trainId: meetid.value,
+            label: '页面名称',
+            img: item.url,
+            homeFlag: 0,
+            events: []
+        });
+    });
+    const results = await Promise.all(promises);
+    const successResults = results.filter(item => item && item.code === 200).map(item => item.data);
+    if (!successResults.length) return;
+    getList();
+}
+const deleteListTpl = async (item: any) => {
+    ElMessageBox({
+        title: '删除提示',
+        cancelButtonText: '我再看看',
+        confirmButtonText: '确认删除',
+        showCancelButton: true,
+        confirmButtonClass: 'el-button--danger',
+        message: h('p', null, [h('div', null, ``), h('div', null, [h('span', null, '删除后页面和触发事件将同步删除,无法撤回,请谨慎操作!')])]),
+        callback: async (action: string) => {
+            if (action === 'confirm') {
+                const res = await httpRequests.get(`/dgtmedicine/trainpage/remove/${item.id}`);
+                if (res) {
+                    ElMessage.success('删除成功');
+                    if (item.id === form.value.id) {
+                        form.value = {};
+                        curEvent.value = null;
+                    }
+                    getList();
+                }
+            }
+        }
+    });
+};
+const setIndexTpl = async (item) => {
+    ElMessageBox({
+        title: '提示',
+        cancelButtonText: '我再看看',
+        confirmButtonText: '确认设置',
+        showCancelButton: true,
+        message: h('p', null, [h('div', null, ``), h('div', null, [h('span', null, '是否设置该页面为会议宣传门户首页?')])]),
+        callback: async (action: string) => {
+            if (action === 'confirm') {
+                const res = await httpRequests.get(`/dgtmedicine/trainpage/setHomePage/${item.id}`);
+                if (res) {
+                    ElMessage.success('设置成功');
+                    getList();
+                }
+            }
+        }
+    });
+}
+const setMuisc = (item: any) => {
+    if (form.value.id === item.id) {
+        form.value = { ...form.value, ...item }
+    }
 };
+const changeLabel = async (item: any) => {
+    const { value: label } = await ElMessageBox.prompt('请输入页面名称', '修改页面名称', {
+        confirmButtonText: '保存',
+        cancelButtonText: '取消',
+        inputValue: item.label,
+        inputPattern: /^(?!\s*$).+/,
+        inputErrorMessage: '页面名称不能为空'
+    });
+    if (label !== undefined) {
+        const res = await httpRequests.post('/dgtmedicine/trainpage/edit', { ...item, label });
+        if (res && res.code === 200) {
+            ElMessage.success('修改成功');
+            getList();
+            if (item.id === form.value.id) {
+                form.value.label = label;
+            }
+        }
+    }
+};
+// 设置背景音乐
+const setBgMusic = async () => {
+    const res: any[] = await importFileGetUrls(['mp3'], false);
+    if (!res || !res.length) return;
+    const music = res.find(item => item.code === 200);
+    if (!music || !music.data || !music.data.url) return;
+    form.value.bgMusic = music.data.url;
+    const musicParams = {
+        id: form.value.id,
+        bgmRact: form.value.bgmRact || {
+            x: 100,
+            y: 100,
+            w: 50,
+            h: 50
+        },
+        bgm: music.data.url
+    };
+    const saveRes = await httpRequests.post('/dgtmedicine/trainpage/setBgm', musicParams);
+    if (saveRes && saveRes.code === 200) {
+        ElMessage.success('设置背景音乐成功');
+        getList();
+        if (form.value.id) {
+            form.value = { ...form.value, ...musicParams }
+        }
+    }
+};
+// 发布页面
+const publishTpl = async () => {
+    if (!form.value?.id) {
+        ElMessage.warning('请先选择一个页面进行发布');
+        return;
+    };
+    const res: any = await httpRequests.post(`/dgtmedicine/trainpage/edit`, form.value);
+    if (!res || res.code !== 200) return;
+    ElMessage.success('发布成功');
+};
+onMounted(() => {
+    getList();
+});
 </script>