🧹 清理重复配置文件

- 删除根目录中重复的 NestJS 配置文件
- 删除 tsconfig.json, tsconfig.build.json, eslint.config.mjs, .prettierrc
- 保留 wwjcloud-nest/ 目录中的完整配置
- 避免配置冲突,确保项目结构清晰
This commit is contained in:
wanwu
2025-10-14 23:56:20 +08:00
parent 7a160dd04b
commit e7a1d6b4d6
3263 changed files with 356 additions and 112679 deletions

View File

@@ -0,0 +1,146 @@
<template>
<div class="box-border pt-[68px] px-[76px] overview-top" v-loading="loading">
<div v-if="detail.appList && !loading">
<div class="flex justify-between items-center">
<div>
<div class="font-[600] text-[26px] text-[#222] leading-[37px]">{{ t('app') }}</div>
<div class="font-[500] text-[14px] text-[#222] leading-[20px] mt-[12px]">{{ t('versionInfo') }}&nbsp;{{ t('currentVersion') }}</div>
</div>
<el-button @click="toAppStore" class="px-[15px]">
<div class="mr-[9px] text-[#3F3F3F] iconfont iconxiazai01"></div>
<span class="font-[600] text-[14px] text-[#222] leading-[20px]">{{ t('appStore') }}</span>
</el-button>
</div>
<div class="flex flex-wrap mt-[40px]">
<template v-for="(item, index) in detail.appList" :key="index">
<div class="app-item w-[280px] box-border !bg-[#fff] rounded-[6px] cursor-pointer mr-[20px] mb-[20px] overflow-hidden" @click="itemPath(item)">
<div class="bg-[#F7FAFB] py-[18px] px-[24px] flex items-center app-item-head">
<el-image class="w-[44px] h-[44px] rounded-[8px]" :src="img(item.icon)" fit="contain">
<template #error>
<div class="image-slot">
<img class="w-[40px] h-[40px] rounded-[8px]" src="@/app/assets/images/app_store/app_store_default.png" />
</div>
</template>
</el-image>
</div>
<div class="py-[18px] px-[24px]">
<div class="font-[600] leading-[1] text-[14px] text-[#222]">{{ item.title }}</div>
<el-tooltip class="box-item" effect="light" :content="item.desc" placement="bottom-start">
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[6px] truncate">
{{ item.desc }}
</div>
</el-tooltip>
</div>
</div>
</template>
<el-empty class="mx-auto overview-empty" v-if="!detail.appList.length && !loading">
<template #image>
<div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
</div>
</template>
<template #description>
<p class="flex items-center">
<span>{{ t('descriptionLeft') }}</span>
<el-link type="primary" @click="toAppStore" class="mx-[5px]">{{ t('link') }}</el-link>
<span>{{ t('descriptionRight') }}</span>
</p>
</template>
</el-empty>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getInstalledAddonList } from '@/app/api/addon'
import { img } from '@/utils/common'
import { useRouter } from 'vue-router'
import storage from '@/utils/storage'
import { findFirstValidRoute } from '@/router/routers'
import useUserStore from '@/stores/modules/user'
import { AnyObject } from '@/types/global'
const router = useRouter()
const userStore:AnyObject = useUserStore()
const loading = ref(true)
const detail:{
appList: {
title: string,
desc: string,
icon: string
}[]
} = reactive({
appList: []
})
const appLink: any = ref({})
const getAuthaddonFn = () => {
loading.value = true
getInstalledAddonList().then(res => {
Object.values(res.data).forEach((item: any, index) => {
if (item.type == 'app') {
detail.appList.push(item)
}
})
userStore.routers.forEach((item:any, index:number) => {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
appLink.value[item.meta.app] = findFirstValidRoute(item.children)
} else {
appLink.value[item.meta.app] = item.name
}
})
loading.value = false
}).catch(() => {
loading.value = false
})
}
getAuthaddonFn()
const itemPath = (data: any) => {
storage.set({ key: 'menuAppStorage', data: data.key })
storage.set({ key: 'plugMenuTypeStorage', data: '' })
const appMenuList = userStore.appMenuList
appMenuList.push(data.key)
userStore.setAppMenuList(appMenuList)
const name: any = appLink.value[data.key]
router.push({ name })
}
// 跳转至开发者
const toAppStore = () => {
router.push('/app_manage/app_store')
}
</script>
<style lang="scss" scoped>
.main-container {
background: linear-gradient(180deg, rgba(253, 253, 253, 0.24) 0%, #FAFAFA 100%);
min-height: calc(100vh - 64px);
}
.overview-top {
background-image: url('@/app/assets/images/index/overview.png');
background-repeat: no-repeat;
background-size: cover;
height: calc(100vh - 120px);
}
.app-item {
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.18);
}
.app-item:hover .app-item-head{
background-color: #FDF4EF;
}
</style>
<style>
.overview-empty .el-empty__image {
width: auto !important;
}
</style>

