feat: 实现减脂体重管理App完整功能

- 实现拍照识别食物功能(集成大语言模型视觉能力)
- 实现智能对话功能(集成大语言模型流式输出)
- 实现食物记录和卡路里管理功能
- 实现体重记录和统计功能
- 实现健康数据管理页面
- 配置数据库表结构(用户、食物记录、体重记录)
- 实现Express后端API路由
- 配置Tab导航和前端页面
- 采用健康运动配色方案
This commit is contained in:
jaystar
2026-02-02 15:17:50 +08:00
commit 28c4d7b3b4
82 changed files with 21891 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
const validName = require('./rule')
const plugin = {
rules: {
'valid-name': validName,
},
};
module.exports = plugin

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
const names = require('./names')
const v5OnlyNames = require('./v5-only-names')
const v5OnlyNamesSet = new Set(v5OnlyNames)
const DEFAULTS = {
whitelist: names,
componentName: 'FontAwesome6',
attributeName: 'name',
};
function getJSXAttribute(openingElement, attrName) {
return openingElement.attributes.find(
(attr) =>
attr &&
attr.type === 'JSXAttribute' &&
attr.name &&
attr.name.name === attrName
);
}
function getStringLiteralValue(attribute) {
if (!attribute || !attribute.value) return null;
const val = attribute.value;
// <Comp name="hello" />
if (val.type === 'Literal' && typeof val.value === 'string') {
return val.value;
}
// <Comp name={'hello'} />
if (
val.type === 'JSXExpressionContainer' &&
val.expression &&
val.expression.type === 'Literal' &&
typeof val.expression.value === 'string'
) {
return val.expression.value;
}
// <Comp name={`hello`} /> template literal without expressions
if (
val.type === 'JSXExpressionContainer' &&
val.expression &&
val.expression.type === 'TemplateLiteral' &&
val.expression.expressions.length === 0
) {
return val.expression.quasis[0]?.value?.cooked ?? null;
}
return null;
}
const replacements = {
'plus-circle': 'circle-plus',
}
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Ensure FontAwesome6 name prop is a string literal in whitelist',
recommended: false,
},
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
whitelist: {
type: 'array',
items: { type: 'string' },
default: DEFAULTS.whitelist,
},
componentName: {
type: 'string',
default: DEFAULTS.componentName,
},
attributeName: {
type: 'string',
default: DEFAULTS.attributeName,
},
caseSensitive: {
type: 'boolean',
default: true
}
},
},
],
messages: {
invalidName:
'{{componentName}} 中不存在图标 {{name}}{{suggestion}}',
},
},
create(context) {
const options = context.options && context.options[0] ? context.options[0] : {};
const componentName = options.componentName || DEFAULTS.componentName;
const attributeName = options.attributeName || DEFAULTS.attributeName;
const caseSensitive = options.caseSensitive ?? true;
const whitelistRaw = Array.isArray(options.whitelist)
? options.whitelist
: DEFAULTS.whitelist;
const normalize = (s) =>
caseSensitive ? String(s) : String(s).toLowerCase();
const whitelist = new Set(whitelistRaw.map(normalize));
function isTargetComponent(node) {
// Supports: <FontAwesome6 />, <NS.FontAwesome6 />
const nameNode = node.name;
if (!nameNode) return false;
if (nameNode.type === 'JSXIdentifier') {
return nameNode.name === componentName;
}
if (nameNode.type === 'JSXMemberExpression') {
// e.g., UI.FontAwesome6
let base = nameNode;
while (base.type === 'JSXMemberExpression') {
if (base.property && base.property.name === componentName) {
return true;
}
base = base.object;
}
}
return false;
}
return {
JSXOpeningElement(opening) {
if (!isTargetComponent(opening)) return;
const attrNode = getJSXAttribute(opening, attributeName);
if (!attrNode) return;
const literal = getStringLiteralValue(attrNode);
// Only lint when it's a string literal
if (literal == null) return;
const normalized = normalize(literal);
if (!whitelist.has(normalized)) {
context.report({
node: attrNode.value || attrNode,
messageId: 'invalidName',
data: {
componentName,
name: literal,
suggestion: getSuggestion(normalized, literal),
},
});
}
},
};
},
};
function getSuggestion(name, originalName) {
if (replacements[name]) {
return `请更换为 ${replacements[name]}`
}
if (v5OnlyNamesSet.has(name)) {
return `${originalName} 只能和 FontAwesome5 组件一起使用`
}
return '请更换为其他图标'
}

