release 1.31

This commit is contained in:
Developer
2026-04-22 10:09:18 +08:00
parent 32f5aa53fa
commit ac62f5961e
212 changed files with 30003 additions and 1350 deletions

View File

@@ -1,113 +0,0 @@
# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFilter
import os
def add_border_and_resize(input_path, output_path, target_size=(1920, 1080), border_color=(255, 255, 255), border_width=20):
img = Image.open(input_path)
original_width, original_height = img.size
aspect_ratio = original_width / original_height
target_aspect = target_size[0] / target_size[1]
if aspect_ratio > target_aspect:
new_width = target_size[0] - border_width * 2
new_height = int(new_width / aspect_ratio)
else:
new_height = target_size[1] - border_width * 2
new_width = int(new_height * aspect_ratio)
img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
output_img = Image.new('RGB', target_size, border_color)
paste_x = (target_size[0] - new_width) // 2
paste_y = (target_size[1] - new_height) // 2
shadow_offset = 8
shadow_rect = [
paste_x + shadow_offset,
paste_y + shadow_offset,
paste_x + new_width + shadow_offset,
paste_y + new_height + shadow_offset
]
shadow_layer = Image.new('RGBA', target_size, (0, 0, 0, 0))
shadow_draw = ImageDraw.Draw(shadow_layer)
shadow_draw.rectangle(shadow_rect, fill=(0, 0, 0, 60))
shadow_blurred = shadow_layer.filter(ImageFilter.GaussianBlur(radius=10))
final_image = Image.new('RGB', target_size, border_color)
if shadow_blurred.mode == 'RGBA':
final_image.paste(shadow_blurred, (0, 0), shadow_blurred)
else:
final_rgb_shadow = shadow_blurred.convert('RGB')
final_image.paste(final_rgb_shadow, (0, 0))
final_image.paste(img_resized, (paste_x, paste_y))
draw = ImageDraw.Draw(final_image)
rect = [paste_x - 2, paste_y - 2, paste_x + new_width + 2, paste_y + new_height + 2]
for i in range(3):
offset = i + 1
current_rect = [
rect[0] - offset,
rect[1] - offset,
rect[2] + offset,
rect[3] + offset
]
draw.rectangle(current_rect, outline=(180, 200, 220))
for i in range(border_width):
current_rect = [
rect[0] - i - 3,
rect[1] - i - 3,
rect[2] + i + 3,
rect[3] + i + 3
]
alpha = int(255 * (1 - i / border_width))
color_with_alpha = (200 + i*2, 210 + i*2, 230 + i*2)
draw.rectangle(current_rect, outline=color_with_alpha)
final_image.save(output_path, quality=95)
print(f'[OK] Processed: {os.path.basename(input_path)} -> {output_path}')
return True
def main():
input_files = [
r'e:\project\flutter\f\mom_kitchen\docs\design\1.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\2.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\3.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\4.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\5.jpg',
]
output_dir = r'e:\project\flutter\f\mom_kitchen\docs\design\processed'
os.makedirs(output_dir, exist_ok=True)
print('='*50)
print('Image Border Processing Script')
print(f'Target Size: 1920 x 1080 px')
print(f'Processing: {len(input_files)} images')
print('='*50)
print()
success_count = 0
for input_file in input_files:
if os.path.exists(input_file):
filename = os.path.basename(input_file)
output_path = os.path.join(output_dir, f'bordered_{filename}')
try:
add_border_and_resize(input_file, output_path)
success_count += 1
except Exception as e:
print(f'[ERROR] Failed to process: {filename} - {str(e)}')
import traceback
traceback.print_exc()
else:
print(f'[WARNING] File not found: {input_file}')
print()
print('='*50)
print(f'[DONE] Processed {success_count}/{len(input_files)} images successfully!')
print(f'Output directory: {output_dir}')
print('='*50)
if __name__ == '__main__':
main()

View File

