持续集成分支

This commit is contained in:
aixianling
2024-10-10 16:04:11 +08:00
parent f35c272a19
commit 00b2dbb4a4
38 changed files with 2222 additions and 111 deletions

31
src/views/building.vue Normal file
View File

@@ -0,0 +1,31 @@
<template>
<section class="building">
<div class="title">功能开发中,敬请期待...</div>
</section>
</template>
<script>
export default {
name: "building"
}
</script>
<style lang="scss" scoped>
.building {
position: relative;
height: 100%;
background-image: url("../assets/building.png");
background-size: 400px 300px;
background-repeat: no-repeat;
background-position: center, center;
.title {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin-top: 150px;
font-weight: bold;
}
}
</style>

42
src/views/console.vue Normal file
View File

@@ -0,0 +1,42 @@
<template>
<section class="console">
<div class="consoleBg" v-text="`欢迎使用${system.fullTitle}`"/>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "console",
label: "工作台",
computed: {
...mapState(['sys']),
system: v => v.sys.info || {}
},
}
</script>
<style lang="scss" scoped>
.console {
height: 100%;
.consoleBg {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-image: url("https://cdn.cunwuyun.cn/dvcp/consoleBg.png");
background-size: 600px 362px;
background-repeat: no-repeat;
background-position: center top;
padding-top: 402px;
font-size: 32px;
font-family: MicrosoftYaHei-Bold, MicrosoftYaHei;
font-weight: bold;
color: #95A1B0;
min-width: 600px;
text-align: center;
}
}
</style>

83
src/views/dvIndex.vue Normal file
View File

@@ -0,0 +1,83 @@
<template>
<section class="dvIndex">
<ai-dv-wrapper v-model="activeTab" :views="views" :title="title" :theme="theme" v-if="views.length" :background="bgImg" :type="currentStyle" :titleSize="titleSize">
<ai-dv-viewer urlPrefix="/app" :instance="instance" :dict="dict" :id="currentView.id"/>
</ai-dv-wrapper>
</section>
</template>
<script>
import Vue from "vue";
import {waiting} from "../utils";
export default {
name: "dvIndex",
provide() {
return {
dv: this
}
},
computed: {
currentView: v => v.views.find(e => e.id == v.activeTab) || v.views?.[0] || {},
background: v => JSON.parse(v.currentView.config || null)?.dashboard?.backgroundImage?.[0]?.url || "",
bgImg: v => v.theme == 1 ? 'https://cdn.cunwuyun.cn/dvcp/dv/img/dj-bg.png' : v.background,
theme() {
if (!this.currentView) return '0'
if (!this.currentView.config) return '0'
const config = JSON.parse(this.currentView.config)
if (config.custom) {
return '0'
}
return config.dashboard.theme
},
currentStyle: v => JSON.parse(v.currentView.config || null)?.dashboard?.style || "black",
titleSize: v => JSON.parse(v.currentView.config || "{}").dashboard?.titleSize
},
data() {
return {
instance: this.$request,
dict: this.$dict,
activeTab: 0,
views: [],
title: "",
}
},
methods: {
getDvOptions() {
let {id} = this.$route.query
return id ? this.instance.post("/app/appdiylargescreen/queryLargeScreenProjectDetailById", null, {
params: {id, status: 1}
}).then(res => {
if (res?.data) {
this.title = res.data.name
this.views = res.data.lsList?.map(e => ({...e, label: e.title}))
}
}) : Promise.reject()
},
loadDvs() {
//新App的自动化格式
waiting.init({innerHTML: '应用加载中..'})
let apps = require.context('../../apps', true, /\.(\/.+)\/App[A-Z][^\/]+D[Vv]\.vue$/, "lazy")
return Promise.all(apps.keys().map(path => apps(path).then(file => {
if (file.default) {
let {name} = file.default
waiting.setContent(`加载${name}...`)
Vue.component(name, file.default)
} else return 0
}))).then(() => {
waiting.setContent(`正在进入系统...`)
setTimeout(() => waiting.close(), 1000)
})
}
},
created() {
this.loadDvs().then(() => this.getDvOptions())
}
}
</script>
<style lang="scss" scoped>
.dvIndex {
height: 100%;
}
</style>

42
src/views/home.vue Normal file
View File

@@ -0,0 +1,42 @@
<template>
<section class="home">
<header-nav/>
<el-row class="fill" type="flex">
<slider-nav/>
<main-content class="fill"/>
</el-row>
<ai-copilot v-if="useCopilot" :http="$request"/>
</section>
</template>
<script>
import SliderNav from "../components/sliderNav";
import MainContent from "../components/mainContent";
import HeaderNav from "../components/headerNav";
import configExtra from "../config.json"
export default {
name: 'app',
components: {HeaderNav, MainContent, SliderNav},
computed: {
useCopilot: () => !!configExtra?.copilot
},
created() {
import("../../apps/actions").then(extra => {
const actions = extra?.default || {}
this.$store.hotUpdate({actions})
Object.keys(actions)?.map(action => this.$store.dispatch(action))
}).catch(() => 0)
},
}
</script>
<style lang="scss" scoped>
.home {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
padding-top: 48px;
box-sizing: border-box;
}
</style>

55
src/views/mainEntry.vue Normal file
View File

