diff --git a/resources/template/shop/main.css b/resources/template/shop/main.css
new file mode 100644
index 00000000..73d2c08f
--- /dev/null
+++ b/resources/template/shop/main.css
@@ -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;
+}
diff --git a/resources/template/shop/main.html b/resources/template/shop/main.html
new file mode 100644
index 00000000..82c84a4d
--- /dev/null
+++ b/resources/template/shop/main.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ {{name}}的神秘商店
+
+
+
+
+ {% for data in data_list %}
+
+
+ {{data.partition}}
+
+
+
+

+
+
+
+
+ {% for goods in data['goods_list'] %}
+
+
+
+
+

+
+
+
+
+ {{goods.name}}
+
+
+ {{goods.description}}
+
+
+ {{goods.price}}金币
+
+ 立即购买
+
+ 限购: {{goods.daily_limit}}
+
+
+
+
+ {% endfor %}
+
+

+

+
+
+

+
+
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/template/shop/main.js b/resources/template/shop/main.js
new file mode 100644
index 00000000..e69de29b
diff --git a/resources/template/shop/res/img/1.png b/resources/template/shop/res/img/1.png
new file mode 100644
index 00000000..331ee5ea
Binary files /dev/null and b/resources/template/shop/res/img/1.png differ
diff --git a/resources/template/shop/res/img/2.png b/resources/template/shop/res/img/2.png
new file mode 100644
index 00000000..f8c22f5e
Binary files /dev/null and b/resources/template/shop/res/img/2.png differ
diff --git a/resources/template/shop/res/img/bag1.png b/resources/template/shop/res/img/bag1.png
new file mode 100644
index 00000000..806b3316
Binary files /dev/null and b/resources/template/shop/res/img/bag1.png differ
diff --git a/resources/template/shop/res/img/bag2.png b/resources/template/shop/res/img/bag2.png
new file mode 100644
index 00000000..d02d1b20
Binary files /dev/null and b/resources/template/shop/res/img/bag2.png differ
diff --git a/resources/template/shop/res/img/bag3.png b/resources/template/shop/res/img/bag3.png
new file mode 100644
index 00000000..e8cff836
Binary files /dev/null and b/resources/template/shop/res/img/bag3.png differ
diff --git a/resources/template/shop/res/img/goods-bk.png b/resources/template/shop/res/img/goods-bk.png
new file mode 100644
index 00000000..66978ef4
Binary files /dev/null and b/resources/template/shop/res/img/goods-bk.png differ
diff --git a/resources/template/shop/res/img/head.png b/resources/template/shop/res/img/head.png
new file mode 100644
index 00000000..e240972c
Binary files /dev/null and b/resources/template/shop/res/img/head.png differ
diff --git a/resources/template/shop/res/img/item-bk1.png b/resources/template/shop/res/img/item-bk1.png
new file mode 100644
index 00000000..2a72b00a
Binary files /dev/null and b/resources/template/shop/res/img/item-bk1.png differ
diff --git a/resources/template/shop/res/img/item-bk2.png b/resources/template/shop/res/img/item-bk2.png
new file mode 100644
index 00000000..c6480ce2
Binary files /dev/null and b/resources/template/shop/res/img/item-bk2.png differ
diff --git a/resources/template/shop/res/img/item-bk3.png b/resources/template/shop/res/img/item-bk3.png
new file mode 100644
index 00000000..29543cb1
Binary files /dev/null and b/resources/template/shop/res/img/item-bk3.png differ
diff --git a/resources/template/shop/res/img/qq.png b/resources/template/shop/res/img/qq.png
new file mode 100644
index 00000000..2faf9e7c
Binary files /dev/null and b/resources/template/shop/res/img/qq.png differ
diff --git a/resources/template/shop/res/img/split.png b/resources/template/shop/res/img/split.png
new file mode 100644
index 00000000..129e05d9
Binary files /dev/null and b/resources/template/shop/res/img/split.png differ
diff --git a/resources/template/shop/res/img/title-bk.png b/resources/template/shop/res/img/title-bk.png
new file mode 100644
index 00000000..6c49f190
Binary files /dev/null and b/resources/template/shop/res/img/title-bk.png differ
diff --git a/zhenxun/builtin_plugins/shop/__init__.py b/zhenxun/builtin_plugins/shop/__init__.py
index 104ae88e..71c7f713 100644
--- a/zhenxun/builtin_plugins/shop/__init__.py
+++ b/zhenxun/builtin_plugins/shop/__init__.py
@@ -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()
diff --git a/zhenxun/builtin_plugins/shop/_data_source.py b/zhenxun/builtin_plugins/shop/_data_source.py
index ec8c3d97..56069b56 100644
--- a/zhenxun/builtin_plugins/shop/_data_source.py
+++ b/zhenxun/builtin_plugins/shop/_data_source.py
@@ -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
diff --git a/zhenxun/builtin_plugins/shop/config.py b/zhenxun/builtin_plugins/shop/config.py
new file mode 100644
index 00000000..7fa13a5d
--- /dev/null
+++ b/zhenxun/builtin_plugins/shop/config.py
@@ -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"
diff --git a/zhenxun/builtin_plugins/shop/html_image.py b/zhenxun/builtin_plugins/shop/html_image.py
new file mode 100644
index 00000000..61801415
--- /dev/null
+++ b/zhenxun/builtin_plugins/shop/html_image.py
@@ -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,
+ )
diff --git a/zhenxun/builtin_plugins/shop/normal_image.py b/zhenxun/builtin_plugins/shop/normal_image.py
new file mode 100644
index 00000000..76420a24
--- /dev/null
+++ b/zhenxun/builtin_plugins/shop/normal_image.py
@@ -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()
diff --git a/zhenxun/builtin_plugins/web_ui/__init__.py b/zhenxun/builtin_plugins/web_ui/__init__.py
index bbea8708..87fa0ded 100644
--- a/zhenxun/builtin_plugins/web_ui/__init__.py
+++ b/zhenxun/builtin_plugins/web_ui/__init__.py
@@ -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("API启动成功", "Web UI")
- except Exception as e:
- logger.error("API启动失败", "Web UI", e=e)
+# app: FastAPI = nonebot.get_app()
+# app.include_router(BaseApiRouter)
+# app.include_router(WsApiRouter)
+# await init_public(app)
+# logger.info("API启动成功", "Web UI")
+# except Exception as e:
+# logger.error("API启动失败", "Web UI", e=e)
diff --git a/zhenxun/models/goods_info.py b/zhenxun/models/goods_info.py
index 4560d903..4a66c1cf 100644
--- a/zhenxun/models/goods_info.py
+++ b/zhenxun/models/goods_info.py
@@ -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);",
]