@@ -1,111 +0,0 @@
# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFilter
import os
def add_border_and_resize(input_path, output_path, target_size=(1080, 1920), border_color=(255, 255, 255), border_width=20):
img = Image.open(input_path)
original_width, original_height = img.size
aspect_ratio = original_width / original_height
target_aspect = target_size[0] / target_size[1]
if aspect_ratio > target_aspect:
new_width = target_size[0] - border_width * 2
new_height = int(new_width / aspect_ratio)
else:
new_height = target_size[1] - border_width * 2
new_width = int(new_height * aspect_ratio)
img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
paste_x = (target_size[0] - new_width) // 2
paste_y = (target_size[1] - new_height) // 2
shadow_offset = 8
shadow_rect = [
paste_x + shadow_offset,
paste_y + shadow_offset,
paste_x + new_width + shadow_offset,
paste_y + new_height + shadow_offset
]
shadow_layer = Image.new('RGBA', target_size, (0, 0, 0, 0))
shadow_draw = ImageDraw.Draw(shadow_layer)
shadow_draw.rectangle(shadow_rect, fill=(0, 0, 0, 60))
shadow_blurred = shadow_layer.filter(ImageFilter.GaussianBlur(radius=10))
final_image = Image.new('RGB', target_size, border_color)
if shadow_blurred.mode == 'RGBA':
final_image.paste(shadow_blurred, (0, 0), shadow_blurred)
else:
final_rgb_shadow = shadow_blurred.convert('RGB')
final_image.paste(final_rgb_shadow, (0, 0))
final_image.paste(img_resized, (paste_x, paste_y))
draw = ImageDraw.Draw(final_image)
rect = [paste_x - 2, paste_y - 2, paste_x + new_width + 2, paste_y + new_height + 2]
for i in range(3):
offset = i + 1
current_rect = [
rect[0] - offset,
rect[1] - offset,
rect[2] + offset,
rect[3] + offset
]
draw.rectangle(current_rect, outline=(180, 200, 220))
for i in range(border_width):
current_rect = [
rect[0] - i - 3,
rect[1] - i - 3,
rect[2] + i + 3,
rect[3] + i + 3
]
color_with_alpha = (200 + i*2, 210 + i*2, 230 + i*2)
draw.rectangle(current_rect, outline=color_with_alpha)
final_image.save(output_path, quality=95)
print(f'[OK] Processed: {os.path.basename(input_path)} -> {output_path}')
return True
def main():
input_files = [
r'e:\project\flutter\f\mom_kitchen\docs\design\1.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\2.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\3.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\4.jpg',
r'e:\project\flutter\f\mom_kitchen\docs\design\5.jpg',
]
output_dir = r'e:\project\flutter\f\mom_kitchen\docs\design\processed\portrait'
os.makedirs(output_dir, exist_ok=True)
print('='*50)
print('Image Border Processing Script (Portrait Mode)')
print(f'Target Size: 1080 x 1920 px (Vertical)')
print(f'Processing: {len(input_files)} images')
print('='*50)
print()
success_count = 0
for input_file in input_files:
if os.path.exists(input_file):
filename = os.path.basename(input_file)
output_path = os.path.join(output_dir, f'bordered_portrait_{filename}')
try:
add_border_and_resize(input_file, output_path)
success_count += 1
except Exception as e:
print(f'[ERROR] Failed to process: {filename} - {str(e)}')
import traceback
traceback.print_exc()
else:
print(f'[WARNING] File not found: {input_file}')
print()
print('='*50)
print(f'[DONE] Processed {success_count}/{len(input_files)} images successfully!')
print(f'Output directory: {output_dir}')
print('='*50)
if __name__ == '__main__':
main()

View File

