first commit

This commit is contained in:
编码猿
2024-09-27 01:23:51 +08:00
commit 72ab70b6fd
212 changed files with 30296 additions and 0 deletions

1
src/.pydio Normal file
View File

@@ -0,0 +1 @@
e8bd96a5-c939-4b6a-a198-0353177e7e79

12
src/App.ts Normal file
View File

@@ -0,0 +1,12 @@
require('module-alias/register')
import { app } from "electron";
import { Run } from "@run/Init.run";
app.on('ready', () => {
new Run()
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
});

1
src/application/.pydio Normal file
View File

@@ -0,0 +1 @@
9af67781-5f56-4703-8a0f-36bf92325403

View File

@@ -0,0 +1 @@
e161dad5-8741-4503-ab79-a3680cb3acd1

View File

@@ -0,0 +1 @@
5870d722-0824-4515-9172-8397f1276c1e

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

View File

@@ -0,0 +1 @@
48ab38da-fa2d-4cf5-bcc5-075c76854ef8

View File

@@ -0,0 +1 @@
ece44459-a089-49b6-8666-6ba7b161a6e0

View File

@@ -0,0 +1,62 @@
class Utils {
/**
* 发送GET 请求
* @param URL 地址
* @param params 参数
* @param callback 回调
* @constructor
*/
Http(URL, params,callback) {
$.ajax({
url: `http://www.bmycode.com:3000/api${URL}`,
type: 'GET',
data: params,
success: (res)=> {
callback(res)
}
})
}
/**
* 根据时间戳返回多久之前的时间
* @param timespan 时间戳
* @returns {string}
*/
formatTime (timespan) {
var dateTime = new Date(timespan);
var year = dateTime.getFullYear();
var month = dateTime.getMonth() + 1;
var day = dateTime.getDate();
var hour = dateTime.getHours();
var minute = dateTime.getMinutes();
var second = dateTime.getSeconds();
var now = new Date();
var now_new = Date.parse(now.toDateString()); //typescript转换写法
var milliseconds = 0;
var timeSpanStr;
milliseconds = now_new - timespan;
if (milliseconds <= 1000 * 60 * 1) {
timeSpanStr = '刚刚';
}
else if (1000 * 60 * 1 < milliseconds && milliseconds <= 1000 * 60 * 60) {
timeSpanStr = Math.round((milliseconds / (1000 * 60))) + '分钟前';
}
else if (1000 * 60 * 60 * 1 < milliseconds && milliseconds <= 1000 * 60 * 60 * 24) {
timeSpanStr = Math.round(milliseconds / (1000 * 60 * 60)) + '小时前';
}
else if (1000 * 60 * 60 * 24 < milliseconds && milliseconds <= 1000 * 60 * 60 * 24 * 15) {
timeSpanStr = Math.round(milliseconds / (1000 * 60 * 60 * 24)) + '天前';
}
else if (milliseconds > 1000 * 60 * 60 * 24 * 15 && year == now.getFullYear()) {
timeSpanStr = month + '月' + day + '号' + hour + ':' + minute;
} else {
timeSpanStr = year + '年' + month + '月' + day + '号' + hour + ':' + minute;
}
return timeSpanStr;
};
}
window.$Utils = new Utils()

10872
src/application/assets/js/lib/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
c5132154-4147-4be9-89d3-dda9ca159cd6

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
780209c9-bb5e-4d40-a8b6-a2bd47c2eab3

View File

@@ -0,0 +1,108 @@
const { ipcRenderer } = require('electron');
class Home {
constructor() {
this.playVideo()
this.GotoList()
this.page = $(".video_list").attr("page")
this.loadMore()
}
/**
* 点击播放视频发送ipc通知主进程创建独立的 PlayVideo 窗口
* 然后向 PlayVideo 方法传入 datat 参数
*/
playVideo() {
$(".abbreviation_f").off("click").on('click',function (e) {
if ($(this).attr("workType") == "video") {
ipcRenderer.send('openWindow', {
action: 'Home.controller/PlayVideo',
data: {
principalId: $(this).attr("principalId"),
photoId: $(this).attr("photoId")
}
});
} else {
ipcRenderer.send('openWindow', {
action: 'Home.controller/PlayVideo',
data: {
caption: $(this).next().text(),
list: JSON.parse( $(this).find(".imgUrls").text() )
}
});
}
return false;
});
}
/**
* 点击使用默认浏览器打开主播主页
* @constructor
*/
GotoList() {
$(".info").off("click").on('click',function (e) {
ipcRenderer.send('openExternal', {
url: `https://live.kuaishou.com/profile/${$(this).prev().attr("principalId")}`
})
})
}
/**
* 滚动加载更多数据
*/
loadMore() {
let self = this;
$(window).scroll(function () {
let scrollTop = $(this).scrollTop();
let scrollHeight = $(document).height();
let windowHeight = $(this).height();
if (scrollTop + windowHeight == scrollHeight) {
self.getVideoList()
}
});
}
/**
* 滚动到底部后发送请求获取数据
*/
getVideoList() {
$Utils.Http("/list", { page: this.page }, res=> {
let MMList = res.data;
// 更新页面上的page页码
$(".video_list").attr({ page: MMList.pcursor })
// 保存页面到变量
this.page = $(".video_list").attr("page")
// 遍历新数据到页面
MMList.list.forEach(function (val,index) {
$(".video_list").append(`
<li>
<div class="abbreviation_f" principalId="${val.user.id}" photoId="${val.id}" workType="${val.workType}">
<div style="background:url('${val.thumbnailUrl}');background-position: center center;" class="zz"></div>
<img src="${val.thumbnailUrl}" class="thumbnailUrl" />
<div class="${val.workType == 'video' ? 'tips red':'tips green'}">
${val.workType == 'video' ? '视频':'图片'}
${
val.imgUrls.length != 0
? `<div class="imgUrls" style="display:none">${JSON.stringify(val.imgUrls)}</div>`
: ''
}
</div>
</div>
<div class="info">
<img src="${val.user.avatar}" />
<p>${val.caption}</p>
</div>
</li>
`);
});
this.playVideo()
this.GotoList()
// 因为页面数据变多了所以为所有的dom重新监听点击播放进入主页事件
})
}
}
new Home();

View File

