inject-view

This commit is contained in:
xu_yanfeng 2025-01-18 20:08:14 +08:00
parent 033a1c209e
commit d20da42c31
6 changed files with 561 additions and 0 deletions

View File

@ -0,0 +1,210 @@
<template>
<div class="ad" v-show="ads.length && isShow" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
<div class="header">
<div class="title">Creator插件推荐</div>
<div style="flex: 1"></div>
<div class="close" @click="onClose">
<i class="icon iconfont icon_close"></i>
</div>
</div>
<div class="body">
<div class="left slide" @click="onClickBtnLeft">
<i class="iconfont icon_arrow_left arrow"></i>
</div>
<div class="list" ref="elAd" @wheel="onWheel">
<Banner v-for="(item, index) in ads" :data="item" :key="index"></Banner>
</div>
<div class="right slide" @click="onClickBtnRight">
<i class="iconfont icon_arrow_right arrow"></i>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, toRaw } from "vue";
import Banner from "./banner.vue";
import { emitter, Msg } from "./const";
import { AdItem, getAdData } from "./loader";
export default defineComponent({
name: "ad",
components: { Banner },
setup() {
let ads = ref<AdItem[]>([]);
let stopAutoScroll = false;
onMounted(async () => {
const data = await getAdData();
if (!data) {
console.log(`get ad failed`);
return;
}
if (!data.valid) {
console.log(`set ad forbidden`);
return;
}
if (!data.data.length) {
console.log(`not find any ad`);
return;
}
ads.value = data.data;
console.log("get ads ", toRaw(ads.value));
setInterval(() => {
return;
if (stopAutoScroll) {
return;
}
if (elAd.value) {
const el = elAd.value as HTMLElement;
let left = el.scrollLeft + adWidth;
if (el.scrollLeft + el.clientWidth > el.scrollWidth) {
left = 0;
}
el.scrollTo({ left, behavior: "smooth" });
}
}, data.scrollDuration * 1000);
});
function testBanner() {
const data = new AdItem();
data.name = "ad test 11111111111 11111111111 44444444444444 5555555555555 111111111111111111 2222222222222222 33333333333333 444444444444444";
data.store = "http://www.baidu.com";
emitter.emit(Msg.ChangeAd, data);
}
const elAd = ref<HTMLElement>(null);
const adWidth = 300;
const isShow = ref(true);
return {
isShow,
elAd,
ads,
onClose() {
isShow.value = false;
},
onMouseEnter() {
stopAutoScroll = true;
},
onMouseLeave() {
stopAutoScroll = false;
},
onWheel(event: WheelEvent) {
if (!elAd.value) {
return;
}
event.preventDefault();
const div = elAd.value as HTMLElement;
if (event.deltaY > 0) {
div.scrollTo({ left: div.scrollLeft + adWidth, behavior: "smooth" });
} else {
div.scrollTo({ left: div.scrollLeft - adWidth, behavior: "smooth" });
}
},
async onClickBtnLeft() {
if (elAd.value) {
const el = elAd.value as HTMLElement;
el.scrollTo({ left: el.scrollLeft - adWidth, behavior: "smooth" });
}
},
async onClickBtnRight() {
if (elAd.value) {
const el = elAd.value as HTMLElement;
el.scrollTo({ left: el.scrollLeft + adWidth, behavior: "smooth" });
}
},
};
},
});
</script>
<style scoped lang="less">
@color-bg: #8d8d8da6;
@color-hover: #4d4d4da6;
@color-active: #ffaa00;
.ad {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
overflow: hidden;
.header {
display: flex;
align-items: flex-end;
.title {
font-size: 12px;
user-select: none;
background-color: white;
// border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 3px 9px;
}
.close {
background-color: white;
border-top-left-radius: 10px;
cursor: pointer;
color: rgb(138, 138, 138);
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
.icon {
font-size: 8px;
&:hover {
color: black;
}
&:active {
color: #ffaa00;
}
}
}
}
.body {
display: flex;
position: relative;
.list {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
}
.left {
left: 0;
background-image: linear-gradient(to left, rgba(0, 0, 0, 0), @color-bg);
&:hover {
background-image: linear-gradient(to left, rgba(0, 0, 0, 0), @color-hover);
}
&:active {
background-image: linear-gradient(to left, rgba(0, 0, 0, 0), @color-active);
}
}
.right {
right: 0px;
background-image: linear-gradient(to right, rgba(0, 0, 0, 0), @color-bg);
&:hover {
background-image: linear-gradient(to right, rgba(0, 0, 0, 0), @color-hover);
}
&:active {
background-image: linear-gradient(to right, rgba(0, 0, 0, 0), @color-active);
}
}
.slide {
display: flex;
height: 100%;
width: 20px;
cursor: pointer;
flex-direction: row;
align-items: center;
position: absolute;
.arrow {
font-size: 22px;
color: rgb(61, 61, 61);
}
}
}
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<div v-if="data" class="banner" :class="ani" @click="onClick" :title="data.tip">
<div class="text">{{ data.name }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, PropType, ref } from "vue";
import { emitter, Msg } from "./const";
import { AdItem } from "./loader";
export default defineComponent({
name: "banner",
props: {
data: {
type: Object as PropType<AdItem>,
default: () => new AdItem(),
},
},
setup(props, { emit }) {
function chageAd(v: AdItem) {
console.log("show ad: ", JSON.stringify(v));
}
onMounted(() => {
emitter.on(Msg.ChangeAd, chageAd);
});
onUnmounted(() => {
emitter.off(Msg.ChangeAd, chageAd);
});
const ani = ref("");
return {
ani,
onClick() {
if (props.data.store) {
window.open(props.data.store);
}
},
};
},
});
</script>
<style scoped lang="less">
@keyframes flip-out {
0% {
transform: rotateX(0);
}
100% {
transform: rotateX(90deg);
}
}
@keyframes flip-in {
0% {
transform: rotateX(90deg);
}
100% {
transform: rotateX(0);
}
}
.banner-out {
animation: flip-out 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955) both;
}
.banner-in {
animation: flip-in 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955) both;
}
.banner {
border: 2px solid #ffffff;
background-color: #ffffff;
overflow: hidden;
min-width: 300px;
max-width: 300px;
min-height: 50px;
max-height: 50px;
cursor: pointer;
display: flex;
border: 2px solid #d2d2d2;
text-align: center;
align-items: flex-end;
&:hover {
border: 2px solid #d1d1d1;
background-color: #d1d1d1;
}
.text {
user-select: none;
flex: 1;
padding-bottom: 2px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: wrap;
}
}
</style>