@@ -1,64 +0,0 @@
// 2026-04-13 | test_detail_id.dart | 详情页ID测试 | 验证详情页API返回数据
// 运行: dart run scripts/test_detail_id.dart
import 'dart:convert';
import 'dart:io';
const String baseUrl = 'https://eat.wktyl.com/api';
Future<void> main() async {
print('=== 详情页ID测试 ===\n');
// 测试ID 32390从日志中获取
const testId = 32390;
print('📡 测试ID: $testId');
await testDetailApi(testId);
// 再测试一个有效的ID
print('\n📡 测试另一个ID: 46518');
await testDetailApi(46518);
}
Future<void> testDetailApi(int id) async {
final client = HttpClient();
try {
final url = Uri.parse('$baseUrl/api.php?act=full&id=$id&_refresh=1');
print(' 请求URL: $url');
final request = await client.getUrl(url);
request.headers.set('Accept', 'application/json');
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
print(' 状态码: ${response.statusCode}');
print(' 响应长度: ${body.length}');
if (body.isEmpty) {
print(' ❌ 响应体为空');
return;
}
final json = jsonDecode(body) as Map<String, dynamic>;
print(' code: ${json['code']}');
print(' message: ${json['message']}');
final data = json['data'] as Map<String, dynamic>?;
if (data == null) {
print(' ❌ data字段为null');
return;
}
print(' ✅ 数据加载成功');
print(' id: ${data['id']}');
print(' title: ${data['title']}');
print(' pic_id: ${data['pic_id']}');
print(' cover: ${data['cover']}');
print(' intro: ${(data['intro'] as String?)?.substring(0, (data['intro'] as String?)?.length.clamp(0, 50) ?? 0)}...');
} catch (e) {
print(' ❌ 请求错误: $e');
} finally {
client.close();
}
}

View File

@@ -1,146 +0,0 @@
// 2026-04-14 | test_discover_image.dart | Discover图片数据验证 | 验证api_discover.php返回的cover/picId字段
// 运行: dart run scripts/test_discover_image.dart
import 'dart:convert';
import 'dart:io';
const String baseUrl = 'https://eat.wktyl.com/api';
Future<void> main() async {
print('=== Discover 图片数据验证 ===\n');
await testDiscoverApi();
await testDetailApi();
await testImageUrl();
}
Future<void> testDiscoverApi() async {
print('📡 测试 api_discover.php 接口');
final client = HttpClient();
try {
final url = Uri.parse('$baseUrl/api_discover.php?total=10');
print(' 请求URL: $url');
final request = await client.getUrl(url);
request.headers.set('Accept', 'application/json');
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
if (body.isEmpty) {
print(' ❌ 响应体为空');
return;
}
final json = jsonDecode(body) as Map<String, dynamic>;
print(' code: ${json['code']}');
final data = json['data'] as Map<String, dynamic>?;
if (data == null) {
print(' ❌ data字段为null');
return;
}
final recipes = data['recipes'] as List?;
if (recipes == null || recipes.isEmpty) {
print(' ❌ recipes为空');
return;
}
print(' 菜谱数量: ${recipes.length}');
print('');
for (int i = 0; i < recipes.length && i < 5; i++) {
final recipe = recipes[i] as Map<String, dynamic>;
print(' ─── 菜谱 #${i + 1} ───');
print(' id: ${recipe['id']}');
print(' title: ${recipe['title']}');
print(' cover: ${recipe['cover']}');
print(' pic_id: ${recipe['pic_id']}');
print(' picId: ${recipe['picId']}');
print(' pic: ${recipe['pic']}');
final allKeys = recipe.keys.toList();
print(' 所有字段: $allKeys');
final cover = recipe['cover'] ?? '';
if (cover.isNotEmpty) {
final regex = RegExp(r'/pic/(\d+)[ab]?\.(jpg|png|webp)$');
final match = regex.firstMatch(cover.toString());
if (match != null) {
print(' ✅ 从cover提取picId: ${match.group(1)}');
} else {
print(' ⚠️ cover不匹配picId正则: $cover');
}
} else {
print(' ❌ cover为空');
}
print('');
}
} catch (e) {
print(' ❌ 请求失败: $e');
} finally {
client.close();
}
}
Future<void> testDetailApi() async {
print('\n📡 测试 api.php?act=detail 接口对比picId字段');
final client = HttpClient();
try {
final url = Uri.parse('$baseUrl/api.php?act=detail&id=32390');
print(' 请求URL: $url');
final request = await client.getUrl(url);
request.headers.set('Accept', 'application/json');
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
if (body.isEmpty) return;
final json = jsonDecode(body) as Map<String, dynamic>;
final data = json['data'] as Map<String, dynamic>?;
if (data == null) return;
print(' id: ${data['id']}');
print(' title: ${data['title']}');
print(' cover: ${data['cover']}');
print(' pic_id: ${data['pic_id']}');
print(' picId: ${data['picId']}');
print(' pic: ${data['pic']}');
final allKeys = data.keys.toList();
print(' 所有字段: $allKeys');
} catch (e) {
print(' ❌ 请求失败: $e');
} finally {
client.close();
}
}
Future<void> testImageUrl() async {
print('\n📡 测试图片URL可访问性');
final client = HttpClient();
try {
final testUrls = [
'https://eat.wktyl.com/api/assets/pic/32390a.jpg',
'https://eat.wktyl.com/api/assets/pic/32390b.jpg',
'https://eat.wktyl.com/api/assets/pic/32390.jpg',
];
for (final url in testUrls) {
try {
final request = await client.getUrl(Uri.parse(url));
final response = await request.close();
print(
' $url${response.statusCode} (${response.contentLength} bytes)',
);
} catch (e) {
print(' $url → ❌ $e');
}
}
} finally {
client.close();
}
}