@@ -0,0 +1,55 @@
<template>
<section class="mainEntry fill">
<ai-detail list v-if="hasIntro">
<template #content>
<ai-intro :id="currentApp.guideId" :instance="$request" @start="handleStartUse"/>
</template>
</ai-detail>
<component v-else :is="app" :instance="$request" :dict="$dict" :permissions="$permissions" :menuName="currentApp.name"/>
</section>
</template>
<script>
import Building from "./building";
import Vue from "vue";
import {mapGetters, mapMutations, mapState} from "vuex";
export default {
name: "mainEntry",
components: {Building},
computed: {
...mapState(['logs']),
...mapGetters(['mods']),
currentApp() {
const {fullPath, name} = this.$route
return this.mods.find(e => !name ? fullPath.indexOf(e.path) > -1 : name == e.route) || Building
},
app() {
const {currentApp} = this
return Vue.component(currentApp?.component) ? currentApp.component : Building
},
hasIntro() {
const {app, currentApp, logs} = this
return !!currentApp.guideId && !logs?.closeIntro?.includes(app)
}
},
methods: {
...mapMutations(['addCloseIntro']),
handleStartUse() {
this.addCloseIntro(this.app)
}
}
}
</script>
<style lang="scss" scoped>
.mainEntry {
width: 100%;
height: 100%;
& > * {
height: 100%;
}
}
</style>

172
src/views/sign.vue Normal file
View File

@@ -0,0 +1,172 @@
<template>
<section class="sign">
<div class="left signLeftBg">
<el-row type="flex" align="middle">
<img class="AiIcon" v-if="/[\\\/]/.test(logo.icon)" :src="logo.icon" alt=""/>
<ai-icon v-else type="logo" :icon="logo.icon"/>
<div v-if="logo.text" class="logoText mar-l8" v-text="logo.text"/>
</el-row>
<div class="signLeftContent">
<div class="titlePane">
<b v-text="system.name"/>
<div v-text="system.title"/>
</div>
<div class="subTitle" v-for="(t,i) in subTitles" :key="i" v-html="t"/>
</div>
</div>
<div class="right">
<div class="projectName mar-b48" :title="system.fullTitle">{{ system.fullTitle }}</div>
<ai-sign v-if="system.edition=='saas'" @login="login" :instance="instance" visible :tps="['wxwork']" :sassLogin="!isDev"/>
<ai-sign v-else isSignIn @login="login" :instance="instance" visible :showScanLogin="system.edition=='standard'||!system.edition"/>
<el-row type="flex" align="middle" class="bottomRecord">
<div v-if="system.recordDesc" v-text="system.recordDesc"/>
<el-link v-if="system.recordNo" v-text="system.recordNo" :href="system.recordURL"/>
<div v-if="system.ssl" v-html="system.ssl"/>
</el-row>
</div>
<app-licence :instance="instance" ref="licence"/>
</section>
</template>
<script>
import {mapMutations, mapState} from 'vuex'
import AppLicence from "../components/AppLicence";
export default {
name: "sign",
components: {AppLicence},
computed: {
...mapState(['user', 'sys']),
instance: v => v.$request,
system: v => v.sys?.info || {},
subTitles() {
let list = [
"构建全域数字大脑,助力政府科学决策",
"全域统一应用入口,移动办公高效协同",
"直接触达居民微信,政民互动“零距离”"
]
return (typeof this.system.desc == "object" ? this.system.desc : JSON.parse(this.system.desc || null)) || list
},
logo: v => !!v.system.loginLogo ? {icon: v.system.loginLogo, text: v.system.loginLogoText} : {icon: v.system.logo, text: v.system.logoText},
isDev: () => process.env.NODE_ENV == "development"
},
created() {
if (this.user.token) {
this.handleGotoHome()
} else {
const {code, auth_code} = this.$route.query
if (code) {
this.toLogin(code)
} else if (auth_code) {
this.tpLogin(auth_code)
}
}
},
methods: {
...mapMutations(['setToken']),
login(data) {
if (data.data == '999') {
return this.$refs.licence.show()
}
if (data?.access_token) {
this.setToken([data.token_type, data.access_token].join(" "))
this.handleGotoHome()
}
},
handleGotoHome() {
this.$message.success("登录成功!")
if (this.$route.hash == "#dv") {
this.$router.push({name: "数据大屏入口", hash: "#dv"})
} else {
this.$router.push({name: "Home"})
}
},
toLogin(code) {
this.instance.post(`/auth/wechatcp-qr/token`, {
code: code,
type: 'cpuser'
}, {
auth: {
username: 'villcloud',
password: "villcloud"
},
params: {
grant_type: 'password',
scope: 'server'
}
}).then(this.login)
},
tpLogin(code) {
this.instance.post("/auth/wechatcp-qr/token", {code}, {
auth: {
username: 'villcloud',
password: "villcloud"
},
params: {
grant_type: 'password',
scope: 'server'
}
}).then(this.login).catch(() => this.$router.push({}))
}
},
}
</script>
<style lang="scss" scoped>
.sign {
display: flex;
box-sizing: border-box;
height: 100%;
.AiIcon {
font-size: 40px;
height: 40px;
}
.logoText {
font-size: 20px;
}
:deep(.left ) {
width: 480px;
flex-shrink: 0;
background-size: 100% 100%;
background-repeat: no-repeat;
padding-left: 64px;
padding-top: 40px;
box-sizing: border-box;
color: #fff;
font-size: 16px;
.iconcunwei1 {
font-size: 36px;
}
}
:deep(.right ) {
flex: 1;
min-width: 0;
background-color: #F6F8FB;
background-image: url("../assets/loginRightTop.png"), url("../assets/loginRightBottom.png");
background-repeat: no-repeat;
background-position: calc(100% - 80px) 0, calc(100% - 40px) 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.bottomRecord {
font-size: 12px;
color: #999;
gap: 16px;
position: fixed;
bottom: 20px;
.el-link {
font-size: inherit;
}
}
}
}
</style>