@@ -0,0 +1,84 @@
const { ipcRenderer } = require('electron');
class PlayVideo {
constructor() {
this.isShowMenu = false
this.downVideo()
this.initSwiper()
this.ShowComment()
this.ShowCommentList()
this.SwiperIndex = 0;
}
initSwiper() {
let self = this;
new Swiper ('.swiper-container', {
direction: 'horizontal', // 垂直切换选项
loop: false, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: {
slideChange: function () {
self.SwiperIndex = this.realIndex;
},
}
})
}
downVideo() {
let self = this;
$(".down_video").click(function () {
// 当前播放的是图片
if ($("#my-video").length == 0) {
let allImg = $(".swiper-wrapper .swiper-slide img").eq(self.SwiperIndex)
ipcRenderer.send('SaveFile', {
url: allImg.attr("src")
})
// 当前播放的是视频
} else {
console.log("下载视频")
ipcRenderer.send('SaveFile', {
url: $("#my-video").attr("src")
})
}
})
}
/**
* 展开评论弹窗
* @constructor
*/
ShowComment() {
$(".down_comment").click(() => {
if (this.isShowMenu) {
$(".comment_list").animate({ right: '-256px'})
this.isShowMenu = false
} else {
$(".comment_list").animate({ right: 0})
this.isShowMenu = true
}
})
}
/**
* 查看更多评论
* @constructor
*/
ShowCommentList() {
$(".view_other").click(function () {
$(this).next().toggle({
'display': 'block'
})
})
}
}
new PlayVideo();

View File

@@ -0,0 +1 @@
577aeb56-0023-42f0-b7e8-2d595cbabaa3

View File

@@ -0,0 +1,73 @@
.Home{
ul {
display: flex;
flex-wrap: wrap;
li {
margin-left: 15px;
width: 15%;
margin-bottom: 18px;
&:hover {
}
.abbreviation_f {
width: 100%;
height: 178px;
position: relative;
text-align: center;
overflow: hidden;
.zz {
position: absolute;
width: 100%;
height: 100%;
z-index: 2;
background: #000;
filter: blur(10px);
}
.thumbnailUrl {
width: 103px;
height: 178px;
position: absolute;
z-index: 3;
left: 50%;
margin-left: -51.5px;
}
.tips {
position: absolute;
z-index: 99;
color: #fff;
padding: 2px 5px;
right: 0;
bottom: 0;
font-size: 13px;
}
.red {
background: red;
}
.green {
background: green;
}
}
.info {
display: flex;
width: 100%;
justify-content: flex-start;
align-items: center;
img {
width: 25px;
height: 25px;
border-radius: 100%;
}
p {
font-size: 13px;
width: 93%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-left: 10px;
color: #bababa;
}
}
}
}
}

View File