View File

@@ -0,0 +1,388 @@
const v5names = [
"acquisitions-incorporated",
"behance-square",
"dribbble-square",
"facebook-square",
"font-awesome-alt",
"font-awesome-flag",
"font-awesome-logo-full",
"git-square",
"github-square",
"google-plus-square",
"hacker-news-square",
"innosoft",
"instagram-square",
"js-square",
"lastfm-square",
"medium-m",
"odnoklassniki-square",
"penny-arcade",
"pied-piper-square",
"pinterest-square",
"reddit-square",
"slack-hash",
"snapchat-ghost",
"snapchat-square",
"steam-square",
"telegram-plane",
"tripadvisor",
"tumblr-square",
"twitter-square",
"viadeo-square",
"vimeo-square",
"whatsapp-square",
"xing-square",
"youtube-square",
"angry",
"arrow-alt-circle-down",
"arrow-alt-circle-left",
"arrow-alt-circle-right",
"arrow-alt-circle-up",
"calendar-alt",
"calendar-times",
"caret-square-down",
"caret-square-left",
"caret-square-right",
"caret-square-up",
"check-circle",
"check-square",
"comment-alt",
"dizzy",
"dot-circle",
"edit",
"file-alt",
"file-archive",
"flushed",
"frown-open",
"frown",
"grimace",
"grin-alt",
"grin-beam-sweat",
"grin-beam",
"grin-hearts",
"grin-squint-tears",
"grin-squint",
"grin-stars",
"grin-tears",
"grin-tongue-squint",
"grin-tongue-wink",
"grin-tongue",
"grin-wink",
"grin",
"hand-paper",
"hand-rock",
"hdd",
"kiss-beam",
"kiss-wink-heart",
"kiss",
"laugh-beam",
"laugh-squint",
"laugh-wink",
"laugh",
"list-alt",
"meh-blank",
"meh-rolling-eyes",
"meh",
"minus-square",
"money-bill-alt",
"pause-circle",
"play-circle",
"plus-square",
"question-circle",
"sad-cry",
"sad-tear",
"save",
"share-square",
"smile-beam",
"smile-wink",
"smile",
"sticky-note",
"stop-circle",
"surprise",
"times-circle",
"tired",
"trash-alt",
"user-circle",
"window-close",
"ad",
"adjust",
"air-freshener",
"allergies",
"ambulance",
"american-sign-language-interpreting",
"angle-double-down",
"angle-double-left",
"angle-double-right",
"angle-double-up",
"apple-alt",
"archive",
"arrow-circle-down",
"arrow-circle-left",
"arrow-circle-right",
"arrow-circle-up",
"arrows-alt-h",
"arrows-alt-v",
"arrows-alt",
"assistive-listening-systems",
"atlas",
"backspace",
"balance-scale-left",
"balance-scale-right",
"balance-scale",
"band-aid",
"baseball-ball",
"basketball-ball",
"beer",
"bible",
"biking",
"birthday-cake",
"blind",
"book-dead",
"book-reader",
"border-style",
"boxes",
"broadcast-tower",
"burn",
"bus-alt",
"car-alt",
"car-crash",
"chalkboard-teacher",
"chevron-circle-down",
"chevron-circle-left",
"chevron-circle-right",
"chevron-circle-up",
"clinic-medical",
"cloud-download-alt",
"cloud-upload-alt",
"cocktail",
"coffee",
"cog",
"cogs",
"columns",
"compress-alt",
"compress-arrows-alt",
"concierge-bell",
"crop-alt",
"cut",
"deaf",
"diagnoses",
"digital-tachograph",
"directions",
"dolly-flatbed",
"donate",
"drafting-compass",
"ellipsis-h",
"ellipsis-v",
"envelope-square",
"exchange-alt",
"exclamation-circle",
"exclamation-triangle",
"expand-alt",
"expand-arrows-alt",
"external-link-alt",
"external-link-square-alt",
"fast-backward",
"fast-forward",
"feather-alt",
"female",
"fighter-jet",
"file-download",
"file-medical-alt",
"file-upload",
"fire-alt",
"first-aid",
"fist-raised",
"football-ball",
"funnel-dollar",
"glass-cheers",
"glass-martini-alt",
"glass-martini",
"glass-whiskey",
"globe-africa",
"globe-americas",
"globe-asia",
"globe-europe",
"golf-ball",
"grip-horizontal",
"h-square",
"hamburger",
"hand-holding-usd",
"hand-holding-water",
"hands-helping",
"hands-wash",
"handshake-alt-slash",
"hard-hat",
"headphones-alt",
"heart-broken",
"heartbeat",
"hiking",
"history",
"home",
"hospital-alt",
"hospital-symbol",
"hot-tub",
"house-damage",
"hryvnia",
"id-card-alt",
"info-circle",
"journal-whills",
"laptop-house",
"level-down-alt",
"level-up-alt",
"long-arrow-alt-down",
"long-arrow-alt-left",
"long-arrow-alt-right",
"long-arrow-alt-up",
"low-vision",
"luggage-cart",
"magic",
"mail-bulk",
"male",
"map-marked-alt",
"map-marked",
"map-marker-alt",
"map-marker",
"map-signs",
"mars-stroke-h",
"mars-stroke-v",
"medkit",
"microphone-alt-slash",
"microphone-alt",
"minus-circle",
"mobile-alt",
"money-bill-wave-alt",
"money-check-alt",
"mouse-pointer",
"mouse",
"paint-brush",
"parking",
"pastafarianism",
"pen-alt",
"pen-square",
"pencil-alt",
"pencil-ruler",
"people-carry",
"percentage",
"phone-alt",
"phone-square-alt",
"phone-square",
"photo-video",
"plus-circle",
"poll-h",
"poll",
"portrait",
"pound-sign",
"pray",
"praying-hands",
"prescription-bottle-alt",
"procedures",
"project-diagram",
"quidditch",
"quran",
"radiation-alt",
"random",
"redo-alt",
"redo",
"remove-format",
"rss-square",
"running",
"search-dollar",
"search-location",
"search-minus",
"search-plus",
"search",
"share-alt-square",
"share-alt",
"shield-alt",
"shipping-fast",
"shopping-bag",
"shopping-basket",
"shopping-cart",
"shuttle-van",
"sign-in-alt",
"sign-language",
"sign-out-alt",
"sign",
"skating",
"skiing-nordic",
"skiing",
"sliders-h",
"smoking-ban",
"sms",
"snowboarding",
"sort-alpha-down-alt",
"sort-alpha-down",
"sort-alpha-up-alt",
"sort-alpha-up",
"sort-amount-down-alt",
"sort-amount-down",
"sort-amount-up-alt",
"sort-amount-up",
"sort-numeric-down-alt",
"sort-numeric-down",
"sort-numeric-up-alt",
"sort-numeric-up",
"space-shuttle",
"square-root-alt",
"star-half-alt",
"step-backward",
"step-forward",
"store-alt-slash",
"store-alt",
"stream",
"subway",
"swimmer",
"swimming-pool",
"sync-alt",
"sync",
"table-tennis",
"tablet-alt",
"tachometer-alt",
"tasks",
"tenge",
"th-large",
"th-list",
"th",
"theater-masks",
"thermometer-empty",
"thermometer-full",
"thermometer-half",
"thermometer-quarter",
"thermometer-three-quarters",
"ticket-alt",
"times",
"tint-slash",
"tint",
"tools",
"torah",
"tram",
"transgender-alt",
"trash-restore-alt",
"trash-restore",
"truck-loading",
"tshirt",
"undo-alt",
"undo",
"university",
"unlink",
"unlock-alt",
"user-alt-slash",
"user-alt",
"user-cog",
"user-edit",
"user-friends",
"user-md",
"user-times",
"users-cog",
"utensil-spoon",
"volleyball-ball",
"volume-down",
"volume-mute",
"volume-up",
"vote-yea",
"walking",
"weight",
"wine-glass-alt"
]
module.exports = v5names