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

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 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.services.log import logger
from zhenxun.utils.depends import UserName from zhenxun.utils.depends import UserName
from zhenxun.utils.enum import BlockType, PluginType from zhenxun.utils.enum import BlockType, PluginType
@ -45,6 +45,14 @@ __plugin_meta__ = PluginMetadata(
plugin_type=PluginType.NORMAL, plugin_type=PluginType.NORMAL,
menu_type="商店", menu_type="商店",
limits=[BaseBlock(check_type=BlockType.GROUP)], limits=[BaseBlock(check_type=BlockType.GROUP)],
configs=[
RegisterConfig(
key="style",
value="zhenxun",
help="商店样式类型,[normal, zhenxun]",
default_value="zhenxun",
)
],
).dict(), ).dict(),
) )
@ -108,7 +116,7 @@ _matcher.shortcut(
@_matcher.assign("$main") @_matcher.assign("$main")
async def _(session: Uninfo, arparma: Arparma): 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) logger.info("查看商店", arparma.header_result, session=session)
await MessageUtils.build_message(image).send() 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 nonebot_plugin_uninfo import Uninfo
from pydantic import BaseModel, create_model from pydantic import BaseModel, create_model
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.friend_user import FriendUser from zhenxun.models.friend_user import FriendUser
from zhenxun.models.goods_info import GoodsInfo from zhenxun.models.goods_info import GoodsInfo
from zhenxun.models.group_member_info import GroupInfoUser 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.models.user_props_log import UserPropsLog
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.enum import GoldHandle, PropHandle 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 from zhenxun.utils.platform import PlatformUtils
ICON_PATH = IMAGE_PATH / "shop_icon" from .config import ICON_PATH, PLATFORM_PATH, base_config
from .html_image import html_image
RANK_ICON_PATH = IMAGE_PATH / "_icon" from .normal_image import normal_image
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",
}
class Goods(BaseModel): class Goods(BaseModel):
@ -137,6 +129,12 @@ async def gold_rank(
class ShopManage: class ShopManage:
uuid2goods: dict[str, Goods] = {} # noqa: RUF012 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 @classmethod
def __build_params( def __build_params(
cls, cls,
@ -484,200 +482,3 @@ class ShopManage:
""" """
user = await UserConsole.get_user(user_id, platform) user = await UserConsole.get_user(user_id, platform)
return user.gold 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 asyncio
import secrets # import secrets
from fastapi import APIRouter, FastAPI # from fastapi import APIRouter, FastAPI
import nonebot # import nonebot
from nonebot.log import default_filter, default_format # from nonebot.log import default_filter, default_format
from nonebot.plugin import PluginMetadata # from nonebot.plugin import PluginMetadata
from zhenxun.configs.config import Config as gConfig # from zhenxun.configs.config import Config as gConfig
from zhenxun.configs.utils import PluginExtraData, RegisterConfig # from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger, logger_ # from zhenxun.services.log import logger, logger_
from zhenxun.utils.enum import PluginType # from zhenxun.utils.enum import PluginType
from .api.logs import router as ws_log_routes # from .api.logs import router as ws_log_routes
from .api.logs.log_manager import LOG_STORAGE # from .api.logs.log_manager import LOG_STORAGE
from .api.tabs.dashboard import router as dashboard_router # from .api.tabs.dashboard import router as dashboard_router
from .api.tabs.database import router as database_router # from .api.tabs.database import router as database_router
from .api.tabs.main import router as main_router # from .api.tabs.main import router as main_router
from .api.tabs.main import ws_router as status_routes # from .api.tabs.main import ws_router as status_routes
from .api.tabs.manage import router as manage_router # from .api.tabs.manage import router as manage_router
from .api.tabs.manage.chat import ws_router as chat_routes # 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 import router as plugin_router
from .api.tabs.plugin_manage.store import router as store_router # from .api.tabs.plugin_manage.store import router as store_router
from .api.tabs.system import router as system_router # from .api.tabs.system import router as system_router
from .auth import router as auth_router # from .auth import router as auth_router
from .public import init_public # from .public import init_public
__plugin_meta__ = PluginMetadata( # __plugin_meta__ = PluginMetadata(
name="WebUi", # name="WebUi",
description="WebUi API", # description="WebUi API",
usage=""" # usage="""
""".strip(), # """.strip(),
extra=PluginExtraData( # extra=PluginExtraData(
author="HibiKier", # author="HibiKier",
version="0.1", # version="0.1",
plugin_type=PluginType.HIDDEN, # plugin_type=PluginType.HIDDEN,
configs=[ # configs=[
RegisterConfig( # RegisterConfig(
module="web-ui", # module="web-ui",
key="username", # key="username",
value="admin", # value="admin",
help="前端管理用户名", # help="前端管理用户名",
type=str, # type=str,
default_value="admin", # default_value="admin",
), # ),
RegisterConfig( # RegisterConfig(
module="web-ui", # module="web-ui",
key="password", # key="password",
value=None, # value=None,
help="前端管理密码", # help="前端管理密码",
type=str, # type=str,
default_value=None, # default_value=None,
), # ),
RegisterConfig( # RegisterConfig(
module="web-ui", # module="web-ui",
key="secret", # key="secret",
value=secrets.token_urlsafe(32), # value=secrets.token_urlsafe(32),
help="JWT密钥", # help="JWT密钥",
type=str, # type=str,
default_value=None, # default_value=None,
), # ),
], # ],
).dict(), # ).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(auth_router)
BaseApiRouter.include_router(store_router) # BaseApiRouter.include_router(store_router)
BaseApiRouter.include_router(dashboard_router) # BaseApiRouter.include_router(dashboard_router)
BaseApiRouter.include_router(main_router) # BaseApiRouter.include_router(main_router)
BaseApiRouter.include_router(manage_router) # BaseApiRouter.include_router(manage_router)
BaseApiRouter.include_router(database_router) # BaseApiRouter.include_router(database_router)
BaseApiRouter.include_router(plugin_router) # BaseApiRouter.include_router(plugin_router)
BaseApiRouter.include_router(system_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(ws_log_routes)
WsApiRouter.include_router(status_routes) # WsApiRouter.include_router(status_routes)
WsApiRouter.include_router(chat_routes) # WsApiRouter.include_router(chat_routes)
@driver.on_startup # @driver.on_startup
async def _(): # async def _():
try: # try:
async def log_sink(message: str): # async def log_sink(message: str):
loop = None # loop = None
if not loop: # if not loop:
try: # try:
loop = asyncio.get_running_loop() # loop = asyncio.get_running_loop()
except Exception as e: # except Exception as e:
logger.warning("Web Ui log_sink", e=e) # logger.warning("Web Ui log_sink", e=e)
if not loop: # if not loop:
loop = asyncio.new_event_loop() # loop = asyncio.new_event_loop()
loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) # noqa: RUF006 # loop.create_task(LOG_STORAGE.add(message.rstrip("\n")))
logger_.add( # logger_.add(
log_sink, colorize=True, filter=default_filter, format=default_format # log_sink, colorize=True, filter=default_filter, format=default_format
) # )
app: FastAPI = nonebot.get_app() # app: FastAPI = nonebot.get_app()
app.include_router(BaseApiRouter) # app.include_router(BaseApiRouter)
app.include_router(WsApiRouter) # app.include_router(WsApiRouter)
await init_public(app) # await init_public(app)
logger.info("<g>API启动成功</g>", "Web UI") # logger.info("<g>API启动成功</g>", "Web UI")
except Exception as e: # except Exception as e:
logger.error("<g>API启动失败</g>", "Web UI", e=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) is_passive = fields.BooleanField(default=False)
"""是否为被动道具""" """是否为被动道具"""
partition = fields.CharField(255, null=True)
"""分区名称"""
icon = fields.TextField(null=True) icon = fields.TextField(null=True)
"""图标路径""" """图标路径"""
class Meta: class Meta: # type: ignore
table = "goods_info" table = "goods_info"
table_description = "商品数据表" table_description = "商品数据表"
@ -159,4 +161,5 @@ class GoodsInfo(Model):
"ALTER TABLE goods_info ADD icon VARCHAR(255);", "ALTER TABLE goods_info ADD icon VARCHAR(255);",
# 删除 daily_purchase_limit 字段 # 删除 daily_purchase_limit 字段
"ALTER TABLE goods_info DROP daily_purchase_limit;", "ALTER TABLE goods_info DROP daily_purchase_limit;",
"ALTER TABLE goods_info ADD partition VARCHAR(255);",
] ]