View File

@ -0,0 +1,5 @@
import { TinyEmitter } from "tiny-emitter";
export const Msg = {
ChangeAd: "ChangeAd",
};
export const emitter = new TinyEmitter();

View File

@ -0,0 +1,146 @@
export interface MirrorInfo {
/**
* url
*/
name: string;
/**
*
*/
time: number;
}
class Config {
private key = "cc-inspector-ad-config";
private data: MirrorInfo[] = [];
constructor() {
const cfg = localStorage.getItem(this.key);
if (cfg) {
try {
const ret = JSON.parse(cfg) as MirrorInfo[];
if (ret) {
ret.forEach((el) => {
this.data.push({ name: el.name, time: el.time });
});
}
} catch {}
}
}
save(name: string, time: number) {
const ret = this.data.find((el) => el.name === name);
if (ret) {
ret.time = time;
} else {
this.data.push({ name: name, time: time } as MirrorInfo);
}
localStorage.setItem(this.key, JSON.stringify(this.data));
}
getTime(url: string) {
const ret = this.data.find((el) => el.name === url);
if (ret) {
return ret.time;
}
return 0;
}
}
export class GithubMirror {
owner: string = "tidys";
repo: string = "cc-inspector-ad";
branch: string = "main";
/**
*
*/
time: number = 0;
/**
*
*/
name: string = "";
private calcUrl: Function;
constructor(name: string, cb) {
this.name = name;
this.time = cfg.getTime(name);
this.calcUrl = cb;
}
private getUrl(file: string) {
if (this.calcUrl) {
return this.calcUrl(this.owner, this.repo, this.branch, file);
} else {
return "";
}
}
public async getData(file: string) {
const url = this.getUrl(file);
if (url) {
const data = await this.reqFecth(url);
return data;
}
return null;
}
private reqFecth(url: string): Promise<Object | null> {
return new Promise((resolve, reject) => {
console.log(`req ad: ${url}`);
fetch(url)
.then((res) => {
return res.json();
})
.then((data) => {
resolve(data);
})
.catch((e) => {
resolve(null);
});
});
}
}
const cfg = new Config();
export class GithubMirrorMgr {
mirrors: GithubMirror[] = [];
constructor() {
// 使用国内gitub镜像来达到下载远程配置文件的目的
this.mirrors.push(
new GithubMirror("github", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("bgithub", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw.bgithub.xyz/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("kkgithub", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw.kkgithub.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("xiaohei", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw-githubusercontent.xiaohei.me/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("gh-proxy", (owner: string, repo: string, branch: string, file: string) => {
return `https://gh-proxy.com/raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("ghproxy", (owner: string, repo: string, branch: string, file: string) => {
return `https://ghproxy.net/https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
}
async getData(file: string): Promise<Object | null> {
this.mirrors.sort((a, b) => b.time - a.time);
for (let i = 0; i < this.mirrors.length; i++) {
const mirror = this.mirrors[i];
const data = await mirror.getData(file);
if (data) {
const time = new Date().getTime();
mirror.time = time;
cfg.save(mirror.name, time);
return data;
}
}
return null;
}
}
export const githubMirrorMgr = new GithubMirrorMgr();

View File

@ -0,0 +1,90 @@
import { githubMirrorMgr } from "./github";
export class AdItem {
/**
* 广
*/
name: string = "";
/**
*
*/
tip: string = "";
/**
*
*/
try: string = "";
/**
* 广store购买链接
*/
store: string = "";
/**
* 广s
*/
duration: number = 0;
/**
* 广
*/
valid: boolean = true;
/**
*
*/
img: string = "";
parse(data: AdItem) {
this.name = data.name;
this.store = data.store || "";
this.try = data.try || "";
this.tip = data.tip || "";
this.duration = data.duration || 0;
this.valid = !!data.valid;
this.img = data.img || "";
return this;
}
}
export class AdData {
desc: string = "";
version: string = "";
/**
* 广
*/
valid: boolean = false;
/**
* 10
*/
showDuration: number = 10;
/**
* 广
*/
scrollDuration: number = 3;
data: Array<AdItem> = [];
parse(data: AdData) {
this.desc = data.desc;
this.version = data.version;
this.valid = !!data.valid;
this.showDuration = data.showDuration || 10;
this.scrollDuration = data.scrollDuration || 3;
if (data.data) {
data.data.forEach((el) => {
const item = new AdItem().parse(el);
if (!item.duration) {
console.warn(`add failed, ad.duration is ${item.duration}, ${JSON.stringify(item)}`);
return;
}
if (!item.valid) {
console.warn(`add failed, ad is invalid, ${JSON.stringify(item)}`);
return;
}
this.data.push(item);
});
}
}
}
export async function getAdData(): Promise<AdData | null> {
const data = await githubMirrorMgr.getData("ad.json");
if (data) {
const ad = new AdData();
ad.parse(data as AdData);
return ad;
}
return null;
}

View File

@ -0,0 +1,18 @@
/**
* web测试inject_view的入口chrome插件不应该走这个界面
*/
import ccui from "@xuyanfeng/cc-ui";
import "@xuyanfeng/cc-ui/dist/ccui.css";
import "@xuyanfeng/cc-ui/iconfont/iconfont.css";
import CCP from "cc-plugin/src/ccp/entry-render";
import { createApp } from "vue";
import pluginConfig from "../../../cc-plugin.config";
import App from "./app.vue";
export default CCP.init(pluginConfig, {
ready: function (rootElement: any, args: any) {
const app = createApp(App);
app.use(ccui);
app.mount(rootElement);
},
});