View File

@@ -1,156 +0,0 @@
// 2026-04-20 | test_filter_steps.dart | 动态筛选接口测试 | 验证filter_steps动态筛选+api_filter分类
// 运行: dart run scripts/test_filter_steps.dart
import 'dart:convert';
import 'dart:io';
const String baseUrl = 'https://eat.wktyl.com/api';
Future<void> main() async {
print('=== 动态筛选接口测试 ===\n');
print('━━━ 1. api_filter: 获取菜谱大类 ━━━');
final mainCats = await getJson('api_filter.php', {'act': 'recipe_main_categories'});
if (mainCats != null) {
final list = mainCats['data']?['list'] as List? ?? [];
print(' 大类数量: ${list.length}');
for (final c in list) {
final m = c as Map;
print(' 📂 ${m['name']} (ID:${m['id']}): ${m['recipe_count']}');
}
}
print('\n━━━ 2. api_filter: 获取中国菜子类 ━━━');
final subCats = await getJson('api_filter.php', {'act': 'recipe_sub_categories', 'parent_id': '12'});
if (subCats != null) {
final list = subCats['data']?['list'] as List? ?? [];
print(' 子类数量: ${list.length}');
for (final c in list.take(5)) {
final m = c as Map;
print(' 📂 ${m['name']} (ID:${m['id']}): ${m['recipe_count']}');
}
}
print('\n━━━ 3. filter_steps: 无筛选条件 ━━━');
final fs1 = await fetchFilterSteps();
if (fs1 != null) {
print(' 匹配菜谱数: ${fs1['matched_count']}');
final opts = fs1['available_options'] as List? ?? [];
print(' 可用分类数: ${opts.length}');
for (final o in opts.take(3)) {
final m = o as Map;
print(' 📂 ${m['name']}: ${m['count']}道 (${(m['children'] as List?)?.length ?? 0}子类)');
}
final tags1 = fs1['available_tags'] as List? ?? [];
print(' 可用标签数: ${tags1.length}');
for (final t in tags1.take(5)) {
final tm = t as Map;
print(' 🏷️ ${tm['name']}: ${tm['count']}');
}
}
print('\n━━━ 4. filter_steps: 选分类[12] ━━━');
final fs2 = await fetchFilterSteps(categories: [12]);
if (fs2 != null) {
print(' 匹配菜谱数: ${fs2['matched_count']}');
final tags2 = fs2['available_tags'] as List? ?? [];
print(' 可用标签数: ${tags2.length}');
for (final t in tags2.take(5)) {
final tm = t as Map;
print(' 🏷️ ${tm['name']}: ${tm['count']}');
}
}
print('\n━━━ 5. filter_steps: 分类[12]+标签[1] ━━━');
final fs3 = await fetchFilterSteps(categories: [12], tags: [1]);
if (fs3 != null) {
print(' 匹配菜谱数: ${fs3['matched_count']}');
final tags3 = fs3['available_tags'] as List? ?? [];
print(' 可用标签数: ${tags3.length}');
for (final t in tags3.take(5)) {
final tm = t as Map;
print(' 🏷️ ${tm['name']}: ${tm['count']}');
}
}
print('\n━━━ 6. 验证动态筛选效果 ━━━');
final c1 = fs1?['matched_count'] ?? 0;
final c2 = fs2?['matched_count'] ?? 0;
final c3 = fs3?['matched_count'] ?? 0;
print(' 无筛选: $c1');
print(' 选分类: $c2');
print(' 分类+标签: $c3');
if (c2 < c1 && c3 < c2) {
print(' ✅ 动态筛选正常:选项越多,匹配越少');
} else if (c2 <= c1 && c3 <= c2) {
print(' ⚠️ 动态筛选部分正常');
} else {
print(' ❌ 动态筛选异常');
}
print('\n━━━ 7. filter_apply: 获取推荐菜谱 ━━━');
final applyResult = await fetchFilterApply(categories: [12], tags: [1], count: 3);
if (applyResult != null) {
final recipes = applyResult['recipes'] as List? ?? [];
print(' 返回菜谱数: ${recipes.length}');
print(' 总匹配数: ${applyResult['total_matched']}');
for (final r in recipes) {
final rm = r as Map;
print(' 🍳 ${rm['title']} (ID: ${rm['id']})');
}
}
print('\n=== 测试完成 ===');
}
Future<Map<String, dynamic>?> getJson(String endpoint, Map<String, String> params) async {
try {
final uri = Uri.parse('$baseUrl/$endpoint').replace(queryParameters: params);
final response = await HttpClient()
.getUrl(uri)
.then((r) => r.close())
.timeout(const Duration(seconds: 15));
final body = await response.transform(utf8.decoder).join();
return json.decode(body) as Map<String, dynamic>;
} catch (e) {
print(' ❌ 请求失败: $e');
return null;
}
}
Future<Map<String, dynamic>?> fetchFilterSteps({
List<int>? categories,
List<int>? tags,
}) async {
final params = <String, String>{'act': 'filter_steps'};
if (categories != null && categories.isNotEmpty) {
params['category'] = categories.join(',');
}
if (tags != null && tags.isNotEmpty) {
params['tag'] = tags.join(',');
}
final data = await getJson('api_what_to_eat.php', params);
if (data != null && data['code'] == 200) {
return data['data'] as Map<String, dynamic>?;
}
return null;
}
Future<Map<String, dynamic>?> fetchFilterApply({
List<int>? categories,
List<int>? tags,
int count = 5,
}) async {
final params = <String, String>{'act': 'filter_apply', 'count': '$count'};
if (categories != null && categories.isNotEmpty) {
params['category'] = categories.join(',');
}
if (tags != null && tags.isNotEmpty) {
params['tag'] = tags.join(',');
}
final data = await getJson('api_what_to_eat.php', params);
if (data != null && data['code'] == 200) {
return data['data'] as Map<String, dynamic>?;
}
return null;
}

View File

@@ -1,55 +0,0 @@
/*
* 文件: test_ingredient_cache.dart
* 名称: 食材缓存测试脚本
* 作用: 验证食材缓存服务的读写功能
* 创建: 2026-04-14
*/
import 'dart:io';
void main() async {
print('========================================');
print('食材缓存测试脚本');
print('========================================\n');
// 测试 API 接口
await testIngredientApi();
print('\n========================================');
print('测试完成');
print('========================================');
}
Future<void> testIngredientApi() async {
final testIds = [849, 1248, 1, 1206, 1209];
for (final id in testIds) {
print('\n--- 测试食材 ID: $id ---');
try {
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('https://eat.wktyl.com/api/api.php?act=ingredient_detail&id=$id'),
);
final response = await request.close();
final body = await response.transform(const SystemEncoding().decoder).join();
print('状态码: ${response.statusCode}');
print('响应长度: ${body.length} 字符');
if (body.isNotEmpty && body.startsWith('{')) {
print('✅ API 响应正常');
} else {
print('❌ API 响应异常');
}
client.close();
} catch (e) {
print('❌ 请求失败: $e');
}
// 延迟避免请求过快
await Future.delayed(const Duration(milliseconds: 500));
}
}