Commit f71c587b authored by 罗超's avatar 罗超

新增资料卡片

parent ba91de8a
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"vue-router": "^4.0.13", "vue-router": "^4.0.13",
"vue-waterfall-plugin-next": "^2.4.3", "vue-waterfall-plugin-next": "^2.4.3",
"vue-waypoint": "^4.3.0", "vue-waypoint": "^4.3.0",
"vue3-draggable-resizable": "^1.6.5",
"vue3-leaderline": "^1.2.11", "vue3-leaderline": "^1.2.11",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
......
...@@ -96,6 +96,10 @@ page { ...@@ -96,6 +96,10 @@ page {
.light-shadow { .light-shadow {
box-shadow: 0px 0px 20px 0px rgba(76,87,125,0.2)!important; box-shadow: 0px 0px 20px 0px rgba(76,87,125,0.2)!important;
} }
.el-pagination.no-bg button,
.el-pagination.no-bg li{
background: transparent;
}
.page{ .page{
height: 100vh; height: 100vh;
display: flex; display: flex;
...@@ -752,6 +756,9 @@ page { ...@@ -752,6 +756,9 @@ page {
.el-menu.no-border{ .el-menu.no-border{
border:none !important; border:none !important;
} }
.no-border{
border:none !important;
}
.el-button:not(.is-link).none-border,.el-menu-item.none-border, .el-button:not(.is-link).none-border,.el-menu-item.none-border,
.el-button:not(.is-link).none-border:hover,.el-menu-item.none-border:hover, .el-button:not(.is-link).none-border:hover,.el-menu-item.none-border:hover,
.el-dropdown.none-border:hover,.el-dropdown-menu.none-border:hover{ .el-dropdown.none-border:hover,.el-dropdown-menu.none-border:hover{
...@@ -788,6 +795,9 @@ page { ...@@ -788,6 +795,9 @@ page {
.dark .el-tabs__item.is-active{ .dark .el-tabs__item.is-active{
color:#000 !important; color:#000 !important;
} }
.bg-dark{
background-color: #000;
}
.dark .el-tabs__item{ .dark .el-tabs__item{
color: grey !important; color: grey !important;
} }
...@@ -812,4 +822,7 @@ page { ...@@ -812,4 +822,7 @@ page {
} }
.columnCount5 { .columnCount5 {
column-count: 5; column-count: 5;
}
.top-tools-popover{
z-index: 99999!important;
} }
\ No newline at end of file
...@@ -153,6 +153,8 @@ import { ...@@ -153,6 +153,8 @@ import {
GridFour, GridFour,
WaterfallsH, WaterfallsH,
ListCheckbox, ListCheckbox,
DocAdd,
FileConversion
} from '@icon-park/vue-next' } from '@icon-park/vue-next'
export interface Icons { export interface Icons {
...@@ -160,6 +162,8 @@ export interface Icons { ...@@ -160,6 +162,8 @@ export interface Icons {
} }
export const icons: Icons = { export const icons: Icons = {
IconFileConversion: FileConversion,
IconDocAdd: DocAdd,
IconPlayOne: PlayOne, IconPlayOne: PlayOne,
IconFullScreenPlay: FullScreenPlay, IconFullScreenPlay: FullScreenPlay,
IconLock: Lock, IconLock: Lock,
......
...@@ -4,6 +4,10 @@ class SpiderService{ ...@@ -4,6 +4,10 @@ class SpiderService{
static async GetThirdPartyResourceAsync(params : any):Promise<HttpResponse>{ static async GetThirdPartyResourceAsync(params : any):Promise<HttpResponse>{
return Api.Post("mongoscenic_GetMongoScenicPage",params) return Api.Post("mongoscenic_GetMongoScenicPage",params)
} }
static async GetSourcesAsync(params : any):Promise<HttpResponse>{
return Api.Post("hotel_post_GetDmcSourcePage",params)
}
} }
export default SpiderService export default SpiderService
\ No newline at end of file
...@@ -27,6 +27,44 @@ export const copyText = (text: string) => { ...@@ -27,6 +27,44 @@ export const copyText = (text: string) => {
}) })
} }
export const replaceText = (text:string,content:string,method:'R'|'I') =>{
const fakeElement = document.createElement('div')
fakeElement.innerHTML = text
const targetElement = findMiniChildNode(fakeElement)
if(targetElement){
const parentElement = targetElement.parentElement
if (parentElement) {
parentElement.innerText = (method=='I'?parentElement.innerText:'')+content
}
}
return fakeElement.innerHTML
}
const convertToHTMLElement = (node: ChildNode): HTMLElement | null => {
if (node instanceof HTMLElement) {
return node;
} else {
return null; // 或者根据需要处理非 HTMLElement 的节点
}
}
const findMiniChildNode = (element: Element | ChildNode): ChildNode | null => {
if (element.hasChildNodes()) {
const childs = element.childNodes;
for (let i = 0; i < childs.length; i++) {
const foundNode = findMiniChildNode(childs.item(i));
if (foundNode) {
return foundNode;
}
}
} else {
return element;
}
return null;
}
// 读取剪贴板 // 读取剪贴板
export const readClipboard = (): Promise<string> => { export const readClipboard = (): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
......
...@@ -13,6 +13,98 @@ export const fillDigit = (digit: number, len: number) => { ...@@ -13,6 +13,98 @@ export const fillDigit = (digit: number, len: number) => {
return padStart('' + digit, len, '0') return padStart('' + digit, len, '0')
} }
export const calculateCardPosition = (element:Element, cardWidth:number, cardHeight:number) => {
const rect = element.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const positions = {
top: {
top: rect.top - cardHeight,
left: rect.left + rect.width / 2 - cardWidth / 2,
fits: rect.top - cardHeight >= 0,
},
bottom: {
top: rect.bottom,
left: rect.left + rect.width / 2 - cardWidth / 2,
fits: rect.bottom + cardHeight <= viewportHeight,
},
left: {
top: rect.top + rect.height / 2 - cardHeight / 2,
left: rect.left - cardWidth,
fits: rect.left - cardWidth >= 0,
},
right: {
top: rect.top + rect.height / 2 - cardHeight / 2,
left: rect.right,
fits: rect.right + cardWidth <= viewportWidth,
},
};
// 遍历 positions 对象,找到第一个 fits 为 true 的位置
for (const [direction, position] of Object.entries(positions)) {
if (position.fits) {
return {
top: Math.max(0, Math.min(position.top, viewportHeight - cardHeight)),
left: Math.max(0, Math.min(position.left, viewportWidth - cardWidth)),
direction,
};
}
}
return {
top: Math.max(0, Math.min(positions.right.top, viewportHeight - cardHeight)),
left: Math.max(0, Math.min(positions.right.left, viewportWidth - cardWidth))
};
};
export const isElementVisibleInParent = (element:Element, parent:Element) => {
const elementRect = element.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
// 元素相对于父级容器的可见区域
const visibleRect = {
top: Math.max(elementRect.top, parentRect.top),
bottom: Math.min(elementRect.bottom, parentRect.bottom),
left: Math.max(elementRect.left, parentRect.left),
right: Math.min(elementRect.right, parentRect.right),
};
// 判断是否可见
const isVisible =
visibleRect.top < visibleRect.bottom &&
visibleRect.left < visibleRect.right;
// 计算可见的宽度和高度
const visibleWidth = isVisible ? visibleRect.right - visibleRect.left : 0;
const visibleHeight = isVisible ? visibleRect.bottom - visibleRect.top : 0;
return {
isVisible,
visibleWidth,
visibleHeight,
};
}
/**
* 查找元素的指定父级
* @param element
* @param className
* @returns
*/
export const findParentWithClass = (element: HTMLElement, className: string): HTMLElement | null => {
let currentElement: HTMLElement | null = element;
while (currentElement) {
if (currentElement.classList.contains(className)) {
return currentElement;
}
currentElement = currentElement.parentElement;
}
return null;
}
export const openNewBlank = (path:string) => { export const openNewBlank = (path:string) => {
if(path!=''){ if(path!=''){
const url = path.includes('http://') || path.includes('https://') ? path : `${window.location.origin}${path}` const url = path.includes('http://') || path.includes('https://') ? path : `${window.location.origin}${path}`
......
...@@ -40,6 +40,12 @@ export const ResolveText = (item: any, index: number,offsetLeft:number,offsetTop ...@@ -40,6 +40,12 @@ export const ResolveText = (item: any, index: number,offsetLeft:number,offsetTop
let color = `rgba(${colors[0][0]},${colors[0][1]},${colors[0][2]},${(parseFloat(colors[0][3]) / 255.0).toFixed(2)})` let color = `rgba(${colors[0][0]},${colors[0][1]},${colors[0][2]},${(parseFloat(colors[0][3]) / 255.0).toFixed(2)})`
// if(value.indexOf('罗威纳追海豚6日')!=-1){
// console.log("StyleSheetData",StyleSheetData)
// console.log("StyleSheet",StyleSheet)
// console.log("f",f)
// }
let styleArray:string[] = [] let styleArray:string[] = []
const lineCount = value.split('\r').length const lineCount = value.split('\r').length
for (let i = 0; i < lineCount; i++) { for (let i = 0; i < lineCount; i++) {
...@@ -116,7 +122,7 @@ export const ResolveText = (item: any, index: number,offsetLeft:number,offsetTop ...@@ -116,7 +122,7 @@ export const ResolveText = (item: any, index: number,offsetLeft:number,offsetTop
domLeft -= 10 //+((leading-1)*fontSize/2) domLeft -= 10 //+((leading-1)*fontSize/2)
let fontName = names.filter((x:any)=>x.indexOf('Adobe')==-1) let fontName = names.filter((x:any)=>x.indexOf('Adobe')==-1)
console.log(fontName) //console.log(fontName)
if(fontName && fontName.length>0){ if(fontName && fontName.length>0){
for (let i = 0; i < fontName.length; i++) { for (let i = 0; i < fontName.length; i++) {
let x = fontName[i]; let x = fontName[i];
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<div <div
class="canvas" class="canvas"
ref="canvasRef" ref="canvasRef"
id="canvasDom"
@wheel="$event => handleMousewheelCanvas($event)" @wheel="$event => handleMousewheelCanvas($event)"
@mousedown="$event => handleClickBlankArea($event)" @mousedown="$event => handleClickBlankArea($event)"
@dblclick="$event => handleDblClick($event)" @dblclick="$event => handleDblClick($event)"
...@@ -98,6 +99,7 @@ ...@@ -98,6 +99,7 @@
<LinkDialog @close="linkDialogVisible = false" /> <LinkDialog @close="linkDialogVisible = false" />
</Modal> </Modal>
</div> </div>
<SourceCard scroll-id="canvas-view-wrap"></SourceCard>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
...@@ -144,6 +146,8 @@ import MultiSelectOperate from './Operate/MultiSelectOperate.vue' ...@@ -144,6 +146,8 @@ import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
import Operate from './Operate/index.vue' import Operate from './Operate/index.vue'
import LinkDialog from './LinkDialog.vue' import LinkDialog from './LinkDialog.vue'
import Modal from '@/components/Modal.vue' import Modal from '@/components/Modal.vue'
import SourceCard from '@/views/components/source/index.vue'
import { findParentWithClass } from '@/utils/common'
const mainStore = useMainStore() const mainStore = useMainStore()
const { const {
...@@ -364,6 +368,15 @@ const contextmenus = (): ContextmenuItem[] => { ...@@ -364,6 +368,15 @@ const contextmenus = (): ContextmenuItem[] => {
] ]
} }
// const handleMouseUp = (event:any)=>{
// const selectionText = window.getSelection()?.toString()
// if(selectionText){
// const parentNode = findParentWithClass(event.target,"element-content")
// if(parentNode){
// console.log(parentNode.style.writingMode)
// }
// }
// }
provide(injectKeySlideScale, canvasScale) provide(injectKeySlideScale, canvasScale)
</script> </script>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<Thumbnails class="layout-content-left" /> <Thumbnails class="layout-content-left" />
<div class="layout-content-center" style="overflow: hidden;"> <div class="layout-content-center" style="overflow: hidden;">
<CanvasTool class="center-top" /> <CanvasTool class="center-top" />
<div v-if="!reloadLoading" style="height: calc(100% - 40px);overflow: scroll;background: #f9f9f9;"> <div v-if="!reloadLoading" id="canvas-view-wrap" style="height: calc(100% - 40px);overflow: scroll;background: #f9f9f9;">
<!-- <Canvas class="center-body" :style="{ height: `calc(100% - 40px)`}" /> --> <!-- <Canvas class="center-body" :style="{ height: `calc(100% - 40px)`}" /> -->
<Canvas class="center-body" :style="{ height: '100%'}" /> <Canvas class="center-body" :style="{ height: '100%'}" />
</div> </div>
......
...@@ -127,7 +127,7 @@ const handleSelectElement = (e: MouseEvent | TouchEvent) => { ...@@ -127,7 +127,7 @@ const handleSelectElement = (e: MouseEvent | TouchEvent) => {
return return
} }
}else{ } else {
e.stopPropagation() e.stopPropagation()
props.selectElement(e, props.elementInfo) props.selectElement(e, props.elementInfo)
} }
......
...@@ -234,6 +234,7 @@ const execCommand = ({ target, action }: RichTextCommand) => { ...@@ -234,6 +234,7 @@ const execCommand = ({ target, action }: RichTextCommand) => {
// 鼠标抬起时,执行格式刷命令 // 鼠标抬起时,执行格式刷命令
const handleMouseup = () => { const handleMouseup = () => {
//console.log('handleMouseup',window.getSelection()?.toString())
if (!textFormatPainter.value) return if (!textFormatPainter.value) return
const { keep, ...newProps } = textFormatPainter.value const { keep, ...newProps } = textFormatPainter.value
...@@ -248,6 +249,10 @@ const handleMouseup = () => { ...@@ -248,6 +249,10 @@ const handleMouseup = () => {
if (!keep) mainStore.setTextFormatPainter(null) if (!keep) mainStore.setTextFormatPainter(null)
} }
const handleSelectionChange = ()=>{
//console.log('handleSelectionChange',window.getSelection()?.toString())
}
// Prosemirror编辑器的初始化和卸载 // Prosemirror编辑器的初始化和卸载
onMounted(() => { onMounted(() => {
editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, { editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, {
...@@ -257,6 +262,7 @@ onMounted(() => { ...@@ -257,6 +262,7 @@ onMounted(() => {
keydown: handleKeydown, keydown: handleKeydown,
click: handleClick, click: handleClick,
mouseup: handleMouseup, mouseup: handleMouseup,
selectionchange: handleSelectionChange
}, },
editable: () => props.editable, editable: () => props.editable,
}) })
......
...@@ -159,6 +159,7 @@ const text = computed<ShapeText>(() => { ...@@ -159,6 +159,7 @@ const text = computed<ShapeText>(() => {
const updateText = (content: string) => { const updateText = (content: string) => {
const _text = { ...text.value, content } const _text = { ...text.value, content }
console.log(_text)
slidesStore.updateElement({ slidesStore.updateElement({
id: props.elementInfo.id, id: props.elementInfo.id,
props: { text: _text }, props: { text: _text },
......
<template>
<div class="search-card-detail">
<div class="row items-center">
<el-button link round size="small" @click="handleBack">
<IconLeft :size="18"></IconLeft>
</el-button>
<div class="sub-title1 col q-mx-md">POI详情</div>
<el-button link round size="small" @click="handleClose">
<IconClose size="14px"></IconClose>
</el-button>
</div>
<el-scrollbar max-height="50vh">
<div class="item-poi-img q-mt-md" v-if="data.ImgArray && data.ImgArray.length > 0">
<div class="container row">
<el-image class="col full-height" :preview-teleported="true" :src="data.ImgArray[0]"
:initial-index="0" :preview-src-list="data.ImgArray" fit="cover" />
<div style="width: 60%;" class="q-ml-sm row" v-if="data.ImgArray.length > 1">
<div class="col column">
<el-image class="col full-width" :preview-teleported="true" :src="data.ImgArray[1]"
:initial-index="1" :preview-src-list="data.ImgArray" fit="cover" />
<el-image v-if="data.ImgArray.length > 2" :preview-teleported="true"
class="col full-width q-mt-sm" :src="data.ImgArray[2]" :initial-index="2"
:preview-src-list="data.ImgArray" fit="cover" />
</div>
<div class="col column q-ml-sm" v-if="data.ImgArray.length > 3">
<el-image class="col full-width" :preview-teleported="true" :src="data.ImgArray[3]"
:initial-index="3" :preview-src-list="data.ImgArray" fit="cover" />
<div class="col full-width q-mt-sm last-img" v-if="data.ImgArray.length > 4">
<el-image class="col full-height" v-if="data.ImgArray.length > 4"
:preview-teleported="true" :src="data.ImgArray[4]" :initial-index="4"
:preview-src-list="data.ImgArray" fit="cover" />
<div class="content column items-center flex-center text-white"
style="text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);"
v-if="data.ImgArray.length > 5">
<div>+ {{ data.ImgArray.length - 5 }}</div>
<div class="q-mt-sm text-small">点击查看</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="q-mt-md">
<text-opera>{{ data.Name }}</text-opera>
<div class="q-mt-md row items-center">
<div class="text-small text-info col">地址</div>
<el-tag type="success" size="small" class="text-bold">{{ getTypeName(data.Platform, 0)
}}</el-tag>
<el-tag type="info" size="small" class="text-bold q-ml-md">{{ getTypeName(data.Type, 1)
}}</el-tag>
</div>
<text-opera><span class="text-small">{{ data.CountryName }}·{{ data.CityName }} {{ data.Address
}}</span></text-opera>
<template v-if="data.Tel && data.Tel != ''">
<div class="text-small text-info col q-mt-md">联系电话</div>
<text-opera><span class="text-small q-mt-sm">{{ data.Tel }}</span></text-opera>
</template>
<template v-if="data.PlayTimeHour && data.PlayTimeHour != ''">
<div class="text-small text-info col q-mt-md">推荐时长</div>
<text-opera><span class="text-small q-mt-sm">{{ data.PlayTimeHour }}</span></text-opera>
</template>
<div class="text-small text-info col q-mt-md">详细介绍</div>
<text-opera><span class="text-small q-mt-sm">{{ data.Feature }}</span></text-opera>
<template v-if="data.About && data.About != ''">
<div class="text-small text-info col q-mt-md">温馨提示</div>
<text-opera><span class="text-small q-mt-sm">{{ data.About }}</span></text-opera>
</template>
<template v-if="data.BookingInfo && data.BookingInfo != ''">
<div class="text-small text-info col q-mt-md">预定须知</div>
<text-opera><span class="text-small q-mt-sm">{{ data.BookingInfo }}</span></text-opera>
</template>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue"
import TextOpera from './TextOpera.vue'
const emit = defineEmits<{
(event: 'close'): void
(event: 'refound'): void
}>()
const props = defineProps({
itemInfo: {
type: Object,
required: true,
},
})
const data = ref<any>(props.itemInfo)
const platforms = ref<{ id: number, name: string }[]>([
{ id: 2, name: "ERP" },
{ id: 1, name: "第三方" }
])
const allTypes = ref<{ id: number, name: string }[]>([
{ id: 0, name: "不限类型" },
{ id: 1, name: "酒店" },
{ id: 2, name: "餐厅" },
{ id: 3, name: "景点" }
])
const getTypeName = (t: number, st: 0 | 1) => {
if (st == 1) {
const p = allTypes.value.find(x => x.id == t)
return (p?.name ?? "").replace('不限', "")
} else {
const p = platforms.value.find(x => x.id == t)
return (p?.name ?? "").replace('不限', "")
}
}
const handleClose = () => emit('close')
const handleBack = () => emit('refound')
watch(props.itemInfo, (val: any) => {
data.value = props.itemInfo
})
</script>
<style>
.search-card-detail .item-poi-img {
position: relative;
width: 100%;
height: 0;
padding-top: 56.25%;
}
.search-card-detail .item-poi-img .container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.search-card-detail .item-poi-img .container img {
border-radius: 8px;
cursor: pointer;
}
.search-card-detail .item-poi-img .container .last-img {
position: relative;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
}
.search-card-detail .item-poi-img .container .last-img .content {
background: rgba(0, 0, 0, .2);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 2;
pointer-events: none;
}
</style>
\ No newline at end of file
<template>
<div>
<div class="row items-center">
<img src="../../../assets/img/resource.png" style="width: 30px; height: 30px" />
<div class="sub-title1 col q-ml-md">搜索</div>
<el-button link round size="small" @click="handleClose">
<IconClose size="14px"></IconClose>
</el-button>
</div>
<div class="q-mt-md" style="user-drag: none;">
<div class="row items-center tools-bar">
<el-dropdown style="margin-left: -1px;" trigger="click">
<el-button type="default" class="ppt-button text-white"
style="background: #000;border-radius: 0;padding: 8px;width:81px;">
<div class="row items-center">
<span class="col" style="margin-top: 2px; text-align: left;">{{ currentPlatform }}</span>
<IconDown class="q-ml-sm" :size="16" />
</div>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(x, i) in platforms" @click="handleChangeTypeOrPlatform(x.id, 0)"
:key="i">{{ x.name }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown style="margin-left: -1px;" trigger="click">
<el-button type="default" class="ppt-button text-white"
style="background: #000;border-radius: 0;padding: 8px;width:81px;">
<div class="row items-center">
<span class="col" style="margin-top: 2px; text-align: left;">{{ currentMatchType }}</span>
<IconDown class="q-ml-sm" :size="16" />
</div>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(x, i) in allTypes" @click="handleChangeTypeOrPlatform(x.id, 1)"
:key="i">{{ x.name }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<input type="text" style="margin-left: -1px;outline: none;" @keyup.enter="handleSearch" v-model="searchParmeters.keywords"
maxlength="12" placeholder="关键字城市/酒店/景点/餐厅..." class="col full-height no-border q-px-md" />
</div>
<el-row :gutter="12" class="q-mt-lg" v-if="!loading">
<el-col :span="12" v-for="(x, i) in data" :key="i">
<div class="item-poi" @click="setSelectedPoiItem(x)" :style="{ 'background-image': `url('${x.PicPath.split('?')[0]}')` }">
<div class="container column">
<div class="row items-center">
<el-tag type="success" size="small" class="text-bold">{{ getTypeName(x.Platform, 0)
}}</el-tag>
<el-tag type="info" size="small" class="text-bold q-ml-md">{{ getTypeName(x.Type, 1)
}}</el-tag>
</div>
<div class="col"></div>
<el-text class="text-white full-width"
style="text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5); font-size: 12px;" line-clamp="1">
{{ x.Name }}
</el-text>
</div>
</div>
</el-col>
</el-row>
<div style="height: 323px;" v-loading="loading" v-if="loading"></div>
<div class="row">
<div class="col"></div>
<el-pagination layout="prev, pager, next" class="no-bg " hide-on-single-page
:page-size="searchParmeters.pageSize" @current-change="handleSearch"
v-model:current-page="searchParmeters.pageIndex" :total="total" small />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref } from "vue"
import { ApiResult } from "@/configs/axios";
import SpiderService from "@/services/SpiderService";
const emit = defineEmits<{
(event: 'close'): void,
(event: 'change',payload: any): void
}>()
const loading = ref(false)
const data = ref<any[]>()
const total = ref(0)
const searchParmeters = reactive({
keywords: '',
platform: 2,
type: 0,
pageIndex: 1,
pageSize: 6
})
const platforms = ref<{ id: number, name: string }[]>([
{ id: 2, name: "ERP" },
{ id: 1, name: "第三方" }
])
const allTypes = ref<{ id: number, name: string }[]>([
{ id: 0, name: "不限类型" },
{ id: 1, name: "酒店" },
{ id: 2, name: "餐厅" },
{ id: 3, name: "景点" }
])
const currentPlatform = computed(() => {
return getTypeName(searchParmeters.platform, 0)
})
const currentMatchType = computed(() => {
return getTypeName(searchParmeters.type, 1)
})
const getTypeName = (t: number, st: 0 | 1) => {
if (st == 1) {
const p = allTypes.value.find(x => x.id == t)
return (p?.name ?? "").replace('不限', "")
} else {
const p = platforms.value.find(x => x.id == t)
return (p?.name ?? "").replace('不限', "")
}
}
const handleChangeTypeOrPlatform = (id: number, t: 0 | 1) => {
if (t == 0) searchParmeters.platform = id
else searchParmeters.type = id
searchParmeters.pageIndex = 1
handleSearch()
}
const handleSearch = async () => {
console.log('handleSearch',searchParmeters.keywords)
if (loading.value) return
loading.value = true
total.value = 0
data.value = []
const response = await SpiderService.GetSourcesAsync(searchParmeters)
if (response.data.resultCode == ApiResult.SUCCESS) {
data.value = response.data.data.pageData
total.value = response.data.data.count
searchParmeters.pageIndex = response.data.data.pageIndex
}
loading.value = false
}
const handleClose = () => emit('close')
const setSelectedPoiItem = (data:any)=> emit('change', data)
handleSearch()
</script>
<style>
.search-card-box .tools-bar {
border-radius: 8px;
height: 32px;
overflow: hidden;
border: 1px solid #000;
}
.search-card-box .item-poi {
position: relative;
width: 100%;
height: 0;
padding-top: 56.25%;
background-size: cover;
background-position: center;
background-color: #000;
border-radius: 12px;
overflow: hidden;
margin-bottom: 12px;
cursor: pointer;
}
.search-card-box .item-poi:hover {
box-shadow: 0px 0px 20px 0px rgba(76, 87, 125, 0.2) !important;
}
.search-card-box .item-poi .container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
padding: 8px;
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.5));
}
</style>
\ No newline at end of file
<template>
<el-popover placement="top" trigger="hover" popper-style="min-width:unset;padding:0;width:auto;">
<template #reference>
<div ref="operaTextRef">
<slot></slot>
</div>
</template>
<div class="row items-center" style="padding:4px">
<div class="text-opera-item" @click="handleCopyText">
<el-tooltip content="复制" placement="top">
<IconCopy :size="18"></IconCopy>
</el-tooltip>
</div>
<div class="text-opera-item" @click="handleInsert">
<el-tooltip content="添加到文本" placement="top">
<IconDocAdd :size="18"></IconDocAdd>
</el-tooltip>
</div>
<div class="text-opera-item" @click="handleReplace">
<el-tooltip content="替换文本" placement="top">
<IconFileConversion :size="18"></IconFileConversion>
</el-tooltip>
</div>
</div>
</el-popover>
</template>
<script lang="ts" setup>
import { useMainStore, useSlidesStore } from "@/store";
import { copyText, replaceText } from "@/utils/clipboard";
import { ElMessage } from "element-plus";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
const mainStore = useMainStore();
const { handleElement } = storeToRefs(mainStore);
const slidesStore = useSlidesStore()
const { addHistorySnapshot } = useHistorySnapshot()
const operaTextRef = ref<any>()
const handleCopyText= () => {
const text = operaTextRef.value.innerText
copyText(text).then(()=>{
ElMessage({ message: '复制成功', type: 'success' })
})
}
const handleReplace=()=>{
if(handleElement.value && 'content' in handleElement.value){
const text = operaTextRef.value.innerText
const newContent = replaceText(handleElement.value!.content,text,'R')
updateElement(newContent)
}
ElMessage({ message: '操作成功', type: 'success' })
}
const handleInsert=()=>{
if(handleElement.value && 'content' in handleElement.value){
const text = operaTextRef.value.innerText
const newContent = replaceText(handleElement.value!.content,text,'I')
updateElement(newContent)
}
ElMessage({ message: '操作成功', type: 'success' })
}
const updateElement = (content:string)=>{
const update:any = {
id:handleElement.value?.id,
props:{content}
}
slidesStore.updateElement(update)
addHistorySnapshot()
}
</script>
<style>
.text-opera-item{
padding:5px;
border-radius: 5px;
cursor: pointer;
color: #000;
line-height: 1;
}
.text-opera-item:hover{
background: #000;
color: #FFF;
}
</style>
\ No newline at end of file
<template>
<div class="semi-box row" :class="{ vertical: isVertical }" v-show="showSemi && visibleInView && showSearchStatus==0"
:style="semiHorStyle">
<div class="items items-center" :class="{ column: isVertical, row: !isVertical }" @click="handleShowSearch">
<img src="../../../assets/img/resource.png" style="width: 20px; height: 20px" />
<span class="text-small q-ml-sm">系统资料</span>
</div>
</div>
<Vue3DraggableResizable v-model:x="localtion.x" v-model:y="localtion.y" :draggable="true" :resizable="false"
v-if="showSearchStatus!=0" class="search-card-box" :class="{ vertical: isVertical }">
<source-list v-show="showSearchStatus==1" @close="()=>showSearchStatus=0" @change="handleShowDetail"></source-list>
<source-detail v-if="showSearchStatus==2" :item-info="recentPoi" @close="()=>showSearchStatus=0" @refound="()=>showSearchStatus=1"></source-detail>
</Vue3DraggableResizable>
</template>
<script lang="ts" setup>
import { useMainStore } from "@/store";
import { calculateCardPosition, isElementVisibleInParent } from "@/utils/common";
import { storeToRefs } from "pinia";
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import SourceList from './SourceList.vue'
import SourceDetail from './SourceDetail.vue'
import Vue3DraggableResizable from "vue3-draggable-resizable";
const mainStore = useMainStore();
const { handleElementId, handleElement, canvasScale } = storeToRefs(mainStore);
const semiHorStyle = ref<{ left: string; top: string }>({
left: "",
top: "",
});
const localtion = ref<{ x: number, y: number }>({
x: 0,
y: 0
})
const showSemi = ref(false);
const showSearchStatus = ref(0);
const showDetail = ref(true)
const visibleInView = ref(true);
const recentElement = ref<Element | null>();
const isVertical = ref(false);
const recentPoi = ref<any>()
let resizeObserver: ResizeObserver | null = null;
let mutationObserver: MutationObserver | null = null;
const props = withDefaults(defineProps<{ scrollId: string }>(), {
scrollId: "",
});
//#region 监听大小变化
const handleObserver = () => {
handleDisObserver();
resizeObserver = new ResizeObserver(() => {
const rect = recentElement.value!.getBoundingClientRect();
resetPosition(rect);
});
resizeObserver.observe(recentElement.value!);
};
const handleDisObserver = () => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
};
//#endregion
//#region 监听属性变化
const handleMutationObserver = () => {
handleDisMutationObserver();
mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "style"
) {
const rect = recentElement.value!.getBoundingClientRect();
resetPosition(rect);
}
});
});
mutationObserver.observe(recentElement.value!, {
attributes: true,
attributeFilter: ["style"],
});
};
const handleDisMutationObserver = () => {
if (mutationObserver) {
mutationObserver.disconnect();
mutationObserver = null;
}
};
//#endregion
const handleShowSearch = () => {
showSearchStatus.value = 1;
};
const resetPosition = (rect: DOMRect, w: number = 0, h: number = 0) => {
if (!isVertical.value) {
w = w == 0 ? rect.width / 2 : rect.width - w + w / 2;
h = (h == 0 ? rect.height : h) + 10;
} else {
w = (w == 0 ? rect.width : w) + 10;
h = h == 0 ? rect.height / 2 : rect.height - h + h / 2;
}
semiHorStyle.value.left = `${rect.left + w}px`;
semiHorStyle.value.top = `${rect.top + h}px`;
calculateCardLocation()
//localtion.value = { x: rect.left + w, y: rect.top + h }
};
const handleChangeSelect = () => {
showSemi.value = false;
showSearchStatus.value = 0
visibleInView.value = false;
recentElement.value = null;
handleDisObserver();
handleDisMutationObserver();
if (handleElement.value?.type == "text") {
const targetDom = document.querySelector(
`#editable-element-${handleElementId.value} .editable-element-text`
);
const rect = targetDom?.getBoundingClientRect();
if (rect) {
recentElement.value = targetDom;
resetPosition(rect);
handleObserver();
handleMutationObserver();
showSemi.value = true;
visibleInView.value = true;
}
isVertical.value =
("vertical" in handleElement.value && handleElement.value.vertical) ??
false;
}
};
const handleChangeParentScale = () => {
if (showSemi && recentElement.value) {
const parentElement = document.querySelector(`#${props.scrollId}`);
const { isVisible, visibleHeight, visibleWidth } = isElementVisibleInParent(
recentElement.value,
parentElement!
);
visibleInView.value = isVisible;
const rect = recentElement.value.getBoundingClientRect();
resetPosition(rect, visibleWidth, visibleHeight);
}
};
const handListenterScroll = () => {
if (props.scrollId != "") {
document
.querySelector(`#${props.scrollId}`)
?.addEventListener("scroll", handleChangeParentScale);
}
};
const handleRemoveListenerScroll = () => {
if (props.scrollId != "") {
document
.querySelector(`#${props.scrollId}`)
?.removeEventListener("scroll", handleChangeParentScale);
}
};
const calculateCardLocation = ()=>{
const {top,left} = calculateCardPosition(recentElement.value!,386,471)
localtion.value.x=left
localtion.value.y = top
}
const handleShowDetail = (poi:any) => {
recentPoi.value = poi
showSearchStatus.value=2
}
watch(handleElementId, (val: any) => {
handleChangeSelect();
});
watch(canvasScale, (val: number) => {
handleChangeParentScale();
});
watch(showSemi, (val) => {
if (!val) showSearchStatus.value = 0;
});
onMounted(() => {
handListenterScroll();
});
onBeforeUnmount(() => {
handleRemoveListenerScroll();
});
</script>
<style scoped>
.semi-box {
position: absolute;
z-index: 2000;
left: 0;
top: 0;
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: rgba(0, 0, 0, 0.1) 0px 8px 16px 0px;
border-radius: 16px;
height: 32px;
/* width: 248px; */
background: #fff;
padding: 2px;
transform: translateX(-50%);
}
.semi-box.vertical {
width: 32px;
height: auto;
transform: translateY(-50%);
}
.semi-box .items {
padding: 3px 7px;
border-radius: 14px;
height: 100%;
cursor: pointer;
}
.semi-box.vertical .items {
padding: 7px 3px;
}
.semi-box .items:hover {
background: #f1f2f4;
}
.search-card-box {
width: 386px;
padding: 16px;
padding-top: 14px;
border-radius: 24px;
border: 1px solid rgba(0, 0, 0, 0.12) !important;
backdrop-filter: blur(10px);
background: hsla(0, 0%, 100%, 0.9);
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1);
/* transform: translateX(-50%); */
z-index: 2000;
user-select: none;
height: auto !important;
}
.search-card-box.vertical {
/* transform: translateY(-50%); */
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment