🎉 新增商店模板及样式配置

This commit is contained in:
HibiKier 2025-01-05 23:55:47 +08:00
parent 012a23008b
commit b7fde205bb
23 changed files with 863 additions and 310 deletions

View File

@ -0,0 +1,297 @@
@font-face {
font-family: fzrzFont;
/* 导入的字体文件 */
src: url("../../font/fzrzExtraBold.ttf");
}
@font-face {
font-family: msyhFont;
/* 导入的字体文件 */
src: url("../../font/msyh.ttf");
}
@font-face {
font-family: hywhFont;
/* 导入的字体文件 */
src: url("../../font/HYWenHei-85W.ttf");
}
@font-face {
font-family: syhtFont;
/* 导入的字体文件 */
src: url("../../font/syht.otf");
}
body {
position: absolute;
left: -8px;
top: -8px;
}
.wrapper {
width: 800px;
font-family: "hywhFont";
padding: 10px 0;
background-color: #fbe4e4;
box-sizing: border-box;
}
.top-title {
color: #e87692;
font-size: 50px;
text-align: center;
font-family: "fzrzFont";
}
.split {
background-image: url("./res/img/split.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 41px;
}
.top-head {
background-image: url("./res/img/head.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 400px;
}
.shop-item {
padding-top: 30px;
box-sizing: border-box;
}
.shop-item-border {
display: flex;
position: relative;
}
.shop-item-title {
background-image: url("./res/img/title-bk.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 100px;
margin-bottom: 20px;
display: flex;
justify-content: center;
}
.shop-item-title-text {
color: white;
font-size: 30px;
margin-top: 13px;
}
.shop-item-left {
min-height: 460px;
position: relative;
width: 140px;
}
.shop-item-left-qq {
position: absolute;
height: 100px;
}
.left-img {
left: 10px;
}
.shop-item-right {
width: 130px;
}
.shop-item-right-zx {
height: 460px;
position: absolute;
z-index: 3;
top: 10px;
}
.right-img {
right: 10px;
}
.shop-item-mid {
width: 480px;
box-sizing: border-box;
padding-top: 20px;
position: relative;
}
.shop-item-mid-bk-inner {
width: 520px;
box-sizing: border-box;
padding-top: 30px;
background-color: #be5972;
border: 1px solid #b14b5f;
border-radius: 10px;
position: absolute;
right: -50px;
top: 10px;
z-index: 1;
height: calc(100% - 20px);
}
.shop-item-mid-bk-out {
width: 520px;
box-sizing: border-box;
background-color: #f096a8;
border: 1px solid #812528;
border-radius: 10px;
height: 100%;
z-index: 2;
position: relative;
padding: 30px;
}
.goods-item {
background-color: #f8cfd8;
width: 100%;
min-height: 130px;
border-radius: 10px;
padding: 10px;
display: flex;
position: relative;
border: 1px solid #994446;
}
.goods-id {
position: absolute;
color: white;
font-size: 15px;
border-top-left-radius: 10px;
top: 0;
left: 0;
border-right: 60px solid transparent;
border-bottom: 60px solid transparent;
border-top: 60px solid #ea7492;
}
.goods-id-text {
position: absolute;
top: -54px;
left: 9px;
color: white;
font-size: 16px;
font-family: "fzrzFont";
transform: rotate(-45deg);
}
.goods-item-left {
height: 100%;
min-height: 130px;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 30px;
box-sizing: border-box;
}
.goods-item-left-o {
height: 24px;
width: 20px;
background-color: #e99eab;
border-radius: 40%;
border: 1px solid #994446;
}
.goods-item-icon {
background-color: #fefefe;
border: 1px solid #994446;
margin-left: 20px;
width: 120px;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.goods-item-icon-img {
height: 104px;
width: 104px;
}
.goods-item-right-des {
width: 272px;
min-height: 80px;
background-color: #fefefe;
border: 1px solid #994446;
margin-left: 10px;
border-radius: 10px;
padding: 5px;
font-family: "msyhFont";
}
.goods-item-right-price {
min-height: 30px;
background-color: #fefefe;
border-radius: 30px;
border: 1px solid #994446;
height: 20px;
margin-left: 10px;
margin-top: 5px;
font-size: 15px;
font-family: "msyhFont";
display: flex;
align-items: center;
padding-left: 10px;
position: relative;
}
.goods-item-right-btn {
min-height: 30px;
background-color: #bf9ac6;
color: white;
border-radius: 30px;
position: absolute;
right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
}
.goods-item-right-btn-line {
width: 1px;
height: 20px;
background-color: #994446;
margin: 0 10px;
}
.shop-item-mid-bag1 {
position: absolute;
width: 70px;
height: 78px;
bottom: -35px;
left: -35px;
z-index: 4;
}
.shop-item-mid-bag2 {
position: absolute;
width: 121px;
height: 89px;
right: -35px;
bottom: -35px;
z-index: 4;
}
.bottom-s {
margin-top: 70px;
}
.goods-item-name {
font-size: 18px;
font-family: "syhtFont";
}
.goods-item-name-line {
height: 2px;
width: 100%;
margin: 3px 0;
background-color: #731c1c;
border-radius: 10px;
}

View File

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
<!-- <link rel="stylesheet" href="./res/font-awesome/css/font-awesome.min.css"> -->
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="wrapper">
<div class="top-title">
{{name}}的神秘商店
</div>
<div class="split"></div>
<div class="top-head"></div>
<div class="shop-border">
{% for data in data_list %}
<div class="shop-item">
<div class="shop-item-title">
<span class="shop-item-title-text">{{data.partition}}</span>
</div>
<div class="shop-item-border">
<div class="shop-item-left">
<img src="./res/img/{{data.left_image[2]}}" class="{{data.left_image[1]}} left-img"
style="top:{{data.left_image[0]}}px" />
</div>
<div class="shop-item-mid">
<div class="shop-item-mid-bk-inner"></div>
<div class="shop-item-mid-bk-out">
{% for goods in data['goods_list'] %}
<div class="goods-item">
<div class="goods-id">
<div class="goods-id-text">{{goods.id}}</div>
</div>
<div class="goods-item-left">
<div class="goods-item-left-o"></div>
<div class="goods-item-left-o"></div>
<div class="goods-item-left-o"></div>
</div>
<div class="goods-item-icon">
<img src="{{goods.icon}}" class="goods-item-icon-img" />
</div>
<div class="goods-item-right">
<div class="goods-item-right-des">
<div class="goods-item-name">
{{goods.name}}
</div>
<div class="goods-item-name-line"></div>
{{goods.description}}
</div>
<div class="goods-item-right-price">
<span>{{goods.price}}金币</span>
<span class="goods-item-right-btn">
<span class="goods-item-right-btn-buy">立即购买</span>
<span class="goods-item-right-btn-line"></span>
<span class="goods-item-right-btn-limit">限购: {{goods.daily_limit}}</span>
</span>
</div>
</div>
</div>
{% endfor %}
</div>
<img src="./res/img/bag1.png" class="shop-item-mid-bag1" />
<img src="./res/img/bag2.png" class="shop-item-mid-bag2" />
</div>
<div class="shop-item-right">
<img src="./res/img/{{data.right_image[2]}}" class="{{data.right_image[1]}} right-img"
style="top:{{data.right_image[0]}}px" />
</div>
</div>
<!-- <div class="shop-item-border">
<div class="shop-item-left">
<img src="./res/img/2.png" class="shop-item-right-zx" />
</div>
<div class="shop-item-mid">
<div class="shop-item-mid-bk-inner"></div>
<div class="shop-item-mid-bk-out">
<div class="goods-item">
<div class="goods-id">
<div class="goods-id-text">11</div>
</div>
<div class="goods-item-left">
<div class="goods-item-left-o"></div>
<div class="goods-item-left-o"></div>
<div class="goods-item-left-o"></div>
</div>
<div class="goods-item-icon">
<img src="./res/img/bag1.png" class="goods-item-icon-img" />
</div>
<div class="goods-item-right">
<div class="goods-item-right-des">
<div class="goods-item-name">
签到道具
</div>
<div class="goods-item-name-line"></div>
背包
</div>
<div class="goods-item-right-price">
<span>100金币</span>
<span class="goods-item-right-btn">
<span class="goods-item-right-btn-buy">立即购买</span>
<span class="goods-item-right-btn-line"></span>
<span class="goods-item-right-btn-limit">限购: 0</span>
</span>
</div>
</div>
</div>
</div>
<img src="./res/img/bag1.png" class="shop-item-mid-bag1" />
<img src="./res/img/bag2.png" class="shop-item-mid-bag2" />
</div>
<div class="shop-item-right">
<img src="./res/img/1.png" class="shop-item-right-zx" />
</div>
</div> -->
</div>
{% endfor %}
</div>
<div class="split bottom-s"></div>
</div>
</body>
<script type="text/javascript" src="main.js">
</script>
</html>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -16,7 +16,7 @@ from nonebot_plugin_alconna import (
)
from nonebot_plugin_uninfo import Uninfo
from zhenxun.configs.utils import BaseBlock, PluginExtraData
from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.depends import UserName
from zhenxun.utils.enum import BlockType, PluginType
@ -45,6 +45,14 @@ __plugin_meta__ = PluginMetadata(
plugin_type=PluginType.NORMAL,
menu_type="商店",
limits=[BaseBlock(check_type=BlockType.GROUP)],
configs=[
RegisterConfig(
key="style",
value="zhenxun",
help="商店样式类型,[normal, zhenxun]",
default_value="zhenxun",
)
],
).dict(),
)
@ -108,7 +116,7 @@ _matcher.shortcut(
@_matcher.assign("$main")
async def _(session: Uninfo, arparma: Arparma):
image = await ShopManage.build_shop_image()
image = await ShopManage.get_shop_image()
logger.info("查看商店", arparma.header_result, session=session)
await MessageUtils.build_message(image).send()

View File

@ -10,7 +10,6 @@ from nonebot_plugin_alconna import UniMessage, UniMsg
from nonebot_plugin_uninfo import Uninfo
from pydantic import BaseModel, create_model
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.goods_info import GoodsInfo
from zhenxun.models.group_member_info import GroupInfoUser
@ -19,19 +18,12 @@ from zhenxun.models.user_gold_log import UserGoldLog
from zhenxun.models.user_props_log import UserPropsLog
from zhenxun.services.log import logger
from zhenxun.utils.enum import GoldHandle, PropHandle
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
from zhenxun.utils.platform import PlatformUtils
ICON_PATH = IMAGE_PATH / "shop_icon"
RANK_ICON_PATH = IMAGE_PATH / "_icon"
PLATFORM_PATH = {
"dodo": RANK_ICON_PATH / "dodo.png",
"discord": RANK_ICON_PATH / "discord.png",
"kaiheila": RANK_ICON_PATH / "kook.png",
"qq": RANK_ICON_PATH / "qq.png",
}
from .config import ICON_PATH, PLATFORM_PATH, base_config
from .html_image import html_image
from .normal_image import normal_image
class Goods(BaseModel):
@ -137,6 +129,12 @@ async def gold_rank(
class ShopManage:
uuid2goods: dict[str, Goods] = {} # noqa: RUF012
@classmethod
async def get_shop_image(cls) -> bytes:
if base_config.get("style") == "zhenxun":
return await html_image()
return await normal_image()
@classmethod
def __build_params(
cls,
@ -484,200 +482,3 @@ class ShopManage:
"""
user = await UserConsole.get_user(user_id, platform)
return user.gold
@classmethod
async def build_shop_image(cls) -> BuildImage:
"""制作商店图片
返回:
BuildImage: 商店图片
"""
goods_lst = await GoodsInfo.get_all_goods()
h = 10
_list: list[GoodsInfo] = [
goods
for goods in goods_lst
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time
]
# A = BuildImage(1100, h, color="#f9f6f2")
total_n = 0
image_list = []
for idx, goods in enumerate(_list):
name_image = BuildImage(
580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf"
)
await name_image.text(
(15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height"
)
await name_image.line((380, -5, 280, 45), "#a29ad6", 5)
await name_image.text((390, 0), "售价:", center_type="height")
if goods.goods_discount != 1:
discount_price = int(goods.goods_discount * goods.goods_price)
old_price_image = await BuildImage.build_text_image(
str(goods.goods_price), font_color=(194, 194, 194), size=15
)
await old_price_image.line(
(
0,
int(old_price_image.height / 2),
old_price_image.width + 1,
int(old_price_image.height / 2),
),
(0, 0, 0),
)
await name_image.paste(old_price_image, (440, 0))
await name_image.text((440, 15), str(discount_price), (255, 255, 255))
else:
await name_image.text(
(440, 0),
str(goods.goods_price),
(255, 255, 255),
center_type="height",
)
_tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25)
await name_image.text(
(
440 + _tmp.width,
0,
),
" 金币",
center_type="height",
)
des_image = None
font_img = BuildImage(600, 80, font_size=20, color="#a29ad6")
p = font_img.getsize("简介:")[0] + 20
if goods.goods_description:
des_list = goods.goods_description.split("\n")
desc = ""
for des in des_list:
if font_img.getsize(des)[0] > font_img.width - p - 20:
msg = ""
tmp = ""
for i in range(len(des)):
if font_img.getsize(tmp)[0] < font_img.width - p - 20:
tmp += des[i]
else:
msg += tmp + "\n"
tmp = des[i]
desc += msg
if tmp:
desc += tmp
else:
desc += des + "\n"
if desc[-1] == "\n":
desc = desc[:-1]
des_image = await text2image(desc, color="#a29ad6")
goods_image = BuildImage(
600,
(50 + des_image.height) if des_image else 50,
font_size=20,
color="#a29ad6",
font="CJGaoDeGuo.otf",
)
if des_image:
await goods_image.text((15, 50), "简介:")
await goods_image.paste(des_image, (p, 50))
await name_image.circle_corner(5)
await goods_image.paste(name_image, (0, 5), center_type="width")
await goods_image.circle_corner(20)
bk = BuildImage(
1180,
(50 + des_image.height) if des_image else 50,
font_size=15,
color="#f9f6f2",
font="CJGaoDeGuo.otf",
)
if goods.icon and (ICON_PATH / goods.icon).exists():
icon = BuildImage(70, 70, background=ICON_PATH / goods.icon)
await bk.paste(icon)
await bk.paste(goods_image, (70, 0))
n = 0
_w = 650
# 添加限时图标和时间
if goods.goods_limit_time > 0:
n += 140
_limit_time_logo = BuildImage(
40, 40, background=f"{IMAGE_PATH}/other/time.png"
)
await bk.paste(_limit_time_logo, (_w + 50, 0))
_time_img = await BuildImage.build_text_image("限时!", size=23)
await bk.paste(
_time_img,
(_w + 90, 10),
)
limit_time = time.strftime(
"%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time)
).split()
y_m_d = limit_time[0]
_h_m = limit_time[1].split(":")
h_m = f"{_h_m[0]}{_h_m[1]}"
await bk.text((_w + 55, 38), str(y_m_d))
await bk.text((_w + 65, 57), str(h_m))
_w += 140
if goods.goods_discount != 1:
n += 140
_discount_logo = BuildImage(
30, 30, background=f"{IMAGE_PATH}/other/discount.png"
)
await bk.paste(_discount_logo, (_w + 50, 10))
_tmp = await BuildImage.build_text_image("折扣!", size=23)
await bk.paste(_tmp, (_w + 90, 15))
_tmp = await BuildImage.build_text_image(
f"{10 * goods.goods_discount:.1f}",
size=30,
font_color=(85, 156, 75),
)
await bk.paste(_tmp, (_w + 50, 44))
_w += 140
if goods.daily_limit != 0:
n += 140
_daily_limit_logo = BuildImage(
35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png"
)
await bk.paste(_daily_limit_logo, (_w + 50, 10))
_tmp = await BuildImage.build_text_image(
"限购!",
size=23,
)
await bk.paste(_tmp, (_w + 90, 20))
_tmp = await BuildImage.build_text_image(
f"{goods.daily_limit}", size=30
)
await bk.paste(_tmp, (_w + 72, 45))
total_n = max(total_n, n)
if n:
await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5)
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
# 添加限时图标和时间
image_list.append(bk)
# await A.apaste(bk, (0, current_h), True)
# current_h += 90
current_h = 0
h = sum(img.height + 10 for img in image_list) or 400
A = BuildImage(1100, h, color="#f9f6f2")
for img in image_list:
await A.paste(img, (0, current_h))
current_h += img.height + 10
w = 950
if total_n:
w += total_n
h = A.height + 230 + 100
h = max(h, 1000)
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
await shop.paste(A, (20, 230))
await shop.paste(shop_logo, (450, 30))
tip = "注【通过 购买道具 序号 或者 商品名称 购买】"
await shop.text(
(
int((1000 - shop.getsize(tip)[0]) / 2),
170,
),
"注【通过 序号 或者 商品名称 购买】",
)
await shop.text(
(20, h - 100),
"神秘药水\t\t售价9999999金币\n\t\t鬼知道会有什么效果~",
)
return shop

View File

@ -0,0 +1,20 @@
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH
base_config = Config.get("shop")
ICON_PATH = IMAGE_PATH / "shop_icon"
RANK_ICON_PATH = IMAGE_PATH / "_icon"
PLATFORM_PATH = {
"dodo": RANK_ICON_PATH / "dodo.png",
"discord": RANK_ICON_PATH / "discord.png",
"kaiheila": RANK_ICON_PATH / "kook.png",
"qq": RANK_ICON_PATH / "qq.png",
}
LEFT_RIGHT_IMAGE = ["1.png", "2.png", "qq.png"]
LEFT_RIGHT_PATH = TEMPLATE_PATH / "shop" / "res" / "img"

View File

@ -0,0 +1,91 @@
import random
import time
from nonebot_plugin_htmlrender import template_to_pic
from pydantic import BaseModel
from zhenxun.configs.config import BotConfig
from zhenxun.configs.path_config import TEMPLATE_PATH
from zhenxun.models.goods_info import GoodsInfo
from zhenxun.utils._build_image import BuildImage
from .config import ICON_PATH, LEFT_RIGHT_IMAGE
class GoodsItem(BaseModel):
goods_list: list[dict]
"""商品列表"""
partition: str
"""分区名称"""
left_image: tuple[int, str, str]
"""左图"""
right_image: tuple[int, str, str]
"""右图"""
def get_left_right_image() -> tuple[tuple[int, str, str], tuple[int, str, str]]:
qq_top = random.randint(0, 280)
img_top = random.randint(10, 80)
left_image = random.choice(LEFT_RIGHT_IMAGE)
right_image = None
if left_image == "qq.png":
left_top = qq_top
right_top = img_top
left_css = "shop-item-left-qq"
right_css = "shop-item-right-zx"
right_image = random.choice(LEFT_RIGHT_IMAGE[:-1])
else:
left_top = img_top
right_top = qq_top
right_image = "qq.png"
left_css = "shop-item-right-zx"
right_css = "shop-item-left-qq"
return (left_top, left_css, left_image), (right_top, right_css, right_image)
async def html_image() -> bytes:
"""构建图片"""
goods_list: list[tuple[int, GoodsInfo]] = [
(i + 1, goods)
for i, goods in enumerate(await GoodsInfo.get_all_goods())
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time
]
partition_dict: dict[str, list[dict]] = {}
for goods in goods_list:
if not goods[1].partition:
goods[1].partition = "默认分区"
if goods[1].partition not in partition_dict:
partition_dict[goods[1].partition] = []
partition_dict[goods[1].partition].append(
{
"id": goods[0],
"price": goods[1].goods_price,
"daily_limit": goods[1].daily_limit or "",
"name": goods[1].goods_name,
"icon": "data:image/png;base64,"
+ BuildImage.open(ICON_PATH / goods[1].icon).pic2bs4()[9:],
"description": goods[1].goods_description,
}
)
data_list = []
for partition in partition_dict:
left, right = get_left_right_image()
data_list.append(
GoodsItem(
goods_list=partition_dict[partition],
partition=partition,
left_image=left,
right_image=right,
)
)
return await template_to_pic(
template_path=str((TEMPLATE_PATH / "shop").absolute()),
template_name="main.html",
templates={"name": BotConfig.self_nickname, "data_list": data_list},
pages={
"viewport": {"width": 800, "height": 1024},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
)

View File

@ -0,0 +1,203 @@
import time
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.goods_info import GoodsInfo
from zhenxun.utils._build_image import BuildImage
from zhenxun.utils.image_utils import text2image
from .config import ICON_PATH
async def normal_image() -> bytes:
"""制作商店图片
返回:
BuildImage: 商店图片
"""
goods_lst = await GoodsInfo.get_all_goods()
h = 10
_list: list[GoodsInfo] = [
goods
for goods in goods_lst
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time
]
# A = BuildImage(1100, h, color="#f9f6f2")
total_n = 0
image_list = []
for idx, goods in enumerate(_list):
name_image = BuildImage(
580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf"
)
await name_image.text(
(15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height"
)
await name_image.line((380, -5, 280, 45), "#a29ad6", 5)
await name_image.text((390, 0), "售价:", center_type="height")
if goods.goods_discount != 1:
discount_price = int(goods.goods_discount * goods.goods_price)
old_price_image = await BuildImage.build_text_image(
str(goods.goods_price), font_color=(194, 194, 194), size=15
)
await old_price_image.line(
(
0,
int(old_price_image.height / 2),
old_price_image.width + 1,
int(old_price_image.height / 2),
),
(0, 0, 0),
)
await name_image.paste(old_price_image, (440, 0))
await name_image.text((440, 15), str(discount_price), (255, 255, 255))
else:
await name_image.text(
(440, 0),
str(goods.goods_price),
(255, 255, 255),
center_type="height",
)
_tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25)
await name_image.text(
(
440 + _tmp.width,
0,
),
" 金币",
center_type="height",
)
des_image = None
font_img = BuildImage(600, 80, font_size=20, color="#a29ad6")
p = font_img.getsize("简介:")[0] + 20
if goods.goods_description:
des_list = goods.goods_description.split("\n")
desc = ""
for des in des_list:
if font_img.getsize(des)[0] > font_img.width - p - 20:
msg = ""
tmp = ""
for i in range(len(des)):
if font_img.getsize(tmp)[0] < font_img.width - p - 20:
tmp += des[i]
else:
msg += tmp + "\n"
tmp = des[i]
desc += msg
if tmp:
desc += tmp
else:
desc += des + "\n"
if desc[-1] == "\n":
desc = desc[:-1]
des_image = await text2image(desc, color="#a29ad6")
goods_image = BuildImage(
600,
(50 + des_image.height) if des_image else 50,
font_size=20,
color="#a29ad6",
font="CJGaoDeGuo.otf",
)
if des_image:
await goods_image.text((15, 50), "简介:")
await goods_image.paste(des_image, (p, 50))
await name_image.circle_corner(5)
await goods_image.paste(name_image, (0, 5), center_type="width")
await goods_image.circle_corner(20)
bk = BuildImage(
1180,
(50 + des_image.height) if des_image else 50,
font_size=15,
color="#f9f6f2",
font="CJGaoDeGuo.otf",
)
if goods.icon and (ICON_PATH / goods.icon).exists():
icon = BuildImage(70, 70, background=ICON_PATH / goods.icon)
await bk.paste(icon)
await bk.paste(goods_image, (70, 0))
n = 0
_w = 650
# 添加限时图标和时间
if goods.goods_limit_time > 0:
n += 140
_limit_time_logo = BuildImage(
40, 40, background=f"{IMAGE_PATH}/other/time.png"
)
await bk.paste(_limit_time_logo, (_w + 50, 0))
_time_img = await BuildImage.build_text_image("限时!", size=23)
await bk.paste(
_time_img,
(_w + 90, 10),
)
limit_time = time.strftime(
"%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time)
).split()
y_m_d = limit_time[0]
_h_m = limit_time[1].split(":")
h_m = f"{_h_m[0]}{_h_m[1]}"
await bk.text((_w + 55, 38), str(y_m_d))
await bk.text((_w + 65, 57), str(h_m))
_w += 140
if goods.goods_discount != 1:
n += 140
_discount_logo = BuildImage(
30, 30, background=f"{IMAGE_PATH}/other/discount.png"
)
await bk.paste(_discount_logo, (_w + 50, 10))
_tmp = await BuildImage.build_text_image("折扣!", size=23)
await bk.paste(_tmp, (_w + 90, 15))
_tmp = await BuildImage.build_text_image(
f"{10 * goods.goods_discount:.1f}",
size=30,
font_color=(85, 156, 75),
)
await bk.paste(_tmp, (_w + 50, 44))
_w += 140
if goods.daily_limit != 0:
n += 140
_daily_limit_logo = BuildImage(
35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png"
)
await bk.paste(_daily_limit_logo, (_w + 50, 10))
_tmp = await BuildImage.build_text_image(
"限购!",
size=23,
)
await bk.paste(_tmp, (_w + 90, 20))
_tmp = await BuildImage.build_text_image(f"{goods.daily_limit}", size=30)
await bk.paste(_tmp, (_w + 72, 45))
total_n = max(total_n, n)
if n:
await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5)
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
# 添加限时图标和时间
image_list.append(bk)
# await A.apaste(bk, (0, current_h), True)
# current_h += 90
current_h = 0
h = sum(img.height + 10 for img in image_list) or 400
A = BuildImage(1100, h, color="#f9f6f2")
for img in image_list:
await A.paste(img, (0, current_h))
current_h += img.height + 10
w = 950
if total_n:
w += total_n
h = A.height + 230 + 100
h = max(h, 1000)
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
await shop.paste(A, (20, 230))
await shop.paste(shop_logo, (450, 30))
tip = "注【通过 购买道具 序号 或者 商品名称 购买】"
await shop.text(
(
int((1000 - shop.getsize(tip)[0]) / 2),
170,
),
"注【通过 序号 或者 商品名称 购买】",
)
await shop.text(
(20, h - 100),
"神秘药水\t\t售价9999999金币\n\t\t鬼知道会有什么效果~",
)
return shop.pic2bytes()

View File

@ -1,117 +1,117 @@
import asyncio
import secrets
# import asyncio
# import secrets
from fastapi import APIRouter, FastAPI
import nonebot
from nonebot.log import default_filter, default_format
from nonebot.plugin import PluginMetadata
# from fastapi import APIRouter, FastAPI
# import nonebot
# from nonebot.log import default_filter, default_format
# from nonebot.plugin import PluginMetadata
from zhenxun.configs.config import Config as gConfig
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger, logger_
from zhenxun.utils.enum import PluginType
# from zhenxun.configs.config import Config as gConfig
# from zhenxun.configs.utils import PluginExtraData, RegisterConfig
# from zhenxun.services.log import logger, logger_
# from zhenxun.utils.enum import PluginType
from .api.logs import router as ws_log_routes
from .api.logs.log_manager import LOG_STORAGE
from .api.tabs.dashboard import router as dashboard_router
from .api.tabs.database import router as database_router
from .api.tabs.main import router as main_router
from .api.tabs.main import ws_router as status_routes
from .api.tabs.manage import router as manage_router
from .api.tabs.manage.chat import ws_router as chat_routes
from .api.tabs.plugin_manage import router as plugin_router
from .api.tabs.plugin_manage.store import router as store_router
from .api.tabs.system import router as system_router
from .auth import router as auth_router
from .public import init_public
# from .api.logs import router as ws_log_routes
# from .api.logs.log_manager import LOG_STORAGE
# from .api.tabs.dashboard import router as dashboard_router
# from .api.tabs.database import router as database_router
# from .api.tabs.main import router as main_router
# from .api.tabs.main import ws_router as status_routes
# from .api.tabs.manage import router as manage_router
# from .api.tabs.manage.chat import ws_router as chat_routes
# from .api.tabs.plugin_manage import router as plugin_router
# from .api.tabs.plugin_manage.store import router as store_router
# from .api.tabs.system import router as system_router
# from .auth import router as auth_router
# from .public import init_public
__plugin_meta__ = PluginMetadata(
name="WebUi",
description="WebUi API",
usage="""
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
configs=[
RegisterConfig(
module="web-ui",
key="username",
value="admin",
help="前端管理用户名",
type=str,
default_value="admin",
),
RegisterConfig(
module="web-ui",
key="password",
value=None,
help="前端管理密码",
type=str,
default_value=None,
),
RegisterConfig(
module="web-ui",
key="secret",
value=secrets.token_urlsafe(32),
help="JWT密钥",
type=str,
default_value=None,
),
],
).dict(),
)
# __plugin_meta__ = PluginMetadata(
# name="WebUi",
# description="WebUi API",
# usage="""
# """.strip(),
# extra=PluginExtraData(
# author="HibiKier",
# version="0.1",
# plugin_type=PluginType.HIDDEN,
# configs=[
# RegisterConfig(
# module="web-ui",
# key="username",
# value="admin",
# help="前端管理用户名",
# type=str,
# default_value="admin",
# ),
# RegisterConfig(
# module="web-ui",
# key="password",
# value=None,
# help="前端管理密码",
# type=str,
# default_value=None,
# ),
# RegisterConfig(
# module="web-ui",
# key="secret",
# value=secrets.token_urlsafe(32),
# help="JWT密钥",
# type=str,
# default_value=None,
# ),
# ],
# ).dict(),
# )
driver = nonebot.get_driver()
# driver = nonebot.get_driver()
gConfig.set_name("web-ui", "web-ui")
# gConfig.set_name("web-ui", "web-ui")
BaseApiRouter = APIRouter(prefix="/zhenxun/api")
# BaseApiRouter = APIRouter(prefix="/zhenxun/api")
BaseApiRouter.include_router(auth_router)
BaseApiRouter.include_router(store_router)
BaseApiRouter.include_router(dashboard_router)
BaseApiRouter.include_router(main_router)
BaseApiRouter.include_router(manage_router)
BaseApiRouter.include_router(database_router)
BaseApiRouter.include_router(plugin_router)
BaseApiRouter.include_router(system_router)
# BaseApiRouter.include_router(auth_router)
# BaseApiRouter.include_router(store_router)
# BaseApiRouter.include_router(dashboard_router)
# BaseApiRouter.include_router(main_router)
# BaseApiRouter.include_router(manage_router)
# BaseApiRouter.include_router(database_router)
# BaseApiRouter.include_router(plugin_router)
# BaseApiRouter.include_router(system_router)
WsApiRouter = APIRouter(prefix="/zhenxun/socket")
# WsApiRouter = APIRouter(prefix="/zhenxun/socket")
WsApiRouter.include_router(ws_log_routes)
WsApiRouter.include_router(status_routes)
WsApiRouter.include_router(chat_routes)
# WsApiRouter.include_router(ws_log_routes)
# WsApiRouter.include_router(status_routes)
# WsApiRouter.include_router(chat_routes)
@driver.on_startup
async def _():
try:
# @driver.on_startup
# async def _():
# try:
async def log_sink(message: str):
loop = None
if not loop:
try:
loop = asyncio.get_running_loop()
except Exception as e:
logger.warning("Web Ui log_sink", e=e)
if not loop:
loop = asyncio.new_event_loop()
loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) # noqa: RUF006
# async def log_sink(message: str):
# loop = None
# if not loop:
# try:
# loop = asyncio.get_running_loop()
# except Exception as e:
# logger.warning("Web Ui log_sink", e=e)
# if not loop:
# loop = asyncio.new_event_loop()
# loop.create_task(LOG_STORAGE.add(message.rstrip("\n")))
logger_.add(
log_sink, colorize=True, filter=default_filter, format=default_format
)
# logger_.add(
# log_sink, colorize=True, filter=default_filter, format=default_format
# )
app: FastAPI = nonebot.get_app()
app.include_router(BaseApiRouter)
app.include_router(WsApiRouter)
await init_public(app)
logger.info("<g>API启动成功</g>", "Web UI")
except Exception as e:
logger.error("<g>API启动失败</g>", "Web UI", e=e)
# app: FastAPI = nonebot.get_app()
# app.include_router(BaseApiRouter)
# app.include_router(WsApiRouter)
# await init_public(app)
# logger.info("<g>API启动成功</g>", "Web UI")
# except Exception as e:
# logger.error("<g>API启动失败</g>", "Web UI", e=e)

View File

@ -27,10 +27,12 @@ class GoodsInfo(Model):
"""每日限购"""
is_passive = fields.BooleanField(default=False)
"""是否为被动道具"""
partition = fields.CharField(255, null=True)
"""分区名称"""
icon = fields.TextField(null=True)
"""图标路径"""
class Meta:
class Meta: # type: ignore
table = "goods_info"
table_description = "商品数据表"
@ -159,4 +161,5 @@ class GoodsInfo(Model):
"ALTER TABLE goods_info ADD icon VARCHAR(255);",
# 删除 daily_purchase_limit 字段
"ALTER TABLE goods_info DROP daily_purchase_limit;",
"ALTER TABLE goods_info ADD partition VARCHAR(255);",
]