@@ -0,0 +1,128 @@
html, body, .PlayVideo, #my-video{
background: #000;
width: 100%;
height: 100%;
outline: none !important;
}
.swiper-container {
width: 100%;
height: 100%;
.swiper-slide {
text-align: center;
}
img {
height: 100%;
}
}
.PlayVideo {
position: fixed;
.title {
width: 100%;
height: 45px;
text-align: center;
line-height: 45px;
color: #fff;
position: fixed;
z-index: 99;
i {
position: absolute;
right: 20px;
font-size: 25px;
cursor: pointer;
&:before {
cursor: pointer;
}
}
.down_video {
right: 60px;
}
.down_comment {
cursor: pointer;
right: 20px;
top: 2px;
font-size: 22px;
}
}
.comment_list {
position: absolute;
right: -256px;
z-index: 90;
width: 256px;
background: #202020;
height: calc(100% - 32px);
top: 0;
overflow-y: scroll;
padding-top: 32px;
.showComment {
display: none;
.sub_comment {
padding: 0 !important;
margin-top: 15px;
border-bottom: none !important;
img {
width: 24px !important;
height: 24px !important;
}
.list_item {
margin-left: 8px !important;
}
.item_content {
font-size: 13px !important;
}
}
}
.item_comment {
display: flex;
justify-content: space-between;
padding: 15px;
box-sizing: border-box;
border-bottom: 1px solid #303030;
&:hover {
background: #2d2b2b;
cursor: pointer;
}
img {
width: 30px;
height: 30px;
border-radius: 100%;
}
.list_item {
margin-left: 13px;
width: 100%;
.item_title {
margin-top: 6px;
color: #576b95;
margin-bottom: 10px;
}
.item_content{
color: #909090;
font-size: 14px;
margin-bottom: 8px;
}
.icon-like {
color: #606060;
font-size: 13px;
.numbe {
margin-left: 4px;
}
.view_other {
margin-left: 12px;
span {
}
.icon-cc-down {
margin-left: 3px;
}
span,
.icon-cc-down {
color: #909090;
font-size: 12px;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
html,body{
width: 100%;
height: 100%;
background: #fff;
-webkit-app-region: drag
}
ul,li {
padding: 0;
margin: 0;
list-style: none;
}
.content {
float: left;
width: calc(100% - 200px);
margin-left: 200px;
margin-top: 50px;
}
a {
text-decoration: none;
}

View File

@@ -0,0 +1,46 @@
.action {
width: 100%;
height: 50px;
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 999;
.left_action {
width: 200px;
height: 100%;
background: #f6f6f6;
float: left;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 6px;
box-sizing: border-box;
.icon-houtui {
margin-right: 15px;
}
}
.right_menu {
width: calc(100% - 200px);
height: 100%;
background: #f9f9f9;
float: left;
display: flex;
align-items: center;
justify-content: space-between;
.menu_text {
font-weight: 500;
font-size: 15px;
margin-left: 32px;
}
.menu_action {
i {
margin-right: 20px;
font-size: 22px;
}
.icon-yifu {
font-size: 20px;
}
}
}
}

View File

@@ -0,0 +1,32 @@
.nav_menu {
width: 200px;
height: calc(100% - 50px);
background: #ededed;
float: left;
position: fixed;
left: 0;
top: 50px;
.title {
margin-left: 20px;
color: #888888;
margin-bottom: 7px;
padding-top: 20px;
font-size: 14px;
}
.item_menu {
height: 40px;
line-height: 40px;
padding-left: 20px;
cursor: pointer;
&:hover {
background: #e1e1e1;
}
.icon-ziyuan {
font-size: 15px;
}
span {
color: #2f2f2f;
margin-left: 9px;
}
}
}

View File

@@ -0,0 +1 @@
13877692-fec3-4c9a-a8d9-56ee0f825f6e

View File

@@ -0,0 +1 @@
h2 底部

View File

@@ -0,0 +1,10 @@
.action
.left_action
i(class="iconfont icon-houtui")
i(class="iconfont icon-qianjin")
.right_menu
.menu_text iTunes 音乐
.menu_action
i(class="iconfont icon-iconzhengli_youjian")
i(class="iconfont icon-yifu")
i(class="iconfont icon-shezhi")

View File

@@ -0,0 +1,39 @@
.nav_menu
.title 我的音乐
.item_menu
i(class="iconfont icon-yinle")
span iTunes音乐
.item_menu
i(class="iconfont icon-changyongicon-")
span 下载管理
.item_menu
i(class="iconfont icon-yun")
span 我的音乐盘
.item_menu
i(class="iconfont icon-shoucang")
span 我的收藏
.title 我的云盘
.item_menu
i(class="iconfont icon-tupian")
span 精彩照片
.item_menu
i(class="iconfont icon-shipin1")
span 视频记忆
.item_menu
i(class="iconfont icon-wenjianjia1")
span 重要文件
.item_menu
i(class="iconfont icon-qita")
span 其他资料
.title 在线娱乐
.item_menu
i(class="iconfont icon-ziyuan")
span 短视频
.item_menu
i(class="iconfont icon-zhibo")
span 娱乐直播
.item_menu
i(class="iconfont icon-xinwen")
span 新闻资讯

View File

@@ -0,0 +1 @@
e26a9949-8bf3-45a8-a658-213dec0a0a26

View File

@@ -0,0 +1,30 @@
doctype html
html
head
meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible" content="IE=edge")
meta(name="viewport" content="width=device-width, initial-scale=1")
meta(name="renderer" content="webkit")
meta(http-equiv="Cache-Control" content="no-siteapp")
link(rel='stylesheet', href='../../assets/css/header.css')
link(rel='stylesheet', href='../../assets/css/nav.css')
script(src="../../assets/js/lib/http.js")
// - 从 Index.config.ts 中渲染公共 css 和 js
each val,index in globals.css
link(rel='stylesheet', href=`${val}`)
each val,index in globals.js
script(src=`${val}`)
script.
if (typeof module === 'object') {
window.jQuery = window.$ = module.exports;
};
block title
body
- if (header)
include ../components/header
- if (nav)
include ../components/nav
block content
block script

View File

@@ -0,0 +1 @@
eac2e5cf-4383-4f4b-b53b-fd2f393a49ac

View File

@@ -0,0 +1 @@
4a111cfc-b7cf-40d4-929b-b16936294b4b

View File

@@ -0,0 +1,28 @@
extends ../../layout/index
block title
title #{title}
link(rel='stylesheet', href='../../assets/css/clear.css')
link(rel='stylesheet', href='../../assets/css/Home.css')
//- 网站的主体内容区域
block content
.content
.Home
ul.video_list(page="#{list.pcursor}")
each val,index in list.list
li
.abbreviation_f(principalId="#{val.user.id}" photoId="#{val.id}" workType="#{val.workType}")
.zz(style="background: url(#{val.thumbnailUrl});background-position: center center;")
img(class="thumbnailUrl" src="#{val.thumbnailUrl}")
div(class="#{val.workType == 'video' ? 'tips red':'tips green'}")
| #{val.workType == 'video' ? '视频':'图片'}
- if (val.imgUrls.length != 0)
div(class="imgUrls" style="display:none") #{JSON.stringify(val.imgUrls)}
.info
img(src="#{val.user.avatar}")
p #{val.caption}
block script
script(src="../../assets/js/page/Home.js")

View File

@@ -0,0 +1 @@
287a9417-e25d-47c0-a1e6-77e2cd2fc62b

View File

@@ -0,0 +1,54 @@
extends ../../layout/index
block title
title 播放页面
link(rel='stylesheet', href='../../assets/css/clear.css')
link(rel='stylesheet', href='../../assets/css/PlayVideo.css')
link(rel='stylesheet', href='https://cdn.bootcdn.net/ajax/libs/Swiper/5.4.5/css/swiper.min.css')
//- 网站的主体内容区域
block content
.PlayVideo
.title
span #{video.caption}
i(class="iconfont down_video icon-changyongicon-")
i(class="iconfont down_comment icon-comment")
.comment_list
each val,index in Comment.commentList
.item_comment
img(src="#{val.headurl}")
.list_item
.item_title #{val.authorName}
.item_content #{val.content}
i(class="iconfont icon-like")
span.numbe #{val.likedCount}
span.view_other
- if (val.subComments.length != 0)
span 查看#{val.subCommentCount}条评论
i(class="iconfont icon-cc-down")
.showComment
each v,i in val.subComments
.item_comment.sub_comment
img(src="#{v.headurl}")
.list_item
.item_title #{v.authorName}
.item_content #{v.content}
- if (img == true)
.swiper-container
.swiper-wrapper
- each val,index in video.list
.swiper-slide
img(src="#{val}")
.swiper-pagination
.swiper-button-prev
.swiper-button-next
- else
video#my-video(controlsList="nodownload" loop controls autoplay
poster='#{video.poster}'
src="#{video.playUrl}")
block script
script(src="https://cdn.bootcdn.net/ajax/libs/Swiper/5.4.5/js/swiper.min.js")
script(src="../../assets/js/page/PlayVideo.js")

1
src/core/.pydio Normal file
View File

@@ -0,0 +1 @@
85ed9a37-2367-442e-ad9b-0524edf9f844

View File

@@ -0,0 +1 @@
d087137f-1bfb-4d06-96ef-473d70be7e66

View File

@@ -0,0 +1,63 @@
import Config from "@config/Index.config";
import Utils from "@utils/Index.utils";
import {Http} from "@net/Http.net";
/**
* @Ipc()
* 实现居中IPC的注册用在controller方法上
* @param IpcParams 传入需要被注册的ipc类
* @constructor
*/
export function Ipc(IpcParams: { new (...args: any[]): {}; }[] ) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
/**
* 因为TS没有提供像java那种可以直接提取某个类中所有方法的系统方法
* 这边原本使用 Object.getOwnPropertyNames 能够正常提取类中方法,但是如果类中有被 @Inject() 依赖注入的属性
* 那么这个属性也会被 (new IpcParams())[val](),这样是肯定报错的,所以做下面约定!
* 在IPC 类中,如果在类的属性上使用 @Inject() 注入。那么请在类的属性名前加上 _例如:
* @Inject()
* private readonly _Net!: Http;
*/
IpcParams.forEach((val: { new (): any; } ) => {
Object.getOwnPropertyNames(val.prototype).splice(1)
.filter(f => !f.includes("_"))
.forEach(v => (new val())[v]())
})
}
}
/**
* @CreateApplicationIpc()
* 实现全局IPC的注册
* 能够自动实例化传入的类数组中的所有类,并自动调用类中的所有的方法
* @param IpcParams 类数组,实例:[ classIpc1, classIpc2, classIpc3 ]
* @constructor
*/
export function CreateApplicationIpc(IpcParams: { new (...args: any[]): {}; }[]): any {
return (_constructor: { new (...args: any[]): {}; } ) => {
IpcParams.forEach((val: { new (): any; } ) => {
Object.getOwnPropertyNames(val.prototype).splice(1)
.filter(f => !f.includes("_"))
.forEach(v => (new val())[v]())
})
}
}
/**
* @Render()
* 创建窗口
* @param templateName 要渲染的模板名称
* @constructor
*/
export function Render(templateName?: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
/**
* 如果被打上注解的类中某个方法是配置文件中的默认启动窗口,那么调用这个方法来创建窗体
* 其他的方法一律不管由js前端渲染进程触发ipc创建
*/
if (propertyKey.includes((Config.StartPage.split("/"))[1])) {
// 如果没有传参数,那么采用方法名来创建窗体!
templateName == undefined ? Utils.startWindows(target,propertyKey) : Utils.startWindows(target,templateName)
}
}
}

View File

@@ -0,0 +1,40 @@
import Config from "@config/Index.config";
import {Http} from "@net/Http.net";
/**
* @GET()
* 用在service类的方法上。
* 如果不传参数将自动根据方法名获取接口请求的地址
* 如果传参了,那么使用用户传入的地址去发送网络请求
*
* 如果 useHandle 为 true 那么方法将获得ajax请求的数据
* 否则 不会将数据给方法
* @param RequestParams
* @constructor
*/
export function GET (RequestParams?: { url?: string, useHandle?: boolean, header?: { [index: string]: any } }) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let url: string = Config.ApiUrl.ApiList[propertyKey],
handle: boolean = false,
header: object = {};
(RequestParams && RequestParams.url)
? url = RequestParams.url
: (RequestParams && RequestParams.useHandle)
? handle = RequestParams.useHandle
: (RequestParams && RequestParams.header)
? header = RequestParams.header
: '';
let OldMethods = descriptor.value;
if (handle) {
descriptor.value = async (data: { [key:string]: any }) =>
await OldMethods.apply(target, [ await (new Http).GET({ url: url, data: data, header: header }) ])
return descriptor
} else {
descriptor.value = async (data: { [key:string]: any }) =>
await (new Http).GET({ url: url, data: data, header: header })
return descriptor
}
}
}

View File

@@ -0,0 +1,39 @@
import 'reflect-metadata';
import IocModel from "@model/Ioc.model";
/**
* 收集类依赖
* @constructor
*/
export function Injectable() {
return (_constructor: { new (...args: any[]): {}; }) => {
if(IocModel.classPool.indexOf(_constructor) !== -1) {
throw new Error('无需重复收集类');
} else {
//注册
IocModel.classPool = [_constructor]
}
}
}
/**
* 将类依赖实例化然后注入到被装饰的属性中
* @constructor
*/
export function Inject() {
return function (target: any, propertyName: string) {
/**
* 使用 reflect-metadata 提供的内置 类型元数据键 design:type 通过反射拿到被装饰属性的类型
* 也就是 类属性要实例化 的 service 类
*/
const propertyType: any = Reflect.getMetadata('design:type', target, propertyName);
console.log("类属性要实例化 propertyType: ", propertyType)
if (IocModel.classPool.indexOf(propertyType) == -1) {
throw new Error('被装饰的属性所属的变量类型类,没有被装饰器@Injectable()注入,请检查!');
} else {
// 从存取器的数组中通过下标取出被装饰属性对应的service类然后实例化这个类在放入被装饰的属性中
target[propertyName] = new (IocModel.classPool[IocModel.classPool.indexOf(propertyType)])()
}
}
}

View File

@@ -0,0 +1,80 @@
import Utils from "@utils/Index.utils";
import {ApplicationMenu} from "@interactive/ApplicationMenu.interactive";
import {Touchbar} from "@interactive/Touchbar.interactive";
import {TrayInteractive} from "@interactive/Tray.interactive";
import { Application } from "@ipc/Application.ipc";
export function AutoLoadWindow(): any {
return (_constructor: {new(...args:any[]):{}} ) => {
return class extends _constructor {
constructor() {
// 这里只要动态 require 导入类即可,然后类上的装饰器就会运行
Utils.GetController()
super();
}
}
}
}
export function CreateApplicationMenu(): any {
return (_constructor: {new(...args:any[]):{}}) => {
return class extends _constructor {
constructor() {
// 创建顶部菜单
new ApplicationMenu()
super();
}
}
}
}
export function CreateTouchbar(): any {
return (_constructor: {new(...args:any[]):{}}) => {
return class extends _constructor {
constructor() {
/**
* 创建Touchbar
* 这边做延时300毫秒的原因解释起来有点复杂
* 1因为我设计了 @Inject() 和 @Injectable() 注解,而在 controller 中
* 被注解的属性 VideoImplService 存放的是 请求中间层而请求中间层是调用的Http请求方法GET而这个方法
* 为了拿到返回值所以设计成async异步的这就导致使用请求中间层的controller方法也是async。
* 2而使用的方法如果是 async 那么就会导致 注解 @Render() 内部 也就是 startWindows 方法内,在自动调用方法
* 获取传递给模板的方法返回数值的时候必须 await也就变成了await target[name]()。
* 而这个注解 @CreateTouchbar() 运行时间 和 注解 @AutoLoadWindow() 基本同时间跑,
* 所以会导致 @CreateTouchbar() 运行的时候太快了,尼玛币 await target[name]() 还没跑完,所以导致
* 存取器里面Windows.CurrentBrowserWindow 没有数值,所以 @CreateTouchbar() 所依赖的 Windows.CurrentBrowserWindow
* 为空所以touchbar菜单无法 setTouchBar 上。
* 3这里等300毫秒是等 await target[name]() 完成,窗口也创建完成,然后 Windows.CurrentBrowserWindow
* 再去创建 touchbar菜单。
* 好了,说完了....同学不要睡了!
*/
setTimeout(()=> new Touchbar(),3000)
super();
}
}
}
}
export function CreateTray(): any {
return (_constructor: {new(...args:any[]):{}}) => {
return class extends _constructor {
constructor() {
// 创建Mac系统顶部图标
new TrayInteractive()
super();
}
}
}
}
// export function CreateApplicationIpc(): any {
// return (_constructor: {new(...args:any[]):{}}) => {
// return class extends _constructor {
// constructor() {
// // 开启应用级ipc监听
// new Application()
// super();
// }
// }
// }
// }

1
src/core/config/.pydio Normal file
View File

@@ -0,0 +1 @@
b9f49d6d-a018-4c87-8f77-433ade2c60aa

View File

@@ -0,0 +1,226 @@
import { PagePath, PageSize } from "@type/PageConfig.types";
import { TrayConfig } from "@type/Tray.types";
import { ApiUrlConfig } from "@type/ApiConfig";
import { app, Menu, shell, MenuItemConstructorOptions, MenuItem, TouchBarConstructorOptions, TouchBar } from "electron";
import Windows from "@/core/model/Windows.model";
const { TouchBarLabel, TouchBarButton,TouchBarSpacer } = TouchBar;
import { join } from "path";
import { JadeOptions } from "jade";
export default class Config {
/**
* 启动页面默认为 Home.controller/Home
* 程序内部根据这个配置自动载入Home.controller.ts文件并自动调用类里面的Home()方法实现窗口创建
* 可以改成其他,例如 PlayVideo.controller/Theme
*/
public static StartPage: string = 'Home.controller/Home';
// 应用版本号
public static CurrentAppVersion: String = '0.0.1';
// 是否为Mac系统
public static isMac: Boolean = process.platform === 'darwin';
public static LogsPath: string = "../../../logs/";
// ajax 请求地址
public static ApiUrl: ApiUrlConfig = {
BaseUrl: 'http://www.bmycode.com:3000/api',
ApiList: {
PlayList: '/list', // 获取视频列表
PalyVideo: '/play', // 获取视频播放地址
VideoComment: '/CommentList', // 获取视频评论
}
};
// jade 模板引擎配置,更多参数自行阅读声明文件
public static jadeCompile0ptions: JadeOptions = {
pretty: true, // 编译输出后是否保留源码格式true 保持
globals: {
css: [
'http://mdui-aliyun.cdn.w3cbus.com/source/dist/css/mdui.min.css',
'http://at.alicdn.com/t/font_1934749_hhc110df87a.css',
],
js: [
'http://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js',
'http://mdui-aliyun.cdn.w3cbus.com/source/dist/js/mdui.min.js'
]
}
};
// 应用程序的页面地址,地址为打包后的相对路径
public static PagePath: PagePath = {
Home: join(__dirname, '../../application/page/Home/Home.html'),
PlayVideo: join(__dirname, '../../application/page/PlayVideo/PlayVideo.html'),
};
// 所有页面窗体配置
public static PageSize: PageSize = {
Home: {
width: 1200,
height: 760,
frame: false,
backgroundColor: '#fff',
titleBarStyle: 'hiddenInset',
transparent: true,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
webviewTag: true,
}
},
PlayVideo: {
width: 1084,
height: 610,
frame: false,
backgroundColor: '#000',
titleBarStyle: 'hidden',
modal: true,
show: false,
resizable: true,
transparent: true,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
webviewTag: true
}
}
};
// Mac系统顶部全局菜单 右边图标
public static TrayConfig: TrayConfig = {
TopMenuRightImage: join(__dirname, '../../application/assets/img/pug.png'),
TopMenuRightDropdown:[
{
label: '显示主窗口',
click: ()=> {
Windows.CurrentBrowserWindow.show();
}
},
{
icon: join(__dirname, '../../application/assets/img/pug.png'),
label: '下拉菜单测试',
type: 'checkbox',
checked: true,
click: (menuItem: any, browserWindow: any)=> {
console.log("menuItem: ", menuItem);
}
},
{
label: '菜单',
submenu: [
{
label: '子菜单1'
},
{
label: '子菜单2'
}
],
},
{
role: 'quit',
label: '退出'
},
],
TopMenuRightTips: '测试提醒'
};
// Mac or win 系统顶部全局菜单
public static TemplateMenu: Array<(MenuItemConstructorOptions) | (MenuItem)>= [
{
label: app.name,
submenu: [
{ label: `关于 ${app.name}`, role: 'about' },
{ type: 'separator' },
{ label: '服务', role: 'services' },
{ type: 'separator' },
{ label: `隐藏 ${app.name}`, role: 'hide' },
{ role: 'hideOthers' },
{ label: '隐藏其他', role: 'unhide' },
{ type: 'separator' },
{ label: `退出${app.name}`, role: 'quit' }
]
},
{
label: '文件',
},
{
label: '编辑',
submenu: [
{ label: '撤销', role: 'undo' },
{ label: '恢复', role: 'redo' },
{type: 'separator' },
{ label: '剪切', role: 'cut' },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' },
{type: 'separator' },
{ label: '粘贴保留样式', role: 'pasteAndMatchStyle' },
{ label: '删除', role: 'delete' },
{ label: '全选', role: 'selectAll' },
{ type: 'separator' },
{
label: '听写',
submenu: [
{ label: '开始听写', role: 'startSpeaking' },
{ label: '停止听写', role: 'stopSpeaking' }
]
}
]
},
{
label: '视图',
submenu: [
{ label: '刷新', role: 'reload' },
{ label: '重置', role: 'resetZoom' },
{ label: '放大', role: 'zoomIn' },
{ label: '缩小', role: 'zoomOut' },
{ type: 'separator' },
{ label: '全屏', role: 'togglefullscreen' },
{ label: '切换开发人员工具', role: 'toggleDevTools' },
]
},
{
label: '窗口',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '最大化',role: 'zoom' },
{ label: '关闭', role: 'close' }
]
},
{
label: '帮助',
role: 'help',
submenu: [
{
label: '了解更多',
click: async () => {
await shell.openExternal('https://electronjs.org')
}
},
{
label: 'GitHub主页',
click: async () => {
}
}
]
}
];
// Mac touchbar 菜单
public static TouchBarConfig: TouchBarConstructorOptions = {
items: [
new TouchBarSpacer({ size: 'small' }),
new TouchBarButton({
label: '测试1',
backgroundColor: '#3a3a3c',
click: () => {}
}),
new TouchBarSpacer({ size: 'small' }),
new TouchBarButton({
label: '测试2',
backgroundColor: '#3a3a3c',
click: () => {}
}),
]
};
}

