no message

This commit is contained in:
kuaifan 2025-05-17 02:03:49 +08:00
parent 18ffad5de5
commit 10f5af5f09
7 changed files with 399 additions and 140 deletions

View File

@ -98,6 +98,7 @@ import emitter from "./store/events";
import SearchBox from "./components/SearchBox.vue";
import UserDetail from "./pages/manage/components/UserDetail.vue";
import {languageName} from "./language";
import {closeLastMicroAggregate} from "./components/MicroApps/queue";
export default {
mixins: [ctrlPressed],
@ -550,6 +551,9 @@ export default {
}
window.__onBeforeUnload = () => {
this.$store.dispatch("onBeforeUnload");
if (closeLastMicroAggregate()) {
return;
}
if (this.$Modal.removeLast()) {
return true;
}

View File

@ -1,55 +0,0 @@
<template>
<div class="micro-app-content">
<micro-app
v-if="isOpen"
:name="appName"
:url="appUrl"
:keep-alive="keepAlive"
:disable-scopecss="disableScopecss"
:data="appData"
@created="$emit('created', $event)"
@beforemount="$emit('beforemount', $event)"
@mounted="$emit('mounted', $event)"
@unmount="$emit('unmount', $event)"
@error="$emit('error', $event)"/>
<div v-if="isLoading" class="micro-app-loader">
<Loading/>
</div>
</div>
</template>
<script>
export default {
name: "MicroContent",
props: {
isOpen: {
type: Boolean,
required: true
},
appName: {
type: String,
required: true
},
appUrl: {
type: String,
required: true
},
keepAlive: {
type: Boolean,
default: true
},
disableScopecss: {
type: Boolean,
default: false
},
isLoading: {
type: Boolean,
default: false
},
appData: {
type: Object,
required: true
}
}
}
</script>

View File

@ -1,54 +1,29 @@
<template>
<div class="micro-app-wrapper">
<template v-for="app in apps">
<Modal
v-if="app.transparent"
v-model="app.isOpen"
:ref="`ref-${app.name}`"
:mask="false"
:footer-hide="true"
:transition-names="[]"
:beforeClose="async () => { await onBeforeClose(app.name) }"
class-name="micro-app-trans"
fullscreen>
<MicroContent
:is-open="app.isOpen"
:app-name="app.name"
:app-url="app.url"
:keep-alive="app.keepAlive"
:disable-scopecss="app.disableScopecss"
:is-loading="app.isLoading"
:app-data="appData(app.name)"
@created="created"
@beforemount="beforemount"
@mounted="mounted"
@unmount="unmount"
@error="error"/>
</Modal>
<DrawerOverlay
v-else
v-model="app.isOpen"
:ref="`ref-${app.name}`"
modal-class="micro-app-modal"
drawer-class="micro-app-drawer"
placement="right"
:beforeClose="async () => { await onBeforeClose(app.name) }"
:size="1200">
<MicroContent
:is-open="app.isOpen"
:app-name="app.name"
:app-url="app.url"
:keep-alive="app.keepAlive"
:disable-scopecss="app.disableScopecss"
:is-loading="app.isLoading"
:app-data="appData(app.name)"
@created="created"
@beforemount="beforemount"
@mounted="mounted"
@unmount="unmount"
@error="error"/>
</DrawerOverlay>
</template>
<div>
<MicroModal
v-for="(app, key) in apps"
:key="key"
v-model="app.isOpen"
:ref="`ref-${app.name}`"
:size="1200"
:transparent="app.transparent"
:beforeClose="async () => { await onBeforeClose(app.name) }">
<micro-app
v-if="app.isOpen"
:name="app.name"
:url="app.url"
:keep-alive="app.keepAlive"
:disable-scopecss="app.disableScopecss"
:data="appData(app.name)"
@created="created"
@beforemount="beforemount"
@mounted="mounted"
@unmount="unmount"
@error="error"/>
<div v-if="app.isLoading" class="micro-app-loader">
<Loading/>
</div>
</MicroModal>
<!--选择用户-->
<UserSelect
@ -60,30 +35,6 @@
</template>
<style lang="scss">
.micro-app-trans {
.ivu-modal-close {
display: none;
}
.ivu-modal-content {
background: transparent;
}
.micro-app-loader {
background-color: rgba(255, 255, 255, 0.6);
}
}
.micro-app-modal {
.ivu-modal-close {
display: none;
}
}
.micro-app-drawer {
.overlay-content {
overflow: hidden;
}
}
.micro-app-loader {
position: absolute;
top: 0;
@ -94,6 +45,12 @@
align-items: center;
justify-content: center;
}
.transparent-mode {
.micro-app-loader {
background-color: rgba(255, 255, 255, 0.6);
}
}
</style>
<script>
@ -104,16 +61,16 @@ import microApp from '@micro-zoe/micro-app'
import DialogWrapper from '../../pages/manage/components/DialogWrapper.vue'
import UserSelect from "../UserSelect.vue";
import {languageList, languageName} from "../../language";
import DrawerOverlay from "../DrawerOverlay/index.vue";
import emitter from "../../store/events";
import TransferDom from "../../directives/transfer-dom";
import MicroContent from "./content.vue";
import store from "../../store";
import MicroModal from "./modal.vue";
import {setMicroAggregate} from "./queue";
export default {
name: "MicroApps",
directives: {TransferDom},
components: {UserSelect, MicroContent, DrawerOverlay},
components: {MicroModal, UserSelect},
data() {
return {
@ -131,11 +88,15 @@ export default {
},
mounted() {
emitter.on('observeMicroApp', this.observeMicroApp);
emitter.on('observeMicroApp:open', this.observeMicroApp);
emitter.on('observeMicroApp:close', this.closeByName);
document.addEventListener('keydown', this.escClose);
},
beforeDestroy() {
emitter.off('observeMicroApp', this.observeMicroApp);
emitter.off('observeMicroApp:open', this.observeMicroApp);
emitter.off('observeMicroApp:close', this.closeByName);
document.removeEventListener('keydown', this.escClose);
},
watch: {
@ -148,6 +109,12 @@ export default {
themeName() {
this.closeAllMicroApp()
},
apps: {
handler(apps) {
setMicroAggregate(apps.filter(item => item.isOpen).map(item => item.name))
},
deep: true,
}
},
computed: {
@ -245,11 +212,7 @@ export default {
this.closeMicroApp(name, destroy)
},
back: () => {
try {
this.$refs[`ref-${name}`][0].close()
} catch (e) {
this.closeMicroApp(name)
}
this.closeByName(name)
},
nextZIndex: () => {
if (typeof window.modalTransferIndex === 'number') {
@ -389,6 +352,33 @@ export default {
}
},
/**
* ESC 关闭微应用
* @param e
*/
escClose(e) {
if (e.keyCode !== 27) {
return;
}
const app = this.apps.findLast(item => item.isOpen);
if (!app) {
return;
}
this.closeByName(app.name)
},
/**
* 通过名称关闭微应用
* @param name
*/
closeByName(name) {
try {
this.$refs[`ref-${name}`][0].onClose()
} catch (e) {
this.closeMicroApp(name)
}
},
/**
* 关闭微应用
* @param name

View File

@ -0,0 +1,289 @@
<template>
<div v-transfer-dom :data-transfer="true" :class="{'transparent-mode': transparent }">
<transition :name="transitions[0]">
<div v-if="value" class="micro-modal-mask" @click="onClose" :style="maskStyle"></div>
</transition>
<transition :name="transitions[1]">
<div v-if="value" class="micro-modal-content" :style="contentStyle">
<div class="micro-modal-close" @click="onClose">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" fill="none" role="img" class="icon fill-current">
<path d="M8.28596 6.51819C7.7978 6.03003 7.00634 6.03003 6.51819 6.51819C6.03003 7.00634 6.03003 7.7978 6.51819 8.28596L11.2322 13L6.51819 17.714C6.03003 18.2022 6.03003 18.9937 6.51819 19.4818C7.00634 19.97 7.7978 19.97 8.28596 19.4818L13 14.7678L17.714 19.4818C18.2022 19.97 18.9937 19.97 19.4818 19.4818C19.97 18.9937 19.97 18.2022 19.4818 17.714L14.7678 13L19.4818 8.28596C19.97 7.7978 19.97 7.00634 19.4818 6.51819C18.9937 6.03003 18.2022 6.03003 17.714 6.51819L13 11.2322L8.28596 6.51819Z" fill="currentColor"></path>
</svg>
</div>
<ResizeLine
class="micro-modal-resize"
v-model="dynamicSize"
placement="right"
:min="minSize"
:max="0"
:reverse="true"
:beforeResize="beforeResize"
@on-change="onChangeResize"/>
<div class="micro-modal-body">
<slot></slot>
</div>
</div>
</transition>
</div>
</template>
<script>
import TransferDom from "../../directives/transfer-dom";
import ResizeLine from "../ResizeLine.vue";
export default {
name: 'MicroModal',
components: {ResizeLine},
directives: {TransferDom},
props: {
value: {
type: Boolean,
default: false
},
size: {
type: Number,
default: 300
},
minSize: {
type: Number,
default: 300
},
transparent: {
type: Boolean,
default: false
},
beforeClose: Function
},
data() {
return {
dynamicSize: 0,
zIndex: 1000
}
},
computed: {
maskStyle({zIndex}) {
return {zIndex}
},
contentStyle({dynamicSize, zIndex}) {
const width = dynamicSize <= 100 ? `${dynamicSize}%` : `${dynamicSize}px`
return {width, zIndex}
},
transitions({transparent}) {
if (transparent) {
return ['', '']
}
return ['micro-modal-fade', 'micro-modal-slide']
},
},
watch: {
value: {
handler(val) {
if (val) {
this.zIndex = typeof window.modalTransferIndex === 'number' ? window.modalTransferIndex++ : 1000;
}
},
immediate: true
},
size: {
handler(val) {
this.dynamicSize = parseInt(val);
},
immediate: true
}
},
methods: {
beforeResize() {
return new Promise(resolve => {
if (this.dynamicSize <= 100) {
this.updateSize();
}
resolve()
})
},
onChangeResize({event}) {
if (event === 'up') {
this.updateSize();
}
},
updateSize() {
this.dynamicSize = this.$refs.body.clientWidth;
},
onClose() {
if (!this.beforeClose) {
return this.handleClose();
}
const before = this.beforeClose();
if (before && before.then) {
before.then(() => {
this.handleClose();
});
} else {
this.handleClose();
}
},
handleClose() {
this.$emit('input', false)
}
}
}
</script>
<style lang="scss" scoped>
.micro-modal {
&-mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(55, 55, 55, .6);
}
&-close {
position: absolute;
top: 0;
left: -40px;
z-index: 1;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
cursor: pointer;
> svg {
width: 24px;
height: 24px;
transition: transform 0.3s ease-in-out;
}
&:hover {
> svg {
transform: rotate(-90deg);
}
}
}
&-resize {
position: absolute;
top: 0;
bottom: 0;
left: -3px;
z-index: 1;
width: 3px;
}
&-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
height: 100%;
max-width: calc(100% - 40px);
}
&-body {
flex: 1;
height: 0;
overflow: hidden;
border-radius: 18px 0 0 18px;
background-color: #fff;
position: relative;
}
&-fade {
&-enter-active {
transition: all .2s ease;
}
&-leave-active {
transition: all .2s ease;
}
&-enter,
&-leave-to {
opacity: 0;
}
}
&-slide {
&-enter-active {
transition: all .2s ease;
}
&-leave-active {
transition: all .2s ease;
}
&-enter,
&-leave-to {
transform: translate(15%, 0) scale(0.98);
opacity: 0;
}
}
}
//
@media (max-width: 500px) {
.micro-modal {
&-mask {
background-color: transparent;
}
&-close,
&-resize {
display: none;
}
&-content {
left: 0;
min-width: 100%;
max-width: 100%;
}
&-body {
border-radius: 0;
}
&-slide {
&-enter,
&-leave-to {
transform: translate(0, 15%) scale(0.98);
}
}
}
}
//
.transparent-mode {
.micro-modal {
&-mask {
background-color: transparent;
}
&-close,
&-resize {
display: none;
}
&-content {
left: 0;
min-width: 100%;
max-width: 100%;
}
&-body {
border-radius: 0;
background-color: transparent;
}
}
}
</style>

View File

@ -0,0 +1,22 @@
import emitter from "../../store/events";
let microAggregate = [];
const setMicroAggregate = (names) => {
microAggregate = names;
}
const hasMicroAggregate = () => {
return microAggregate.length > 0;
}
const closeLastMicroAggregate = () => {
const name = microAggregate.pop();
if (!name) {
return false;
}
emitter.emit("observeMicroApp:close", name);
return true;
}
export { setMicroAggregate, hasMicroAggregate, closeLastMicroAggregate};

View File

@ -6,6 +6,7 @@
<script>
import {mapState} from "vuex";
import {closeLastMicroAggregate, hasMicroAggregate} from "../MicroApps/queue";
export default {
name: "MobileBack",
@ -110,6 +111,9 @@ export default {
if (!this.mobileTabbar) {
return true;
}
if (hasMicroAggregate()) {
return true;
}
if (this.$Modal.visibleList().length > 0) {
return true;
}
@ -134,6 +138,11 @@ export default {
//
this.$store.commit('menu/operation', {})
//
if (closeLastMicroAggregate()) {
return;
}
//
if (this.$Modal.removeLast()) {
return;

View File

@ -1193,7 +1193,7 @@ export default {
case 'appstore':
this.$store.dispatch("openMicroApp", {
name: 'appstore',
url: 'appstore/web/',
url: 'http://localhost:5173/',
disableScopecss: true,
});
break;