View File

@@ -0,0 +1,632 @@
<template>
<div class="flex items-center justify-center">
<el-card class="box-card !border-none profile-data w-[1280px] mt-[20px] loading-box" shadow="never" v-loading="loading" >
<!-- <div> -->
<div class="box-border">
<div class="bg-[#fff] mb-[20px] rounded-[6px] relative banner-box" v-if="showBanner" @mouseenter="hovering = 'banner'" @mouseleave="hovering = ''">
<span class="absolute right-0 top-[-5px] text-[#999] hover:text-red-500 cursor-pointer z-10" v-if="hovering === 'banner'">
<el-icon class="icon" :size="20" color="#7b7b7b" @click="closeModule('banner')">
<CircleCloseFilled />
</el-icon>
</span>
<div class="flex h-[156px]">
<div class="w-full h-full ">
<el-carousel :interval="3000" height="156px" class="rounded-[6px]">
<!-- <el-carousel-item >
<div class="h-full index-carousel" @click="toApplication">
<img :src="img('static/resource/images/banner_1.png')" alt="" class="w-full h-full cursor-pointer">
</div>
</el-carousel-item>
<el-carousel-item >
<div class="h-full index-carousel" @click="toApplication">
<img :src="img('static/resource/images/banner_2.png')" alt="" class="w-full h-full cursor-pointer">
</div>
</el-carousel-item> -->
<el-carousel-item v-for="(item, index) in bannerlistr" :key="index">
<div class="h-full index-carousel" @click="toApplicationurl(item)">
<img :src="item.image" alt="" class="w-full h-full cursor-pointer">
</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
</div>
<div v-if="showQuickStart" class="mt-[50px] flex items-center justify-between relative" @mouseenter="hovering = 'quickStart'" @mouseleave="hovering = ''">
<span class="absolute right-0 top-[-5px] text-[#999] hover:text-red-500 cursor-pointer z-10" v-if="hovering === 'quickStart'">
<el-icon class="icon" :size="20" color="#7b7b7b" @click="closeModule('quickStart')">
<CircleCloseFilled />
</el-icon>
</span>
<div class="w-[67%]">
<p class="text-[18px] text-[#1D1F3A] mb-[10px]">快速开始构建你的项目及应用</p>
<p class="text-[14px] text-[#4F516D]">基于Vue3+PHP8+MYSQL8的跨平台应用构建</p>
<div class="mt-[20px] grid grid-cols-2 gap-[20px]">
<!-- 卡片 1 -->
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toLink('/admin/tools/addon')">
<div class="title flex items-center">
<span class="iconfont iconkaifayigechajianc !text-[20px] mr-[10px]"></span>
<span class="text-[14px] text-[#1D1F3A]">开发一个插件</span>
</div>
<div class="absolute bottom-3 left-5 right-4 desc">
<p class="text-[12px] text-[#4F516D]">创建自定义功能扩展</p>
</div>
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
</div>
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toLink('/admin/tools/code')">
<div class="title flex items-center">
<span class="iconfont iconAIshengchengchajianc !text-[20px] mr-[10px]"></span>
<span class="text-[14px] text-[#1D1F3A]">AI生成插件</span>
</div>
<div class="absolute bottom-3 left-5 right-4 desc">
<p class="text-[12px] text-[#4F516D]">使用AI自动生成插件代码</p>
</div>
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
</div>
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toHref('/app_manage/app_store','uninstalled')">
<div class="title flex items-center">
<span class="iconfont iconanzhuangyigechajian1 !text-[20px] mr-[10px]"></span>
<span class="text-[14px] text-[#1D1F3A]">安装一个插件</span>
</div>
<div class="absolute bottom-3 left-5 right-4 desc">
<p class="text-[12px] text-[#4F516D]">从市场安装现有插件</p>
</div>
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
</div>
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toLink('/admin/tools/cloud_compile')">
<div class="title flex items-center">
<span class="iconfont iconyunbianyic !text-[20px] mr-[10px]"></span>
<span class="text-[14px] text-[#1D1F3A]">云编译</span>
</div>
<div class="absolute bottom-3 left-5 right-4 desc">
<p class="text-[12px] text-[#4F516D]">在线编译你的应用</p>
</div>
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
</div>
</div>
</div>
<div class="w-[33%] ml-[20px]">
<p class="text-[18px] text-[#1D1F3A] mb-[10px]">AI编程</p>
<div class="flex items-center text-[14px] text-[#4F516D]">
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '1' }]" @click="activeNameTabFn1('1')">{{ t("插件开发") }}</div>
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '2' }]" @click="activeNameTabFn1('2')">{{ t("插件设计") }}</div>
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '3' }]" @click="activeNameTabFn1('3')">{{ t("APP编译") }}</div>
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '4' }]" @click="activeNameTabFn1('4')">{{ t("小程序发布") }}</div>
</div>
<div class="bg-[#EDEEF4] h-[160px] rounded-[6px] mt-[20px] text-[#666] cursor-pointer">
<video src="@/app/assets/images/index/low-play.mp4" preload="auto" :controls="true" muted autoplay loop class="w-full h-full"></video>
</div>
</div>
</div>
<div v-if="showNiuCloud && appList.length > 0" class="mt-[50px] relative" @mouseenter="hovering = 'niuCloud'" @mouseleave="hovering = ''">
<span class="absolute right-0 top-[-5px] text-[#999] hover:text-red-500 cursor-pointer z-10" v-if="hovering === 'niuCloud'">
<el-icon class="icon" :size="20" color="#7b7b7b" @click="closeModule('niuCloud')">
<CircleCloseFilled />
</el-icon>
</span>
<p class="text-[18px] text-[#1D1F3A]">NIUCLOUD生态精选应用推荐</p>
<!-- <div class="flex justify-between mt-[20px]">
<div class="flex">
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'installed' }]" @click="activeNameTabFn('installed')">{{ t("全部") }}</div>
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'uninstalled' }]" @click="activeNameTabFn('uninstalled')">{{ t("商城") }}</div>
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'all' }]" @click="activeNameTabFn('all')">{{ t("数字人") }}</div>
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'all1' }]" @click="activeNameTabFn('all1')">{{ t("拼团") }}</div>
</div>
</div> -->
<div class="mt-[20px]">
<div class="grid grid-cols-5 gap-4">
<div v-for="(item,index) in appList.slice(0,5)" :key="index" @click="toApplicationDetail(item)" class="bg-[#EDEEF4] rounded-[8px] overflow-hidden text-[#666] cursor-pointer hover:shadow-xl transition-shadow duration-300 border-[1px] border-[#EDEEF4]">
<img :src="img(item.app_logo)" alt="" class="w-full rounded-t-[6px]" >
<div class="bg-[#fff] p-[10px]">
<div class="text-[16px] text-[#1D1F3A] mb-[10px]">
<span class="using-hidden">{{ item.app_name }}</span>
</div>
<div class="text-[12px] text-[#4F516D]">
<span class="using-hidden">{{ item.app_desc }}</span>
</div>
<div class="text-[12px] mt-[20px] flex justify-between">
<div class="flex items-center">
<img :src="img(item.developer.headimg)" alt="" class="w-[18px] h-[18px] rounded-full mr-[6px]" v-if="item.developer.headimg">
<img src="@/app/assets/images/member_head.png" alt="" class="w-[18px] h-[18px] rounded-full mr-[6px]" v-else>
<span class="text-[#4F516D] text-[12px] using-hidden">{{ item.developer.nickname }}</span>
</div>
<div class="flex items-center">
<div class="mr-[10px]">
<span class="iconfont iconchakan !text-[12px] mr-[8px] !text-[#4F516D]"></span>
<span class="text-[#4F516D] text-[12px]">{{ item.visit_num}}</span>
</div>
<div>
<span class="iconfont iconxiaoliang !text-[12px] mr-[8px] !text-[#4F516D]"></span>
<span class="text-[#4F516D] text-[12px]">{{ item.sale_num }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-[18px] mt-[50px] mb-[15px]">{{ t("dataSummarize") }}</div>
<el-card class="box-card !border-none profile-data" shadow="never">
<el-row :gutter="20" class="top">
<el-col>
<el-card shadow="never" @click="toHref('site/manage','1')" class="cursor-pointer min-w-[180px] first-con">
<div class="text-[20px] font-bold">{{ statInfo.today_data.norma_site_count }}</div>
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("normalSiteSum") }}</div>
</el-card>
</el-col>
<el-col>
<el-card shadow="never" @click="toHref('site/manage','1')" class="cursor-pointer min-w-[180px] first-con">
<div class="text-[20px] font-bold">{{ statInfo.today_data.week_expire_site_count }}</div>
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("weekExpireSiteCount") }}</div>
</el-card>
</el-col>
<el-col>
<el-card shadow="never" @click="toHref('site/manage','2')" class="cursor-pointer min-w-[180px] first-con">
<div class="text-[20px] font-bold">{{ statInfo.today_data.expire_site_count }}</div>
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("expireSiteSum") }}</div>
</el-card>
</el-col>
<el-col>
<el-card shadow="never" @click="toHref('/app_manage/app_store','uninstalled')" class="cursor-pointer min-w-[180px] first-con">
<div class="text-[20px] font-bold">{{ statInfo.app.app_no_installed_count }}</div>
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("noInstallAppSun") }}</div>
</el-card>
</el-col>
<el-col>
<el-card shadow="never" @click="toHref('/app_manage/app_store','installed')" class="cursor-pointer min-w-[180px] first-con">
<div class="text-[20px] font-bold">{{ statInfo.app.app_installed_count }}</div>
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("installAppSun") }}</div>
</el-card>
</el-col>
</el-row>
</el-card>
<div class="text-[18px] mt-[50px] mb-[15px]">{{ t("常用功能") }}</div>
<div class="flex justify-between">
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toTypeLink('site/manage','list')">
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_list1.png" />
<span class="text-[14px] ml-3">{{ t("siteList") }}</span>
</div>
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toTypeLink('site/manage','group')">
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_tc1.png" />
<span class="text-[14px] ml-3">{{ t("sitePackage") }}</span>
</div>
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toTypeLink('site/manage','list')" >
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_add1.png" />
<span class="text-[14px] ml-3">{{ t("newSite") }}</span>
</div>
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toLink('/admin/site/user')">
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_user1.png" />
<span class="text-[14px] ml-3">{{ t("administrator") }}</span>
</div>
<div class="flex-1 h-[80px] flex items-center cursor-pointer bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toApplication">
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/app_store1.png" />
<span class="text-[14px] ml-3">{{ t("appMarketplace") }}</span>
</div>
</div>
<div class="mt-[50px] flex site">
<div class="flex-1 ">
<div class="text-[18px] mb-[15px]">{{ t("newSite") }}</div>
<el-card class="box-card border mr-[30px] echarts-box" shadow="never">
<div ref="newSiteStat" class="echarts-con" :style="{ width: '100%', height: '280px' }"></div>
</el-card>
</div>
<div class="flex-1 ">
<div class="text-[18px] mb-[15px]">{{ t("addUser") }}</div>
<el-card class="box-card border flex-1 echarts-box" shadow="never">
<div ref="addUser" class="echarts-con" :style="{ width: '100%', height: '280px' }"></div>
</el-card>
</div>
</div>
<div class="flex justify-between text-[12px] mt-[20px] text-[#666]" v-if="copyright">
<div>
<a :href="copyright.copyright_link" target="_blank">
<span class="mr-3" v-if="copyright.company_name">{{ copyright.company_name }}</span>
</a>
<a href="https://beian.miit.gov.cn/" v-if="copyright.icp" target="_blank">
<span class="mr-3">{{ copyright.icp }}</span>
</a>
</div>
<div class="flex items-center cursor-pointer">
<span class="mx-1" @click="getInfoFn">版权信息</span> | <span
class="mx-1">开发者联盟与隐私的声明</span> | <span class="mx-1">隐私政策</span> | <span
class="mx-1">联系我们</span> | <span class="mx-1">Cookies</span>
</div>
</div>
</div>
<!-- </div> -->
</el-card>
<el-dialog v-model="dialogVisible" title="版权信息" width="500">
<span>{{ copyright.copyright_desc }}</span>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, watch ,onMounted} from 'vue'
import { t } from '@/lang'
import { getStatInfo } from '@/app/api/stat'
import { getAppIndex ,getAdvList} from '@/app/api/addon'
import * as echarts from 'echarts'
import { getFrameworkNewVersion } from '@/app/api/module'
import { getWebCopyright } from '@/app/api/sys'
import { useRoute, useRouter } from 'vue-router'
import { AnyObject } from '@/types/global'
import useStyleStore from '@/stores/modules/style'
import { img } from '@/utils/common'
const activeName = ref('installed')
const activeNameTabFn = (data: any) => {
activeName.value = data
}
const activeName1 = ref('1')
const activeNameTabFn1 = (data: any) => {
activeName1.value = data
}
const dialogVisible = ref(false)
const loading = ref(true)
const newSiteStat = ref<any>(null)
const addUser = ref<any>(null)
const styleStore = useStyleStore()
const copyright = ref(null)
getWebCopyright().then(({ data }) => {
copyright.value = data
})
interface NewVersion {
last_version: string
}
interface StatInfo {
today_data: AnyObject,
system: AnyObject,
version: AnyObject,
about: any,
site_stat: AnyObject,
site_group_stat: AnyObject,
member_count_stat: AnyObject,
app: AnyObject
}
const newVersion = ref<NewVersion>({
last_version: ''
})
// 插件
const appList = ref<any>([])
const getAppIndexFn = () => {
getAppIndex().then((res) => {
appList.value = res.data
})
}
const bannerlistr = ref<any>([])
const getAdvListFn = () => {
getAdvList().then((res) => {
bannerlistr.value = res.data
})
}
const getInfoFn = () => {
if (copyright.value.copyright_desc) {
dialogVisible.value = true
}
}
getFrameworkNewVersion().then(({ data }) => {
newVersion.value = data
})
const statInfo = ref<StatInfo>({
today_data: {},
system: {},
version: {},
about: [],
member_count_stat: {},
site_stat: {},
site_group_stat: {},
app: {}
})
const getStatInfoFn = async() => {
statInfo.value = await (await getStatInfo()).data
setTimeout(() => {
drawChart()
}, 20)
}
// 绘制折线图
const drawChart = () => {
// 新增站点
const newSiteStatChart = echarts.init(newSiteStat.value)
const newSiteStatOption = ref({
legend: {},
xAxis: {
data: []
},
yAxis: {},
tooltip: {
trigger: 'axis'
},
series: [
{
name: t('newSite'),
type: 'line',
data: [],
itemStyle: {
normal: {
color: '#2FCEB6' //点的颜色
}
},
lineStyle: {
color: '#2FCEB6' //线的颜色
}
},
]
})
newSiteStatOption.value.xAxis.data = statInfo.value.site_stat.date
newSiteStatOption.value.series[0].data = statInfo.value.site_stat.value
newSiteStatChart.setOption(newSiteStatOption.value)
// 新增用户
const newUserChart = echarts.init(addUser.value)
const newUserOption: AnyObject = ref({
legend: {},
xAxis: {
data: []
},
yAxis: {},
tooltip: {
trigger: 'axis'
},
series: [
{
name: t('addUser'),
type: 'line',
data: [],
itemStyle: {
normal: {
color: '#F7DC76' //点的颜色
}
},
lineStyle: {
color: '#F7DC76' //线的颜色
}
}
]
})
newUserOption.value.xAxis.data = statInfo.value.member_count_stat.date
newUserOption.value.series[0].data = statInfo.value.member_count_stat.value
newUserChart.setOption(newUserOption.value)
window.addEventListener('resize', () => {
// 页面大小变化后Echarts也更改大小
newSiteStatChart.resize()
newUserChart.resize()
})
}
const router = useRouter()
const route = useRoute()
if (route.path == '/admin/index') {
styleStore.changeStyle()
}
watch(() => route.path, (newval, oldval) => {
if (newval !== '/admin/index') {
styleStore.changeBlack()
}
})
/**
* 链接跳转
*/
const toLink = (link: any) => {
router.push(link)
}
const toHref = (url: any,id: any) => {
router.push({
path: url,
query: { id }
})
}
const toTypeLink = (url: any,type: any) => {
router.push({
path: url,
query: { type }
})
}
const toApplication = () => {
window.open('https://www.niucloud.com/app')
}
const toApplicationurl = (item: any) => {
window.open(item.url)
}
const toApplicationDetail = (item: any) => {
window.open(`https://www.niucloud.com/app/detail?id=${item.app_id}`)
}
// 更新时间
const time = ref('')
const nowTime = () => {
const date = new Date()
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hh = checkTime(date.getHours())
const mm = checkTime(date.getMinutes())
const ss = checkTime(date.getSeconds())
function checkTime(i: any) {
if (i < 10) {
return '0' + i
}
return i
}
time.value = year + '-' + month + '-' + day + ' ' + hh + ':' + mm + ':' + ss
}
nowTime()
const showQuickStart = ref(true)
const showNiuCloud = ref(true)
const showBanner = ref(true)
const hovering = ref('')
onMounted(() => {
showQuickStart.value = localStorage.getItem('showQuickStart') !== 'false'
showNiuCloud.value = localStorage.getItem('showNiuCloud') !== 'false'
showBanner.value = localStorage.getItem('showBanner') !== 'false'
// 定义异步初始化函数
const initData = async () => {
loading.value = true;
try {
await Promise.all([
getStatInfoFn(),
getAdvListFn(),
getAppIndexFn()
]);
loading.value = false;
} catch (error) {
console.error('初始化失败', error);
loading.value = false;
}
};
// 执行初始化函数
initData();
})
const closeModule = (module:any) => {
if (module === 'quickStart') {
showQuickStart.value = false
localStorage.setItem('showQuickStart', 'false')
} else if (module === 'niuCloud') {
showNiuCloud.value = false
localStorage.setItem('showNiuCloud', 'false')
} else if (module === 'banner') {
showBanner.value = false
localStorage.setItem('showBanner', 'false')
}
}
</script>
<style lang="scss" scoped>
.profile-data {
background-color: transparent !important;
}
:deep(.profile-data .el-card__header) {
padding: 0 !important;
border: none !important;
}
:deep(.profile-data .el-card__body) {
padding: 0 !important;
}
.top :deep(.el-col) {
max-width: calc(100% / 5) !important;
}
.first-con {
// border: 1px solid #E9ECEF;
// background: #fff;
padding: 20px 30px 10px;
// height: 80px;
background: #EDEEF4 !important;
border-radius: 6px !important;
border: none !important;
&:hover {
background: #dcdfe6 !important; // 你可以换成你想要的颜色
}
}
.echarts-con {
// border: 1px solid #E9ECEF;
// background: #fff;
padding-top: 20px;
border-radius: 6px !important;
}
.echarts-box{
border-radius: 6px !important;
}
.quick-action-card {
position: relative;
overflow: visible; /* 允许伪元素超出 */
z-index: 0;
}
/* 伪元素作为视觉外壳 */
.quick-action-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #EDEEF4;
border-radius: 8px;
z-index: -1;
transition: transform 0.3s ease;
}
/* 悬浮时放大背景,但不动内容 */
.quick-action-card:hover::before {
transform: scaleY(1.3);
transform-origin: center;
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
.desc{
opacity: 0;
transition: opacity 0.3s ease;
}
.quick-action-card:hover > .desc{
opacity: 1;
}
.quick-action-card > span:last-child {
opacity: 0;
transform: translate(-8px, 8px); /* 初始位置稍微向下右 */
transition: all 0.3s ease;
}
.quick-action-card:hover > span:last-child {
opacity: 1;
transform: translate(0, 0); /* 回到原位(实现右上浮动) */
}
.quick-action-card:hover > .desc,
.quick-action-card:hover > span:last-child {
opacity: 1;
}
.quick-action-card:hover > .title {
transform: translateY(-15px);
}
.title {
transition: transform 0.3s ease;
}
:deep(.banner-box .el-carousel__indicator){
padding: 2px !important;
}
:deep(.loading-box .el-loading-spinner){
top: 33%;
}
</style>

View File

@@ -0,0 +1,255 @@
<template>
<div class="main-container w-[375px] mx-auto mt-[20px] mb-[40px] relative">
<div class="flex full-container h-[800px]">
<iframe v-show="loadingIframe" class="w-[375px]" :src="wapPreview" frameborder="0" id="previewIframe"></iframe>
<div v-show="loadingDev" class="w-[375px] border border-slate-100 bg-body pt-[20px] px-[20px]">
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model.trim="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
</div>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</div>
<div class="w-[400px] absolute bg-body top-[10%] -right-[450px]" v-if="loadingIframe">
<div class="info-wrap mt-[20px]">
<div class="px-[20px] pb-[10px] font-bold">{{ t('h5') }}</div>
<el-form label-width="40px" class="px-[20px]">
<el-form-item :label="t('link')" v-show="wapPreview">
<el-input readonly :value="wapPreview">
<template #append>
<el-button @click="copyEvent(wapPreview)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label=" " v-show="wapImage">
<el-image :src="wapImage" />
</el-form-item>
</el-form>
<div class="px-[20px] pb-[10px] font-bold mt-[40px]">{{ t('weapp') }}</div>
<el-form label-width="40px" class="px-[20px]">
<el-form-item label=" " v-if="weappConfig.qr_code">
<el-image class="w-[150px] h-[150px]" :src="img(weappConfig.qr_code)" />
</el-form-item>
<el-form-item label=" " v-else>
<span class="text-gray-400">{{ t('weappNotSet') }}</span>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, inject } from 'vue'
import { t } from '@/lang'
import { useRoute } from 'vue-router'
import { getWeappConfig } from '@/app/api/weapp'
import { getUrl } from '@/app/api/sys'
import { useClipboard } from '@vueuse/core'
import { ElMessage } from 'element-plus'
import { img } from '@/utils/common'
import QRCode from 'qrcode'
import storage from '@/utils/storage'
const wapUrl = ref('')
const wapDomain = ref('')
const wapImage = ref('')
const wapPreview = ref('')
const loadingIframe = ref(false) // 加载iframe
const loadingDev = ref(false) // 加载开发环境配置
const timeIframe = ref(0) // iframe打开时间
const difference = ref(0) // 检测页面加载差异小于1000毫秒则配置wap端域名
const route = useRoute()
route.query.page = route.query.page || '' // 页面路径
const setLayout = inject('setLayout')
setLayout('decorate')
getUrl().then((res: any) => {
wapUrl.value = res.data.wap_url
let repeat = true // 防重复执行
// 开发模式下执行
if (import.meta.env.MODE == 'development') {
wapDomain.value = res.data.wap_domain
// env文件配置过wap域名
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
repeat = false
setDomain()
}
const wap_domain_storage = storage.get('wap_domain')
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
repeat = false
setDomain()
}
}
if (repeat) {
setDomain()
}
})
const save = () => {
if (wapDomain.value.trim().length == 0) {
ElMessage({
type: 'warning',
message: `${t('wapDomainPlaceholder')}`
})
return
}
wapUrl.value = wapDomain.value + '/wap'
setDomain()
storage.set({ key: 'wap_domain', data: wapUrl.value })
loadingIframe.value = true
loadingDev.value = false
}
const setDomain = () => {
if (route.query.page) {
wapPreview.value = `${wapUrl.value}${route.query.page}`
// errorCorrectionLevel密度容错率LH(高)
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
const send = () => {
timeIframe.value = new Date().getTime()
postMessage()
}
// 同步发送一次消息
send()
// 如果同步发送消息的 uni-app没有接收到回应则定时发送消息
let sendCount = 0
const timeInterVal = setInterval(() => {
// 接收 uni-app 发送的消息 或者 发送50次后未响应则停止发送
if (uniAppLoadStatus.value || sendCount >= 50) {
clearInterval(timeInterVal)
return
}
send()
sendCount++
}, 200)
// 如果10秒内加载不出来则需要配置域名
setTimeout(() => {
if (difference.value == 0) initLoad()
}, 1000 * 10)
}
}
const uniAppLoadStatus = ref(false) // uni-app 加载状态true加载完成false未完成
// 监听 uni-app 端 是否加载完成
window.addEventListener('message', (event) => {
try {
let data = {
type: ''
}
if (typeof event.data == 'string') {
data = JSON.parse(event.data)
} else if (typeof event.data == 'object') {
data = event.data
}
if (data.type && ['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
loadingDev.value = false
loadingIframe.value = true
const loadTime = new Date().getTime()
uniAppLoadStatus.value = true // 加载完成
difference.value = loadTime - timeIframe.value
}
} catch (e) {
initLoad()
console.log('preview 后台接受数据错误', e)
}
}, false)
// 将数据发送到uniapp
const postMessage = () => {
const data = JSON.stringify({
type: 'appOnReady',
message: '加载完成'
})
if (window.previewIframe) window.previewIframe.contentWindow.postMessage(data, '*')
}
// 初始化加载状态
const initLoad = () => {
loadingDev.value = true
loadingIframe.value = false
wapPreview.value = ''
wapImage.value = ''
}
const weappConfig = reactive({
qr_code: ''
})
// 获取微信配置
getWeappConfig().then((res: any) => {
if (res.code == 1) {
const data = res.data
weappConfig.qr_code = data.qr_code
}
})
// 复制
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
</script>
<style lang="scss">
body {
background: #edf0f3;
}
.main-container{
overflow: inherit !important;
border-radius: inherit;
background: inherit;
}
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped></style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,162 @@
<template>
<!--工具管理-->
<div class="main-container">
<el-card class="card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">工具管理</span>
</div>
<div class="flex flex-wrap mt-[20px]">
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/addon')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">插件开发</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">点击新建插件生成插件后系统会生成对应插</div>
</div>
<img src="@/app/assets/images/tools/addon_develop.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/code')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">代码生成</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">代码生成</div>
</div>
<img src="@/app/assets/images/tools/code.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/list')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">数据字典</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">数据字典</div>
</div>
<img src="@/app/assets/images/tools/sys_dict_list.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/update_cache')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">更新缓存</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">更新缓存</div>
</div>
<img src="@/app/assets/images/tools/tools_update_cache.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/detection')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">环境监测</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">环境监测</div>
</div>
<img src="@/app/assets/images/tools/tools_check_environment.png" class="w-[256px] h-[128px] cursor-pointer" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/schedule')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">计划任务</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">计划任务</div>
</div>
<img src="@/app/assets/images/tools/tools_schedule.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/authorize')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">授权信息</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">查看授权信息及重新认证授权</div>
</div>
<img src="@/app/assets/images/tools/app_auth.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/admin_menu')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">平台菜单</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">平台菜单</div>
</div>
<img src="@/app/assets/images/tools/official_market.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/site_menu')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">站点菜单</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">站点菜单</div>
</div>
<img src="@/app/assets/images/tools/official_market.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/upgrade')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">系统更新</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">系统更新</div>
</div>
<img src="@/app/assets/images/tools/tools_upgrade.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/upgrade_records')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">升级记录</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">升级记录</div>
</div>
<img src="@/app/assets/images/tools/tools_upgrade_records.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/backup_records')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">备份记录</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">备份记录</div>
</div>
<img src="@/app/assets/images/tools/tools_backup_records.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/cloud_compile')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">云编译</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">云编译</div>
</div>
<img src="@/app/assets/images/tools/tools_cloud_compile.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="developerDialogVisible = true">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">开发模式</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">开发环境和生产环境说明</div>
</div>
<img src="@/app/assets/images/tools/developer.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="goRouter">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">官方市场</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">官方市场</div>
</div>
<img src="@/app/assets/images/tools/official_market.png" class="w-[256px] h-[128px]" />
</div>
</div>
</el-card>
<el-dialog v-model="developerDialogVisible" class="developer-dialog-wrap" title="开发人员模式说明" width="30%">
<div>
<p class="text-[16px] mb-[4px]">开发模式</p>
<div class="text-[14px] indent-[2em]">开发人员模式即软件开发环境指框架开启了开发模式(DEBUG=TRUE) 开发模式时会出现开发选项卡仅用于开发人员使用包括应用及插件的安装卸载系统升级等等本菜单及子项功能均不受系统管理和权限控制</div>
<p class="text-[16px] mt-[15px] mb-[4px]">生产模式</p>
<div class="text-[14px] indent-[2em]">生产模式即软件生产环境指框架关闭了调试开发模式(DEBUG=FALSE) 软件正式部署运营开发选项卡隐藏一般来说开发人员开发或者部署好环境后要关闭开发模式</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="developerDialogVisible = false">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
const systemStore = useSystemStore()
systemStore.setHeadMenu('')
const router = useRouter()
const developerDialogVisible = ref(false)
const toLink = (link:any) => {
router.push(link)
}
const goRouter = () => {
window.open('https://www.niucloud.com/app')
}
</script>
<style lang="scss" scoped>
.tools-item-shadow {
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.2);
}
img {
opacity: .8;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div class="flex w-screen h-screen flex-col items-center justify-center" v-loading="loading">
<template v-if="!loading">
<template v-if="authResult.result == 'success'">
<icon name="element CircleCheckFilled" size="50px" color="#0eb118"/>
<h3 class="text-xl mt-[20px]">授权绑定成功</h3>
</template>
<template v-else>
<icon name="element CircleCloseFilled" size="50px" color="#e70000"/>
<h3 class="text-xl mt-[20px]">抱歉授权绑定失败</h3>
</template>
<p class="text-secondary mt-[5px]" v-if="authResult.result == 'fail'">
{{ authResult.msg }}
<el-button type="primary" link @click="authBindWechat">重新授权</el-button>
</p>
<el-button type="primary" class="mt-[20px] w-[150px]" @click="confirm">确认</el-button>
</template>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { getAuthorizationUrl, getAuthorizationResult } from '@/app/api/wxoplatform'
const route = useRoute()
const loading = ref(true)
const authResult = ref({
result: 'fail',
msg: ''
})
getAuthorizationResult(route.query).then(({ data }) => {
authResult.value.result = 'success'
loading.value = false
}).catch((e) => {
authResult.value.msg = e.toString()
loading.value = false
})
const authBindWechat = () => {
getAuthorizationUrl().then(({ data }) => {
location.href = data
})
}
const confirm = () => {
window.close()
}
</script>
<style lang="scss" scoped>
</style>