View File

@@ -0,0 +1 @@
1352edee-3a21-42cf-bf29-5ca3b2846b11

View File

@@ -0,0 +1,47 @@
import { Render, Ipc } from "@annotation/Creted.annotation";
import { VideoImplService } from "@service/impl/Video.impl.service";
import { Inject } from "@annotation/Ioc.annotation";
import { FileIpc } from "@ipc/module/File.ipc";
export class HomeController {
@Inject()
public readonly VideoImplService!: VideoImplService;
@Render()
public async Home(test: string) {
let res = await this.VideoImplService.PlayList({});
return {
header: true,
nav: true,
title: '首页页面窗口-测试传给模板的数据',
desc: `点我发送ipc打开窗口代码实现分别在src/application/assets/js/page/Home.js
和 src/core/ipc/Application.ipc.ts 和 @CreateApplicationIpc 装饰器中!!!`,
list: res.data
};
}
@Render()
@Ipc([ FileIpc ])
// 注意 PlayVideo() 方法将被前端发起的全局ipc自动调用ipc内部会根据这个方法名去自动创建同名的 视频播放窗口,
// 并IPC会自动动调用这个 PlayVideo() 方法,并把前端的参数向传入方法的 params方法内部无脑的去用即可
public async PlayVideo(params?: { [key:string]:any } ) {
console.log("参数:", params)
if ((params as object).hasOwnProperty("list")) {
return { // 用户点击图片组
header: false,
nav: false,
img: true,
video: params
}
} else {
return { // 用户点击视频
header: false,
nav: false,
img: false,
video: await this.VideoImplService.PalyVideo(params),
Comment: await this.VideoImplService.VideoComment({ photoId: (params as any).photoId })
}
}
}
}

View File

@@ -0,0 +1,27 @@
import { BaseControllerTypes } from "@type/BaseController.types";
import { Render, Ipc } from "@annotation/Creted.annotation";
import {FileIpc} from "@ipc/module/File.ipc";
/**
* 首页窗体
* @Render() 注解:
* 用在类的方法上,注意方法名 和 页面模板名称一致。
* 作用:表示使用该方法创建页面窗口,方法返回的数据需要是个对象,返回的数据会传给模板
* 用法:
* 1@Render()不传参数会根据方法名自动载入对应的jade页面文件来创建窗口
* 2@Render('PlayVideo')传入参数会根据传入的参数去寻找对应的jade页面来创建窗口
* 注意:传入参数后不仅会使用自定义页面,还会抛弃配置文件中的 StartPage 主窗窗口配置
*
* @Ipc() 注解:
* 用在类的方法上,接受一个未实例化的类作为参数
* 作用Ipc注解会自动运行这个类里面的所有方法请在方法中放置 ipcMain.on 代码!
*/
export class SettingController {
@Render()
public Setting() {
return {
title: '设置页面窗口-测试传给模板的数据',
desc: '介绍'
};
}
}

View File

@@ -0,0 +1 @@
ca1b2ba3-e8cd-4c29-9ca1-f27abf9f8252

View File

@@ -0,0 +1,16 @@
import {app, Menu, shell, MenuItemConstructorOptions, MenuItem} from "electron";
import Config from "@config/Index.config";
export class ApplicationMenu {
private AppMenu!: Menu;
constructor() {
this.buildFromTemplate();
}
buildFromTemplate(): void {
this.AppMenu = Menu.buildFromTemplate(Config.TemplateMenu);
Menu.setApplicationMenu(this.AppMenu);
}
}

View File

@@ -0,0 +1,33 @@
import { dialog, Notification } from "electron";
import Windows from "@model/Windows.model"
export class Dialog {
/**
* 错误弹窗
* @param title
* @param content
* @constructor
*/
public static async ErrorBox(title: string, content: string): Promise<void> {
await dialog.showErrorBox(title, content)
}
public static async showSaveDialog(title: string, message: string): Promise<any> {
let SaveFile = await dialog.showOpenDialog(Windows.CurrentBrowserWindow, {
title: title,
message: message,
buttonLabel: '亲,点我确认选择!',
filters: [
{ name: 'All', extensions: ['*'] },
],
properties: [
'openDirectory',
'createDirectory'
]
});
if(!SaveFile.canceled) {
return SaveFile.filePaths
}
}
}

View File

@@ -0,0 +1,10 @@
import { app, BrowserWindow, TouchBar, dialog, shell } from "electron";
import Windows from "@/core/model/Windows.model";
import Config from "@config/Index.config";
const { TouchBarLabel, TouchBarButton,TouchBarSpacer } = TouchBar;
export class Touchbar {
constructor() {
Windows.CurrentBrowserWindow.setTouchBar(new TouchBar(Config.TouchBarConfig))
}
}

View File

@@ -0,0 +1,38 @@
import { nativeImage, NativeImage, Menu, Tray } from "electron";
import Config from "@config/Index.config";
export class TrayInteractive {
private tray!: Tray;
constructor() {
this.tray = new Tray(this.setTemplateImage());
this.buildTrayMenu()
this.TrayItemClick()
}
/**
* 全局菜单图标被点击时触发事件
* @constructor
*/
private TrayItemClick() {
this.tray.on('click', () => {
console.log("按钮被点击");
})
}
private buildTrayMenu() {
let contextMenu: Menu = Menu.buildFromTemplate(Config.TrayConfig.TopMenuRightDropdown);
this.tray.setToolTip(Config.TrayConfig.TopMenuRightTips)
this.tray.setContextMenu(contextMenu)
}
/**
* 创建 NativeImage 图片图片会随着Mac系统主题的切换而自定变纯黑或纯白
*/
private setTemplateImage(): NativeImage {
let image: NativeImage = nativeImage.createFromPath(Config.TrayConfig.TopMenuRightImage);
image.setTemplateImage(true)
return image
}
}

View File

1
src/core/ipc/.pydio Normal file
View File

@@ -0,0 +1 @@
cbaff76c-a698-40e1-916a-489df3b0fc0f

View File

@@ -0,0 +1,52 @@
import {ipcMain, shell} from "electron";
import Utils from "@utils/Index.utils";
import Windows from "@model/Windows.model";
import Config from "@config/Index.config";
import {PageSize} from "@type/PageConfig.types";
export class Application {
/**
* 创建非主窗体之外的窗体,
* 在js渲染进程中触发事件
* 1独立窗体
* 2依附在主窗体上的子窗体
* 3依附在独立窗体上的子窗体
* @constructor
*/
CreatedWindow() {
ipcMain.on('openWindow', async (event, arg: any) => {
let ControllerName: string = arg.action.split("/")[0],
MethodsName: string = arg.action.split("/")[1],
data: object = arg.data || {}
// parent false 创建单独的窗体
if (!arg.parent || !arg.hasOwnProperty("parent")) {
let a = require(`../controller/${ControllerName}.js`);
Utils.startWindows(new a[Utils.toUpperCase(ControllerName)](), MethodsName, data)
return false
}
// parent true 创建默认依附主窗口的子窗口
if (arg.hasOwnProperty("parent") || arg.parent) {
// @ts-ignore 合并配置参数,注入 parent: 当前默认启动的父类窗口实例
Object.assign(Config.PageSize[MethodsName], { parent: Windows.CurrentBrowserWindow });
// 实例化子窗口类,创建窗口
let a = require(`../controller/${ControllerName}.js`);
Utils.startWindows(new a[Utils.toUpperCase(ControllerName)](), MethodsName, data)
return false
// 创建默认依附在自定义窗口的子窗口
} else {
console.log("创建默认依附在自定义窗口的子窗口")
}
})
}
/**
* 打开浏览器窗口
*/
shellWindows() {
ipcMain.on("openExternal", async (event, arg:any) => {
await shell.openExternal(arg.url)
});
}
}

View File

@@ -0,0 +1 @@
9f151eb6-361c-4604-80e5-4aacdb861429

View File

@@ -0,0 +1,20 @@
import { ipcMain } from "electron";
import { Dialog } from "@interactive/Dialog.interactive";
import {Inject, Injectable } from "@annotation/Ioc.annotation";
import { Http } from "@net/Http.net";
import Utils from "@utils/Index.utils";
export class FileIpc {
@Inject()
private readonly _Net!: Http;
openFile() {
ipcMain.on('SaveFile', async (event, arg) => {
let SaveFilePath = await Dialog.showSaveDialog("选择路径","选择下载路径");
if (SaveFilePath != undefined) {
Utils.DownFile(arg.url,SaveFilePath)
}
})
}
}

1
src/core/model/.pydio Normal file
View File

@@ -0,0 +1 @@
0afe9a3d-f1e9-43e0-975d-c298d3d7f242

View File

@@ -0,0 +1,13 @@
class IocModel {
private _classPool: Array<{ new (...args: any[]): {}; }> = [];
get classPool(): Array<{ new(...args: any[]): {} }> {
return this._classPool;
}
set classPool(value: Array<{ new(...args: any[]): {} }>) {
this._classPool = [...value, ...this._classPool]
}
}
export default new IocModel();

View File

@@ -0,0 +1,65 @@
import { WebContents, BrowserWindow } from "electron";
class Windows {
private _HomeBrowserWindow!: BrowserWindow;
private _CurrentBrowserWindow!: BrowserWindow;
private _SettingBrowserWindow!: BrowserWindow;
private _CurrentWindowNew!: any;
private _HomeBrowserWindowWebContents!: typeof WebContents;
private _SettingBrowserWindowWebContents!: typeof WebContents;
get CurrentWindowNew(): any {
return this._CurrentWindowNew;
}
set CurrentWindowNew(value: any) {
this._CurrentWindowNew = value;
}
get CurrentBrowserWindow(): Electron.BrowserWindow {
return this._CurrentBrowserWindow;
}
set CurrentBrowserWindow(value: Electron.BrowserWindow) {
this._CurrentBrowserWindow = value;
}
get HomeBrowserWindow(): BrowserWindow {
return this._HomeBrowserWindow;
}
set HomeBrowserWindow(value: BrowserWindow) {
this.CurrentBrowserWindow = value;
this._HomeBrowserWindow = value;
}
get SettingBrowserWindow(): BrowserWindow {
return this._SettingBrowserWindow;
}
set SettingBrowserWindow(value: BrowserWindow) {
this.CurrentBrowserWindow = value;
this._SettingBrowserWindow = value;
}
get HomeBrowserWindowWebContents(): typeof WebContents {
return this._HomeBrowserWindowWebContents;
}
set HomeBrowserWindowWebContents(value: typeof WebContents) {
this._HomeBrowserWindowWebContents = value;
}
get SettingBrowserWindowWebContents(): typeof WebContents {
return this._SettingBrowserWindowWebContents;
}
set SettingBrowserWindowWebContents(value: typeof WebContents) {
this._SettingBrowserWindowWebContents = value;
}
}
export default new Windows();

1
src/core/net/.pydio Normal file
View File

@@ -0,0 +1 @@
32c6526e-0e3a-4ad6-bd4b-56580bfc716f

89
src/core/net/Http.net.ts Normal file
View File

@@ -0,0 +1,89 @@
import Config from "@config/Index.config";
import {Inject, Injectable } from "@annotation/Ioc.annotation";
import { LogsUtils } from '@utils/logs.utils';
import Axios, { AxiosResponse } from "axios";
import Utils from "@utils/Index.utils";
interface RequestParams {
url: string,
data?: { [index: string]: any; },
header?: { [index: string]: any }
}
@Injectable()
export class Http {
private ResponseData!: AxiosResponse<any>;
constructor() {
Axios.defaults.baseURL = Utils.CheckAjaxUrl();
}
@Inject()
private LogsUtils!: LogsUtils;
/**
* GET请求
* @param params
* @constructor
*/
public async GET(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.get(params.url, {
params: params.data,
headers: Object.assign({}, params.header)
});
return this.ResponseData.data;
} catch (e) {
throw new Error(`GET 请求出错:${e.message}`)
}
}
/**
* POST请求
* @param params
* @constructor
*/
public async POST(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.post(params.url, params.data, {
headers: Object.assign({}, params.header)
})
return this.ResponseData.data;
} catch (e) {
throw new Error(`POST 请求出错:${e.message}`)
}
}
/**
* PUT 请求
* @param params
* @constructor
*/
public async PUT(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.put(params.url, params.data, {
headers: Object.assign({}, params.header)
})
return this.ResponseData.data;
} catch (e) {
throw new Error(`PUT 请求出错:${e.message}`)
}
}
/**
* DELETE 请求
* @param params
*/
public async DELETE(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.delete(params.url, {
params: params.data,
headers: Object.assign({}, params.header)
})
return this.ResponseData.data;
} catch (e) {
throw new Error(`DELETE 请求出错:${e.message}`)
}
}
}

1
src/core/run/.pydio Normal file
View File

@@ -0,0 +1 @@
b7bb9238-8c04-4671-a65c-4880baa9ff7d

27
src/core/run/Init.run.ts Normal file
View File

@@ -0,0 +1,27 @@
import { AutoLoadWindow, CreateApplicationMenu, CreateTouchbar, CreateTray }
from "@annotation/Run.annotation";
import { CreateApplicationIpc } from "@annotation/Creted.annotation";
import { Application } from "@ipc/Application.ipc";
import { BaseControllerTypes } from "@type/BaseController.types";
/**
* 启动类
* 作用:注册全局事件
* V2版本开发新的注解支持
* @AutoLoadWindow() 注解 根据 Index.config.ts 中的 StartPage 自动寻找controller文件夹下的ts文件然后作为主窗体启动
* @CreateApplicationMenu() 创建菜单
* @CreateTouchbar() 创建mac键盘上的Touchbar
* @CreateTray() 创建mac顶部全局菜单图标
* @CreateApplicationIpc() 注册全局ipc
*/
@AutoLoadWindow()
@CreateApplicationMenu()
@CreateTouchbar()
@CreateTray()
@CreateApplicationIpc([ Application ])
export class Run implements BaseControllerTypes {
event(): void {}
ipc(): void {}
monitor(): void {}
ui(): void {}
}

1
src/core/service/.pydio Normal file
View File

@@ -0,0 +1 @@
1310afb9-bc18-41af-84b1-88d4f9e46e90

View File

@@ -0,0 +1,4 @@
export interface VideoService {
PlayList(params: object): Promise<any>;
PalyVideo(params: object | undefined): Promise<any>;
}

View File

@@ -0,0 +1 @@
1de04712-d1ce-4518-8326-6afa1ed4af55

View File

@@ -0,0 +1,32 @@
import { VideoService } from "@service/Video.service";
import { GET } from "@annotation/Http.annotation";
import { Injectable } from "@annotation/Ioc.annotation";
@Injectable()
export class VideoImplService implements VideoService {
/**
* @param params
* url: 请求地址(可选参数)
* 1如果不传方法名将自动作为请求的地址
* 2如果传了采用传入的地址作为请求地址
* useHandle: 是否将注解通过ajax获得的数据还给方法可选参数
* 1如果不传数据将直接返回给页面的方法调用者service的方法不需要retrun
* 2如果传了service方法的参数即是接口的请求参数在请求完成后将会保存ajax返回结果
* 所以根据实际业务可以传入true对ajax返回的数据做一些数据处理然后再返回给页面使用
*/
@GET({ useHandle: true })
public async PlayList(params: object): Promise<any> {
/**
* useHandle 为 true 的方法会被调用两次一次是页面调用params存储的是ajax请求参数
* 第二次是注解内部调用params 存储的是ajax请求到的具体数据
*/
return params
}
@GET()
public async PalyVideo(params: object | undefined): Promise<any> {}
@GET()
public async VideoComment(params: object | undefined): Promise<any> {}
}

1
src/core/types/.pydio Normal file
View File

@@ -0,0 +1 @@
bfc4a79a-fb3d-479d-917b-3f98d337341c

View File

@@ -0,0 +1,6 @@
export interface ApiUrlConfig {
BaseUrl: string,
ApiList: {
[key: string]: any
}
}

View File

@@ -0,0 +1,7 @@
export interface BaseControllerTypes {
// render(): { [ key:string ]: any };
ipc(): void;
ui(): void;
event(): void;
monitor(): void;
}

View File

@@ -0,0 +1,13 @@
import { BrowserWindowConstructorOptions } from "electron";
export interface PagePath {
Home?: String,
PlayVideo?: String
}
export interface PageSize {
// 系统首页 窗体的配置
Home?: BrowserWindowConstructorOptions,
// 设置页面 窗体的配置
PlayVideo?: BrowserWindowConstructorOptions
}

View File

@@ -0,0 +1,7 @@
import { MenuItem, MenuItemConstructorOptions } from "electron";
export interface TrayConfig {
TopMenuRightImage: string;
TopMenuRightDropdown: Array<(MenuItemConstructorOptions) | (MenuItem)>;
TopMenuRightTips: string;
}

1
src/core/utils/.pydio Normal file
View File

@@ -0,0 +1 @@
0d2b0ed4-6fd4-46e2-9c76-c4515be3f00e

View File

@@ -0,0 +1,119 @@
import { join } from "path";
import Config from "@config/Index.config";
import {BrowserWindow, Notification, NotificationConstructorOptions, shell} from "electron";
import {mkdirSync, writeFileSync, existsSync, PathLike} from "fs";
import {renderFile} from "jade";
import Windows from "@model/Windows.model";
export default class Utils {
public static CheckAjaxUrl(): string {
return Config.ApiUrl.BaseUrl
}
/**
* 返回相对路径
* @param path 路径
* @constructor
*/
public static GetFilePath(path: string): string {
return join(__dirname, path)
}
/**
* 根据配置返回对应的controller类require后的类
* @constructor
*/
public static GetController(): any {
return require(`../controller/${(Config.StartPage.split("/"))[0]}.js`)
}
/**
* Home.controller 拆分为数组然后controller的首字母C大写
* 然后返回 HomeController 的类名
*/
public static toUpperCase(cont: string): string {
let Controller = cont.split(".")
return Controller[0]+ Controller[1].charAt(0).toUpperCase() + Controller[1].slice(1);
}
/**
* 系统通知
* @param parmas
* @constructor
*/
public static Notification(parmas: NotificationConstructorOptions): void {
new Notification(parmas).show();
}
/**
* 下载资源
* @param url 需要下载的资源地址
* @param path 下载后需要保存的路径
* @constructor
*/
public static DownFile(url: string, path: PathLike) {
Windows.CurrentBrowserWindow.webContents.downloadURL(url)
Windows.CurrentBrowserWindow.webContents.session.on(
"will-download",
(event: any, item: any, webContents: any) => {
item.setSavePath(`${path}/${item.getFilename()}`);
item.once('done', (event: any, state: any) => {
if (state === 'completed') {
// 下载成功后显示通知
Utils.Notification({
title: '下载完成',
body: `您的视频 ${item.getFilename()} 已成功下载!`,
silent: true,
})
}
})
}
)
}
/**
* 创建文件夹
* @param name 文件夹
* @param html html
*/
public static mkdir(name: string, html: string): void {
let path = join(__dirname, `../../application/page/${name}`);
existsSync(path) ? null : mkdirSync(path)
Utils.mkFile(path, name, html)
}
/**
* 生成文件并写入数据
* @param path 路径
* @param name html文件名
* @param html html数据
*/
public static mkFile(path: string, name: string, html: string): void {
writeFileSync(`${path}/${name}.html`, html)
}
/**
* 创建启动窗口
* @param target
* @param name
*/
public static async startWindows(target: any, name: string, params?: object) {
let data: { [ p: string ]: any } = await target[name](params),
Dom: string = renderFile(
Utils.GetFilePath(`../../../src/application/page/${name}/${name}.jade`),
Object.assign(Config.jadeCompile0ptions, data),
);
// 在dist/ 创建并生成对应的html文件
Utils.mkdir(name, Dom)
try {
// @ts-ignore
Windows.CurrentBrowserWindow = new BrowserWindow(Config.PageSize[name]);
// @ts-ignore
Windows.CurrentBrowserWindow.loadFile(<string>Config.PagePath[name]).then(r => {});
Windows.CurrentBrowserWindow.show();
} catch (e) {
throw new Error("创建窗体失败请检查配置文件窗体的html路径是否正确")
}
}
}

View File

@@ -0,0 +1,31 @@
import moment from "moment"
import { appendFileSync } from "fs"
import { join } from "path";
import Config from "@config/Index.config";
import { Injectable } from "@annotation/Ioc.annotation";
import os from "os"
@Injectable()
export class LogsUtils {
/**
* 返回日志文件名 application_2020-07-16_21-03-21.log
* @param level 日志类型
* @constructor
*/
public GetTime(level: string = "application"): string {
return `${level}_${moment().format("YYYY-MM-DD-HH")}.log`;
}
/**
* 传入数据生成日志
* @param data 数据
* @param level 日志类型
*/
public logs(data: string, level: string = "application"): void {
appendFileSync(join(
__dirname,
`${Config.LogsPath}/${this.GetTime(level)}`), `${moment().format("YYYY/MM/DD HH:mm:ss")}${data}${os.EOL}${os.EOL}`)
}
}