Compare commits

..

60 Commits

Author SHA1 Message Date
0a71bef851 feat:更新账单类别UI 2026-08-03 21:35:20 +08:00
8ecc38befd feat:更新账单类别选项框 2026-08-03 19:12:21 +08:00
e205d1ddf4 feat:增加Drawer组件 2026-07-30 17:00:01 +08:00
be05e4695c feat:增加汽车图标 2026-07-21 21:30:13 +08:00
4b5f853603 feat:增加汽车事件模块 2026-07-20 23:19:14 +08:00
6ff52e9d50 feat:增加账单运动类别 2026-07-05 21:07:22 +08:00
3535d33c4b feat:增加美食分析助手 2026-07-04 20:25:51 +08:00
0ec0bba2fa feat:更新支出单词 2026-07-02 21:44:03 +08:00
98f2842869 feat:增加日程计划模块 2026-06-04 18:30:13 +08:00
f7e98f2a28 feat:更新账单记录组件 2026-06-03 09:57:47 +08:00
b9f4936dcc feat:增加数字键盘组件 2026-06-03 07:50:55 +08:00
3201a1c32b feat:增加物品过期时间显示 2026-05-21 22:05:15 +08:00
6aed1854ca feat:更新图标样式 2026-04-16 21:55:38 +08:00
f8a605b8c0 fix:修复视频无法播放的问题 2026-04-07 22:49:07 +08:00
08999c608d fix:修复布局问题 2026-04-06 22:21:11 +08:00
7eeac7c465 feat:更新查看原图图片格式 2026-04-06 17:40:11 +08:00
420f250bac feat:更新证书查询条件 2026-04-02 23:10:17 +08:00
3e59b58f84 feat:增加刷新功能 2026-04-02 23:00:51 +08:00
ac914f6c05 feat:增加奖状证书模块 2026-04-01 23:24:06 +08:00
11887bf3d5 feat:更新运动健康模块 2026-03-15 23:14:35 +08:00
1fb76fc220 feat:更新上传文件组件 2026-03-15 21:52:49 +08:00
d7d167f3e2 feat:去除电脑端登录限制 2026-03-15 10:29:58 +08:00
5b7dcd614a feat:更改代理地址 2026-02-14 16:56:54 +08:00
4dc541da85 feat:增加图片存储模式 2026-02-14 16:55:52 +08:00
223a59cd8a feat:更新图片模块 2026-02-12 23:24:27 +08:00
2fbae7f80f feat:增加vant预览图片组件 2026-02-12 19:23:57 +08:00
8940b3972a feat:增加vant组件库 2026-02-12 17:27:14 +08:00
1b8bd5a7e2 feat:更新美食和旅游模块 2026-02-09 23:16:19 +08:00
45342fb55c feat:更新上传图片和视频组件 2026-02-04 22:24:39 +08:00
e5e1e5e2b3 feat:更新上传图片组件 2026-02-03 23:16:19 +08:00
94270b923c feat:更新美食模块图片功能 2026-02-02 22:54:12 +08:00
aa7abbdde3 Merge branch 'master' of https://cxxgit.iepose.cn/Cxx0822/sweet-hut-ui 2026-02-01 22:35:02 +08:00
7b96e60b8d feat:更新图片存储路径 2026-02-01 22:34:38 +08:00
71639cad51 Merge branch 'master' of https://cxxgit.iepose.cn/Cxx0822/sweet-hut-ui 2025-12-04 11:01:45 +08:00
d87732dbf7 feat:更新依赖 2025-12-04 11:01:37 +08:00
c0cfbbaa15 feat:禁用日期选择输入模式 2025-11-17 22:23:59 +08:00
f5e75b745f feat:增加健康模块睡眠时间监控 2025-11-17 19:58:59 +08:00
4d7946759c feat:增加体重图表自适应坐标轴 2025-11-17 09:39:40 +08:00
2bcc61fdaa feat:调整体重趋势图刻度 2025-11-13 10:16:26 +08:00
63a1e126ad feat:增加健康统计图表功能 2025-11-12 19:41:11 +08:00
f62613205c fix:修复日记显示不全的问题 2025-11-11 23:33:19 +08:00
5ec0781a53 feat:更新账单搜索模块 2025-11-11 09:35:49 +08:00
e875a8e2d5 Merge branch 'master' of https://cxxgit.iepose.cn/Cxx0822/sweet-hut-ui 2025-11-10 19:51:06 +08:00
7032070d3f feat:增加账单搜索功能 2025-11-10 19:50:22 +08:00
ee20c19b72 feat:更新运动健康模块 2025-11-04 22:35:53 +08:00
5f3a7ca5b4 feat:增加运动健康模块 2025-11-04 20:05:31 +08:00
ed0c8e6664 fix:修复博客接口错误的问题 2025-11-04 09:22:49 +08:00
355a6c256d feat:增加账本统计模块 2025-10-30 23:02:55 +08:00
9e0b8ead79 feat:更新tabbar显示 2025-10-30 16:50:13 +08:00
cc33d3028b feat:增加月经天数统计 2025-10-30 15:00:50 +08:00
930202a6b6 feat:增加账本图片预览 2025-10-17 16:48:09 +08:00
2e81e57774 feat:重构模块 2025-10-08 22:06:39 +08:00
f60d78fe8b feat:删除博客模块 2025-09-17 18:26:13 +08:00
f3c86b9775 feat:更新家庭账本样式 2025-09-15 22:59:50 +08:00
38e25bf88a feat:修复纪念日问题 2025-08-24 16:01:45 +08:00
ee9943f618 fix:修复错误 2025-06-27 14:01:25 +08:00
d8e823f165 feat:格式化代码 2025-06-22 22:58:39 +08:00
b4e0d8ebc2 feat:更新博客评论模块 2025-06-22 21:01:49 +08:00
b9c18375f5 feat: 增加博客评论模块 2025-06-20 17:36:35 +08:00
e5ff5608a8 fix:修复博客分页错误 2025-06-19 20:01:01 +08:00
346 changed files with 7109 additions and 11028 deletions

1
.env
View File

@@ -1 +0,0 @@
VITE_API_HOST=43.225.157.27

1
.env.development Normal file
View File

@@ -0,0 +1 @@
VITE_API_ALIST_PATH=/local/test

1
.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_API_ALIST_PATH=/local

View File

@@ -1,6 +1,6 @@
FROM docker-0.unsee.tech/nginx:1.27.0
FROM nginx:1.27.0
COPY dist/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone
RUN chmod -R 755 /usr/share/nginx/html/

114
components.d.ts vendored Normal file
View File

@@ -0,0 +1,114 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AnniversaryCard: typeof import('./src/components/Anniversary/AnniversaryCard.vue')['default']
AnniversaryQuery: typeof import('./src/components/Anniversary/AnniversaryQuery.vue')['default']
AppLoading: typeof import('./src/components/Common/AppLoading.vue')['default']
ArriveButton: typeof import('./src/components/Common/ArriveButton.vue')['default']
AssetCard: typeof import('./src/components/Asset/AssetCard.vue')['default']
AssetQuery: typeof import('./src/components/Asset/AssetQuery.vue')['default']
AwardCard: typeof import('./src/components/Award/AwardCard.vue')['default']
AwardQuery: typeof import('./src/components/Award/AwardQuery.vue')['default']
BillCalendar: typeof import('./src/components/Bill/BillCalendar.vue')['default']
BillChart: typeof import('./src/components/Bill/BillChart.vue')['default']
BillDailyItem: typeof import('./src/components/Bill/BillDailyItem.vue')['default']
BillDailyList: typeof import('./src/components/Bill/BillDailyList.vue')['default']
BillDrawer: typeof import('./src/components/Bill/BillDrawer.vue')['default']
BillQuery: typeof import('./src/components/Bill/BillQuery.vue')['default']
BillRank: typeof import('./src/components/Bill/BillRank.vue')['default']
BillSearch: typeof import('./src/components/Bill/BillSearch.vue')['default']
BillStats: typeof import('./src/components/Bill/BillStats.vue')['default']
BlogArchive: typeof import('./src/components/Blog/Archive/BlogArchive.vue')['default']
BlogCategory: typeof import('./src/components/Blog/Category/BlogCategory.vue')['default']
BlogCategoryDetail: typeof import('./src/components/Blog/Category/BlogCategoryDetail.vue')['default']
BlogComment: typeof import('./src/components/Blog/Comment/BlogComment.vue')['default']
BlogDetail: typeof import('./src/components/Blog/Content/BlogDetail.vue')['default']
BlogLabel: typeof import('./src/components/Blog/Content/BlogLabel.vue')['default']
BlogOverview: typeof import('./src/components/Blog/Content/BlogOverview.vue')['default']
CarCard: typeof import('./src/components/Car/CarCard.vue')['default']
CarQuery: typeof import('./src/components/Car/CarQuery.vue')['default']
CategoryCard: typeof import('./src/components/Anniversary/CategoryCard.vue')['default']
CommonLogin: typeof import('./src/components/Login/CommonLogin.vue')['default']
DailyCard: typeof import('./src/components/DailyCard.vue')['default']
DailySummary: typeof import('./src/components/DailySummary.vue')['default']
EmailLogin: typeof import('./src/components/Login/EmailLogin.vue')['default']
FoodBasicData: typeof import('./src/components/Food/Data/FoodBasicData.vue')['default']
FoodCalendar: typeof import('./src/components/Food/FoodCalendar.vue')['default']
FoodCard: typeof import('./src/components/Food/FoodCard.vue')['default']
FoodDailyItem: typeof import('./src/components/Food/FoodDailyItem.vue')['default']
FoodMaterialData: typeof import('./src/components/Food/Data/FoodMaterialData.vue')['default']
FoodMaterialDialog: typeof import('./src/components/Food/Form/FoodMaterialDialog.vue')['default']
FoodMaterialForm: typeof import('./src/components/Food/Form/FoodMaterialForm.vue')['default']
FoodOthersData: typeof import('./src/components/Food/Data/FoodOthersData.vue')['default']
FoodQuery: typeof import('./src/components/Food/FoodQuery.vue')['default']
FoodRecordData: typeof import('./src/components/Food/Data/FoodRecordData.vue')['default']
FoodStats: typeof import('./src/components/Food/FoodStats.vue')['default']
FoodStepData: typeof import('./src/components/Food/Data/FoodStepData.vue')['default']
FoodStepDialog: typeof import('./src/components/Food/Form/FoodStepDialog.vue')['default']
FoodStepForm: typeof import('./src/components/Food/Form/FoodStepForm.vue')['default']
HealthChart: typeof import('./src/components/Health/HealthChart.vue')['default']
HealthForm: typeof import('./src/components/Health/HealthForm.vue')['default']
HealthStats: typeof import('./src/components/Health/HealthStats.vue')['default']
HomeQuery: typeof import('./src/components/HomeQuery.vue')['default']
JournalCalendar: typeof import('./src/components/Journal/JournalCalendar.vue')['default']
JournalCard: typeof import('./src/components/Journal/JournalCard.vue')['default']
JournalDrawer: typeof import('./src/components/Journal/JournalDrawer.vue')['default']
KpiCalendar: typeof import('./src/components/kpi/KpiCalendar.vue')['default']
KpiCard: typeof import('./src/components/kpi/KpiCard.vue')['default']
LedgerCard: typeof import('./src/components/Ledger/LedgerCard.vue')['default']
LedgerQuery: typeof import('./src/components/Ledger/LedgerQuery.vue')['default']
LedgerRank: typeof import('./src/components/Ledger/LedgerRank.vue')['default']
LoginAuth: typeof import('./src/components/Login/LoginAuth.vue')['default']
Milestone: typeof import('./src/components/Milestone.vue')['default']
MobileSetting: typeof import('./src/components/Common/MobileSetting.vue')['default']
ModuleCard: typeof import('./src/components/ModuleCard.vue')['default']
NumberInput: typeof import('./src/components/NumberInput.vue')['default']
PasswordCard: typeof import('./src/components/Password/PasswordCard.vue')['default']
PasswordQuery: typeof import('./src/components/Password/PasswordQuery.vue')['default']
PeriodForm: typeof import('./src/components/Period/PeriodForm.vue')['default']
PeriodLegend: typeof import('./src/components/Period/PeriodLegend.vue')['default']
PhoneLogin: typeof import('./src/components/Login/PhoneLogin.vue')['default']
PlanCalendar: typeof import('./src/components/Plan/PlanCalendar.vue')['default']
PlanCard: typeof import('./src/components/Plan/PlanCard.vue')['default']
PlanQuery: typeof import('./src/components/Plan/PlanQuery.vue')['default']
ProductCard: typeof import('./src/components/Product/ProductCard.vue')['default']
ProductDrawer: typeof import('./src/components/Product/ProductDrawer.vue')['default']
ProductQuery: typeof import('./src/components/Product/ProductQuery.vue')['default']
ProfileAccount: typeof import('./src/components/Profile/ProfileAccount.vue')['default']
ProfileInfo: typeof import('./src/components/Profile/ProfileInfo.vue')['default']
ProfilePassword: typeof import('./src/components/Profile/ProfilePassword.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ServerCard: typeof import('./src/components/Server/ServerCard.vue')['default']
ServerProgress: typeof import('./src/components/Server/ServerProgress.vue')['default']
StatsCard: typeof import('./src/components/StatsCard.vue')['default']
TravelBasicData: typeof import('./src/components/Travel/Data/TravelBasicData.vue')['default']
TravelCalendar: typeof import('./src/components/Travel/TravelCalendar.vue')['default']
TravelCard: typeof import('./src/components/Travel/TravelCard.vue')['default']
TravelConsumeData: typeof import('./src/components/Travel/Data/TravelConsumeData.vue')['default']
TravelDailyItem: typeof import('./src/components/Travel/TravelDailyItem.vue')['default']
TravelDailyList: typeof import('./src/components/Travel/TravelDailyList.vue')['default']
TravelDialog: typeof import('./src/components/Travel/TravelDialog.vue')['default']
TravelGuideData: typeof import('./src/components/Travel/Data/TravelGuideData.vue')['default']
TravelQuery: typeof import('./src/components/Travel/TravelQuery.vue')['default']
TravelRecordData: typeof import('./src/components/Travel/Data/TravelRecordData.vue')['default']
TravelSpotList: typeof import('./src/components/Travel/TravelSpotList.vue')['default']
TravelStats: typeof import('./src/components/Travel/TravelStats.vue')['default']
VanButton: typeof import('vant/es')['Button']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanFloatingBubble: typeof import('vant/es')['FloatingBubble']
VanSwipeCell: typeof import('vant/es')['SwipeCell']
WeatherIcon: typeof import('./src/components/Common/WeatherIcon.vue')['default']
WeatherTimeCard: typeof import('./src/components/Common/WeatherTimeCard.vue')['default']
}
}

View File

@@ -1,106 +1,41 @@
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
#root /var/www/html;
root /usr/share/nginx/html;
# Add index.php to the list if you are using PHP
#index index.html index.htm index.nginx-debian.html;
index index.html;
server_name _;
location /admin-service/ {
proxy_pass http://admin-service:8080/;
client_max_body_size 500M;
location /sweet-hut/ {
alias /usr/share/nginx/html/;
try_files $uri $uri/ /sweet-hut/index.html;
}
location /home-service/ {
proxy_pass http://home-service:8081/;
location /home-api/ {
proxy_pass http://host.docker.internal:8081/;
}
location /blog-service/ {
proxy_pass http://blog-service:8082/;
location /alist-api/ {
proxy_pass http://host.docker.internal:5244/;
}
location /blog-api/ {
proxy_pass http://host.docker.internal:8082/;
}
location /image-api/ {
proxy_pass http://host.docker.internal:3000/;
}
location /agent-api/ {
proxy_pass http://host.docker.internal:8000/;
}
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
# pass PHP scripts to FastCGI server
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
# fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

177
package-lock.json generated
View File

@@ -29,9 +29,11 @@
"lunar-javascript": "^1.7.2",
"md-editor-v3": "^4.21.3",
"pinia": "^2.3.1",
"ua-parser-js": "^2.0.3",
"v-calendar": "^3.1.2",
"vue": "3.5.12",
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
"vue3-emoji-picker": "^1.1.8",
"vue3-eslint": "git+https://gitee.com/Cxx0822/vue3-eslint"
},
"devDependencies": {
@@ -2737,12 +2739,21 @@
"version": "18.19.86",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.86.tgz",
"integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/resize-observer-browser": {
"version": "0.1.11",
"resolved": "https://registry.npmmirror.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz",
@@ -3519,8 +3530,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/atob": {
"version": "2.1.2",
@@ -4019,7 +4029,6 @@
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"peer": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -4488,11 +4497,30 @@
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-europe-js": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/detect-europe-js/-/detect-europe-js-0.1.2.tgz",
"integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
},
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -5848,7 +5876,6 @@
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"peer": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -6372,6 +6399,12 @@
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/idb": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz",
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
"license": "ISC"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
@@ -6844,6 +6877,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-standalone-pwa": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz",
"integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
},
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"license": "MIT"
},
"node_modules/is-string": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz",
@@ -7400,7 +7453,6 @@
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
@@ -7410,7 +7462,6 @@
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -7636,6 +7687,26 @@
"license": "MIT",
"optional": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
@@ -10207,6 +10278,12 @@
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
"license": "MIT"
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/traverse": {
"version": "0.6.11",
"resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.6.11.tgz",
@@ -10406,6 +10483,59 @@
"node": ">=14.17"
}
},
"node_modules/ua-is-frozen": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz",
"integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
},
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"license": "MIT"
},
"node_modules/ua-parser-js": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/ua-parser-js/-/ua-parser-js-2.0.3.tgz",
"integrity": "sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "AGPL-3.0-or-later",
"dependencies": {
"@types/node-fetch": "^2.6.12",
"detect-europe-js": "^0.1.2",
"is-standalone-pwa": "^0.1.1",
"node-fetch": "^2.7.0",
"ua-is-frozen": "^0.1.2"
},
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
@@ -10434,7 +10564,6 @@
"version": "5.26.5",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"license": "MIT"
},
"node_modules/union-value": {
@@ -11406,6 +11535,20 @@
"vue-router": "^4.2.2"
}
},
"node_modules/vue3-emoji-picker": {
"version": "1.1.8",
"resolved": "https://registry.npmmirror.com/vue3-emoji-picker/-/vue3-emoji-picker-1.1.8.tgz",
"integrity": "sha512-k9tVHeQEBVLzVCLYAkFaI1nib3FJFQwdPhWD5khJkhks3ktg3g12z5wPGOSDpIuSLNtelRGvq1qdmZuJu5khfA==",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.0",
"idb": "^7.1.0",
"vue": "^3.2.23"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/vue3-eslint": {
"version": "1.0.0",
"resolved": "git+https://gitee.com/Cxx0822/vue3-eslint#849a4e0e783d3ecc57035fb2f92950998b6b2e41",
@@ -11444,6 +11587,22 @@
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/whatwg-url/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",

View File

@@ -2,6 +2,7 @@
"name": "sweet-hut-ui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vite build",
@@ -23,35 +24,32 @@
"dayjs": "^1.11.13",
"echarts-liquidfill": "^3.1.0",
"element-china-area-data": "^6.1.0",
"element-plus": "~2.9.7",
"element-plus": "^2.9.7",
"file-icons-vue": "^1.3.4",
"font-awesome": "^4.7.0",
"lunar-javascript": "^1.7.2",
"md-editor-v3": "^4.21.3",
"pinia": "^2.3.1",
"ua-parser-js": "^2.0.3",
"v-calendar": "^3.1.2",
"vant": "^4.9.22",
"vue": "3.5.12",
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
"vue3-emoji-picker": "^1.1.8",
"vue3-eslint": "git+https://gitee.com/Cxx0822/vue3-eslint"
},
"devDependencies": {
"@types/node": "^18.19.86",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-vue": "5.2.1",
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^5.2.1",
"autoprefixer": "^10.4.21",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.2.0",
"eslint-plugin-vue": "^8.7.1",
"postcss": "^8.5.3",
"sass": "^1.86.3",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"unplugin-vue-components": "^32.1.0",
"vfonts": "^0.0.3",
"vite": "5.2.0",
"vite": "^5.2.0",
"vite-plugin-mock": "^3.0.2",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.8.27"

370
pnpm-lock.yaml generated
View File

@@ -54,7 +54,7 @@ importers:
specifier: ^6.1.0
version: 6.1.0
element-plus:
specifier: ~2.9.7
specifier: ^2.9.7
version: 2.9.7(vue@3.5.12(typescript@5.8.3))
file-icons-vue:
specifier: ^1.3.4
@@ -71,15 +71,24 @@ importers:
pinia:
specifier: ^2.3.1
version: 2.3.1(typescript@5.8.3)(vue@3.5.12(typescript@5.8.3))
ua-parser-js:
specifier: ^2.0.3
version: 2.0.6
v-calendar:
specifier: ^3.1.2
version: 3.1.2(@popperjs/core@2.11.8)(vue@3.5.12(typescript@5.8.3))
vant:
specifier: ^4.9.22
version: 4.9.22(vue@3.5.12(typescript@5.8.3))
vue:
specifier: 3.5.12
version: 3.5.12(typescript@5.8.3)
vue3-common:
specifier: git+https://gitee.com/Cxx0822/vue3-common
version: git+https://gitee.com/Cxx0822/vue3-common#ba7c8a0b1a20a714f0bf09b10d9486b5fd518ca0(axios@1.9.0)(crypto-js@4.2.0)(echarts@5.6.0)(element-plus@2.9.7(vue@3.5.12(typescript@5.8.3)))(font-awesome@4.7.0)(js-cookie@3.0.5)(path-browserify@1.0.1)(pinia@2.3.1(typescript@5.8.3)(vue@3.5.12(typescript@5.8.3)))(qs@6.14.0)(vue-router@4.5.1(vue@3.5.12(typescript@5.8.3)))(vue@3.5.12(typescript@5.8.3))
vue3-emoji-picker:
specifier: ^1.1.8
version: 1.1.8(typescript@5.8.3)
vue3-eslint:
specifier: git+https://gitee.com/Cxx0822/vue3-eslint
version: git+https://gitee.com/Cxx0822/vue3-eslint#849a4e0e783d3ecc57035fb2f92950998b6b2e41(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint-config-standard@16.0.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)
@@ -87,36 +96,15 @@ importers:
'@types/node':
specifier: ^18.19.86
version: 18.19.86
'@typescript-eslint/eslint-plugin':
specifier: ^5.62.0
version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3)
'@typescript-eslint/parser':
specifier: ^5.62.0
version: 5.62.0(eslint@7.32.0)(typescript@5.8.3)
'@vant/auto-import-resolver':
specifier: ^1.3.0
version: 1.3.0
'@vitejs/plugin-vue':
specifier: 5.2.1
specifier: ^5.2.1
version: 5.2.1(vite@5.2.0(@types/node@18.19.86)(sass@1.86.3))(vue@3.5.12(typescript@5.8.3))
autoprefixer:
specifier: ^10.4.21
version: 10.4.21(postcss@8.5.3)
eslint:
specifier: ^7.32.0
version: 7.32.0
eslint-config-standard:
specifier: ^16.0.3
version: 16.0.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0)
eslint-plugin-import:
specifier: ^2.31.0
version: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)
eslint-plugin-node:
specifier: ^11.1.0
version: 11.1.0(eslint@7.32.0)
eslint-plugin-promise:
specifier: ^5.2.0
version: 5.2.0(eslint@7.32.0)
eslint-plugin-vue:
specifier: ^8.7.1
version: 8.7.1(eslint@7.32.0)
postcss:
specifier: ^8.5.3
version: 8.5.3
@@ -129,11 +117,14 @@ importers:
typescript:
specifier: ^5.8.3
version: 5.8.3
unplugin-vue-components:
specifier: ^32.1.0
version: 32.1.0(esbuild@0.20.2)(rollup@4.40.0)(vite@5.2.0(@types/node@18.19.86)(sass@1.86.3))(vue@3.5.12(typescript@5.8.3))
vfonts:
specifier: ^0.0.3
version: 0.0.3
vite:
specifier: 5.2.0
specifier: ^5.2.0
version: 5.2.0(@types/node@18.19.86)(sass@1.86.3)
vite-plugin-mock:
specifier: ^3.0.2
@@ -486,6 +477,9 @@ packages:
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'}
'@jridgewell/remapping@2.3.5':
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
@@ -497,6 +491,9 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -993,6 +990,17 @@ packages:
resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
'@vant/auto-import-resolver@1.3.0':
resolution: {integrity: sha512-lJyWtCyFizR4bHZvMiNMF3w+WTFTUWAvka1eqTnPK9ticUcKTCOx6qEmHcm8JPb3g1t3GaD2W3MnHkBp/nHamw==}
'@vant/popperjs@1.3.0':
resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
'@vant/use@1.6.0':
resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==}
peerDependencies:
vue: ^3.0.0
'@vavt/util@2.1.0':
resolution: {integrity: sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ==}
@@ -1061,6 +1069,9 @@ packages:
'@vue/shared@3.5.13':
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
'@vue/shared@3.5.28':
resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==}
'@vueuse/core@13.1.0':
resolution: {integrity: sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==}
peerDependencies:
@@ -1093,8 +1104,13 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.14.1:
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.18.0:
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
engines: {node: '>=0.4.0'}
hasBin: true
@@ -1334,6 +1350,10 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chokidar@5.0.0:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
class-utils@0.3.6:
resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
engines: {node: '>=0.10.0'}
@@ -1393,6 +1413,12 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.4:
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
connect@3.7.0:
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
engines: {node: '>= 0.10.0'}
@@ -1527,6 +1553,9 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
detect-europe-js@0.1.2:
resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
@@ -1826,6 +1855,9 @@ packages:
resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
engines: {node: '>=0.10.0'}
exsolve@1.1.1:
resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==}
extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
@@ -1857,6 +1889,15 @@ packages:
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -2064,6 +2105,9 @@ packages:
htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
ignore@4.0.6:
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
engines: {node: '>= 4'}
@@ -2222,6 +2266,9 @@ packages:
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
engines: {node: '>= 0.4'}
is-standalone-pwa@0.1.1:
resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
is-string@1.1.1:
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
engines: {node: '>= 0.4'}
@@ -2348,6 +2395,10 @@ packages:
resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
engines: {node: '>=4.0.0'}
local-pkg@1.2.1:
resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
engines: {node: '>=14'}
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
@@ -2379,6 +2430,9 @@ packages:
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
map-cache@0.2.2:
resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
engines: {node: '>=0.10.0'}
@@ -2466,6 +2520,9 @@ packages:
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
engines: {node: '>=0.10.0'}
mlly@1.8.2:
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
mockjs@1.1.0:
resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==}
hasBin: true
@@ -2561,6 +2618,10 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
obug@2.1.4:
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
engines: {node: '>=12.20.0'}
on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
@@ -2619,6 +2680,9 @@ packages:
pathe@0.2.0:
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2626,6 +2690,10 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
picomatch@4.0.5:
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
engines: {node: '>=12'}
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@@ -2643,6 +2711,12 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.3.1:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
posix-character-classes@0.1.1:
resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
engines: {node: '>=0.10.0'}
@@ -2744,6 +2818,9 @@ packages:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
query-string@4.3.4:
resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
engines: {node: '>=0.10.0'}
@@ -2766,6 +2843,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
readdirp@5.0.0:
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
engines: {node: '>= 20.19.0'}
reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
@@ -3068,6 +3149,10 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
to-object-path@0.3.0:
resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
engines: {node: '>=0.10.0'}
@@ -3142,9 +3227,19 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
ua-is-frozen@0.1.2:
resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==}
ua-parser-js@2.0.6:
resolution: {integrity: sha512-EmaxXfltJaDW75SokrY4/lXMrVyXomE/0FpIIqP2Ctic93gK7rlme55Cwkz8l3YZ6gqf94fCU7AnIkidd/KXPg==}
hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
ufo@1.6.4:
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -3164,6 +3259,53 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
unplugin-utils@0.3.2:
resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==}
engines: {node: '>=20.19.0'}
unplugin-vue-components@32.1.0:
resolution: {integrity: sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@nuxt/kit': ^3.2.2 || ^4.0.0
vue: ^3.0.0
peerDependenciesMeta:
'@nuxt/kit':
optional: true
unplugin@3.3.0:
resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
'@farmfe/core': '*'
'@rspack/core': '*'
bun-types-no-globals: '*'
esbuild: '*'
rolldown: '*'
rollup: '*'
unloader: '*'
vite: '*'
webpack: '*'
peerDependenciesMeta:
'@farmfe/core':
optional: true
'@rspack/core':
optional: true
bun-types-no-globals:
optional: true
esbuild:
optional: true
rolldown:
optional: true
rollup:
optional: true
unloader:
optional: true
vite:
optional: true
webpack:
optional: true
unset-value@1.0.0:
resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
engines: {node: '>=0.10.0'}
@@ -3201,6 +3343,11 @@ packages:
v8-compile-cache@2.4.0:
resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==}
vant@4.9.22:
resolution: {integrity: sha512-P2PDSj3oB6l3W1OpVlQpapeLmI6bXMSvPqPdrw5rutslC0Y6tSkrVB/iSD57weD7K92GsjGkvgDK0eZlOsXGqw==}
peerDependencies:
vue: ^3.0.0
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
@@ -3301,6 +3448,10 @@ packages:
vue: ^3.5.12
vue-router: ^4.2.2
vue3-emoji-picker@1.1.8:
resolution: {integrity: sha512-k9tVHeQEBVLzVCLYAkFaI1nib3FJFQwdPhWD5khJkhks3ktg3g12z5wPGOSDpIuSLNtelRGvq1qdmZuJu5khfA==}
engines: {node: '>=16.0.0'}
vue3-eslint@git+https://gitee.com/Cxx0822/vue3-eslint#849a4e0e783d3ecc57035fb2f92950998b6b2e41:
resolution: {commit: 849a4e0e783d3ecc57035fb2f92950998b6b2e41, repo: https://gitee.com/Cxx0822/vue3-eslint, type: git}
version: 1.0.0
@@ -3329,6 +3480,9 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
@@ -3812,12 +3966,19 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/remapping@2.3.5':
dependencies:
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/set-array@1.2.1': {}
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
@@ -4366,6 +4527,14 @@ snapshots:
'@typescript-eslint/types': 5.62.0
eslint-visitor-keys: 3.4.3
'@vant/auto-import-resolver@1.3.0': {}
'@vant/popperjs@1.3.0': {}
'@vant/use@1.6.0(vue@3.5.12(typescript@5.8.3))':
dependencies:
vue: 3.5.12(typescript@5.8.3)
'@vavt/util@2.1.0': {}
'@vitejs/plugin-vue@5.2.1(vite@5.2.0(@types/node@18.19.86)(sass@1.86.3))(vue@3.5.12(typescript@5.8.3))':
@@ -4471,6 +4640,8 @@ snapshots:
'@vue/shared@3.5.13': {}
'@vue/shared@3.5.28': {}
'@vueuse/core@13.1.0(vue@3.5.12(typescript@5.8.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
@@ -4507,13 +4678,15 @@ snapshots:
dependencies:
acorn: 7.4.1
acorn-jsx@5.3.2(acorn@8.14.1):
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.14.1
acorn: 8.15.0
acorn@7.4.1: {}
acorn@8.14.1: {}
acorn@8.15.0: {}
acorn@8.18.0: {}
ajv@6.12.6:
dependencies:
@@ -4792,6 +4965,10 @@ snapshots:
dependencies:
readdirp: 4.1.2
chokidar@5.0.0:
dependencies:
readdirp: 5.0.0
class-utils@0.3.6:
dependencies:
arr-union: 3.1.0
@@ -4851,6 +5028,10 @@ snapshots:
concat-map@0.0.1: {}
confbox@0.1.8: {}
confbox@0.2.4: {}
connect@3.7.0:
dependencies:
debug: 2.6.9
@@ -4979,6 +5160,8 @@ snapshots:
delayed-stream@1.0.0: {}
detect-europe-js@0.1.2: {}
detect-libc@1.0.3:
optional: true
@@ -5374,8 +5557,8 @@ snapshots:
espree@9.6.1:
dependencies:
acorn: 8.14.1
acorn-jsx: 5.3.2(acorn@8.14.1)
acorn: 8.15.0
acorn-jsx: 5.3.2(acorn@8.15.0)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
@@ -5410,6 +5593,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
exsolve@1.1.1: {}
extend-shallow@2.0.1:
dependencies:
is-extendable: 0.1.1
@@ -5452,6 +5637,10 @@ snapshots:
dependencies:
reusify: 1.1.0
fdir@6.5.0(picomatch@4.0.5):
optionalDependencies:
picomatch: 4.0.5
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
@@ -5684,6 +5873,8 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
idb@7.1.1: {}
ignore@4.0.6: {}
ignore@5.3.2: {}
@@ -5835,6 +6026,8 @@ snapshots:
dependencies:
call-bound: 1.0.4
is-standalone-pwa@0.1.1: {}
is-string@1.1.1:
dependencies:
call-bound: 1.0.4
@@ -5949,6 +6142,12 @@ snapshots:
emojis-list: 3.0.0
json5: 1.0.2
local-pkg@1.2.1:
dependencies:
mlly: 1.8.2
pkg-types: 2.3.1
quansync: 0.2.11
lodash-es@4.17.21: {}
lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
@@ -5973,6 +6172,10 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
map-cache@0.2.2: {}
map-visit@1.0.0:
@@ -6075,6 +6278,13 @@ snapshots:
for-in: 1.0.2
is-extendable: 1.0.1
mlly@1.8.2:
dependencies:
acorn: 8.18.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.4
mockjs@1.1.0:
dependencies:
commander: 13.1.0
@@ -6179,6 +6389,8 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
obug@2.1.4: {}
on-finished@2.3.0:
dependencies:
ee-first: 1.1.1
@@ -6231,10 +6443,14 @@ snapshots:
pathe@0.2.0: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
picomatch@4.0.5: {}
pify@2.3.0: {}
pinia@2.3.1(typescript@5.8.3)(vue@3.5.12(typescript@5.8.3)):
@@ -6249,6 +6465,18 @@ snapshots:
pirates@4.0.7: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.2
pathe: 2.0.3
pkg-types@2.3.1:
dependencies:
confbox: 0.2.4
exsolve: 1.1.1
pathe: 2.0.3
posix-character-classes@0.1.1: {}
possible-typed-array-names@1.1.0: {}
@@ -6340,6 +6568,8 @@ snapshots:
dependencies:
side-channel: 1.1.0
quansync@0.2.11: {}
query-string@4.3.4:
dependencies:
object-assign: 4.1.1
@@ -6363,6 +6593,8 @@ snapshots:
readdirp@4.1.2: {}
readdirp@5.0.0: {}
reflect.getprototypeof@1.0.10:
dependencies:
call-bind: 1.0.8
@@ -6768,6 +7000,11 @@ snapshots:
dependencies:
any-promise: 1.3.0
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.5)
picomatch: 4.0.5
to-object-path@0.3.0:
dependencies:
kind-of: 3.2.2
@@ -6866,8 +7103,18 @@ snapshots:
typescript@5.8.3: {}
ua-is-frozen@0.1.2: {}
ua-parser-js@2.0.6:
dependencies:
detect-europe-js: 0.1.2
is-standalone-pwa: 0.1.1
ua-is-frozen: 0.1.2
uc.micro@2.1.0: {}
ufo@1.6.4: {}
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -6888,6 +7135,44 @@ snapshots:
unpipe@1.0.0: {}
unplugin-utils@0.3.2:
dependencies:
pathe: 2.0.3
picomatch: 4.0.5
unplugin-vue-components@32.1.0(esbuild@0.20.2)(rollup@4.40.0)(vite@5.2.0(@types/node@18.19.86)(sass@1.86.3))(vue@3.5.12(typescript@5.8.3)):
dependencies:
chokidar: 5.0.0
local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
obug: 2.1.4
picomatch: 4.0.5
tinyglobby: 0.2.17
unplugin: 3.3.0(esbuild@0.20.2)(rollup@4.40.0)(vite@5.2.0(@types/node@18.19.86)(sass@1.86.3))
unplugin-utils: 0.3.2
vue: 3.5.12(typescript@5.8.3)
transitivePeerDependencies:
- '@farmfe/core'
- '@rspack/core'
- bun-types-no-globals
- esbuild
- rolldown
- rollup
- unloader
- vite
- webpack
unplugin@3.3.0(esbuild@0.20.2)(rollup@4.40.0)(vite@5.2.0(@types/node@18.19.86)(sass@1.86.3)):
dependencies:
'@jridgewell/remapping': 2.3.5
picomatch: 4.0.5
webpack-virtual-modules: 0.6.2
optionalDependencies:
esbuild: 0.20.2
rollup: 4.40.0
vite: 5.2.0(@types/node@18.19.86)(sass@1.86.3)
unset-value@1.0.0:
dependencies:
has-value: 0.3.1
@@ -6924,6 +7209,13 @@ snapshots:
v8-compile-cache@2.4.0: {}
vant@4.9.22(vue@3.5.12(typescript@5.8.3)):
dependencies:
'@vant/popperjs': 1.3.0
'@vant/use': 1.6.0(vue@3.5.12(typescript@5.8.3))
'@vue/shared': 3.5.28
vue: 3.5.12(typescript@5.8.3)
vary@1.1.2: {}
vfonts@0.0.3: {}
@@ -7027,6 +7319,14 @@ snapshots:
vue: 3.5.12(typescript@5.8.3)
vue-router: 4.5.1(vue@3.5.12(typescript@5.8.3))
vue3-emoji-picker@1.1.8(typescript@5.8.3):
dependencies:
'@popperjs/core': 2.11.8
idb: 7.1.1
vue: 3.5.12(typescript@5.8.3)
transitivePeerDependencies:
- typescript
vue3-eslint@git+https://gitee.com/Cxx0822/vue3-eslint#849a4e0e783d3ecc57035fb2f92950998b6b2e41(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint-config-standard@16.0.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0):
dependencies:
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3)
@@ -7052,6 +7352,8 @@ snapshots:
webidl-conversions@7.0.0: {}
webpack-virtual-modules@0.6.2: {}
whatwg-mimetype@3.0.0: {}
which-boxed-primitive@1.1.1:

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -4,7 +4,7 @@
</template>
<script setup lang="ts">
import AppLoading from '@/components/AppLoading.vue'
import AppLoading from '@/components/Common/AppLoading.vue'
import { useAppStore } from '@/store/app.ts'
import { useRouter } from 'vue-router'

View File

@@ -1,29 +0,0 @@
import { cloudService } from './index'
import { IAccount } from '@/types/auth.ts'
export const queryAccountApi = (): Promise<IAccount[]> =>
cloudService({
url: '/admin-service/account',
method: 'get'
})
export const registerAccountApi = (account: IAccount): Promise<boolean> =>
cloudService({
url: '/admin-service/account',
method: 'post',
data: account
})
export const deleteAccountApi = (id: number): Promise<boolean> =>
cloudService({
url: `/admin-service/account/${id}`,
method: 'delete'
})
export const updateAccountPasswordApi = (id: number,
oldPassword: string, newPassword: string): Promise<boolean> =>
cloudService({
url: `/admin-service/account/password/${id}`,
method: 'put',
params: { oldPassword, newPassword }
})

View File

@@ -4,33 +4,33 @@ import { IStatsChart } from 'vue3-common/types'
export const addAnniversaryApi = (anniversary: IAnniversary): Promise<boolean> =>
cloudService({
url: '/home-service/anniversary',
url: '/home-api/anniversary',
method: 'post',
data: anniversary
})
export const updateAnniversaryApi = (id: number, anniversary: IAnniversary): Promise<boolean> =>
cloudService({
url: `/home-service/anniversary/${id}`,
url: `/home-api/anniversary/${id}`,
method: 'put',
data: anniversary
})
export const deleteAnniversaryApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/anniversary/${id}`,
url: `/home-api/anniversary/${id}`,
method: 'delete'
})
export const queryAnniversaryApi = (category: string): Promise<IAnniversary[]> =>
cloudService({
url: '/home-service/anniversary',
url: '/home-api/anniversary',
method: 'get',
params: { category }
})
export const queryAnniversaryCategoryApi = (): Promise<IStatsChart[]> =>
cloudService({
url: '/home-service/anniversary/category',
url: '/home-api/anniversary/category',
method: 'get'
})

View File

@@ -3,33 +3,33 @@ import { IAssetRecord } from '@/types/asset'
export const addAssetApi = (asset: IAssetRecord): Promise<boolean> =>
cloudService({
url: '/home-service/asset',
url: '/home-api/asset',
method: 'post',
data: asset
})
export const updateAssetApi = (id: number, asset: IAssetRecord): Promise<boolean> =>
cloudService({
url: `/home-service/asset/${id}`,
url: `/home-api/asset/${id}`,
method: 'put',
data: asset
})
export const deleteAssetApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/asset/${id}`,
url: `/home-api/asset/${id}`,
method: 'delete'
})
export const queryAssetApi = (category: string): Promise<IAssetRecord[]> =>
cloudService({
url: '/home-service/asset',
url: '/home-api/asset',
method: 'get',
params: { category }
})
export const queryAssetCategoryApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/asset/category',
url: '/home-api/asset/category',
method: 'get'
})

35
src/apis/award.ts Normal file
View File

@@ -0,0 +1,35 @@
import { cloudService } from './index'
import { IAward, IAwardQuery } from '@/types/award.ts'
export const addAwardApi = (award: IAward): Promise<boolean> =>
cloudService({
url: '/home-api/award',
method: 'post',
data: award
})
export const updateAwardApi = (id: number, award: IAward): Promise<boolean> =>
cloudService({
url: `/home-api/award/${id}`,
method: 'put',
data: award
})
export const deleteAwardApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-api/award/${id}`,
method: 'delete'
})
export const queryAwardApi = (id: number): Promise<IAward> =>
cloudService({
url: `/home-api/award/${id}`,
method: 'get'
})
export const queryAwardListApi = (query: IAwardQuery): Promise<IAward[]> =>
cloudService({
url: '/home-api/award',
method: 'get',
params: query
})

View File

@@ -4,106 +4,113 @@ import {
IBillCategory,
IBillPay,
IBillQuery,
IBillRecord,
IBillRecord, IBillSearch
} from '@/types/bill'
import { IStatsChart } from 'vue3-common/dist/types'
export const addBillRecordApi = (billRecord: IBillRecord): Promise<boolean> =>
cloudService({
url: '/home-service/bill',
url: '/home-api/bill',
method: 'post',
data: billRecord
})
export const updateBillRecordApi = (id: number, billRecord: IBillRecord): Promise<boolean> =>
cloudService({
url: `/home-service/bill/${id}`,
url: `/home-api/bill/${id}`,
method: 'put',
data: billRecord
})
export const deleteBillRecordApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/bill/${id}`,
url: `/home-api/bill/${id}`,
method: 'DELETE'
})
export const queryBillRecordApi = (id: number): Promise<IBillRecord> =>
cloudService({
url: `/home-service/bill/${id}`,
url: `/home-api/bill/${id}`,
method: 'get'
})
export const queryBillSummaryApi = (query: IBillQuery): Promise<IBillRecord[]> =>
cloudService({
url: '/home-service/bill/summary',
url: '/home-api/bill/summary',
method: 'get',
params: query
})
export const searchBillSummaryApi = (search: IBillSearch): Promise<IBillRecord[]> =>
cloudService({
url: '/home-api/bill/search',
method: 'get',
params: search
})
export const queryBillStatsApi = (type: string, query: IBillQuery): Promise<IStatsChart[]> =>
cloudService({
url: `/home-service/bill/stats/${type}`,
url: `/home-api/bill/stats/${type}`,
method: 'get',
params: query
})
export const queryBillPayApi = (): Promise<IBillPay[]> =>
cloudService({
url: '/home-service/bill/pay',
url: '/home-api/bill/pay',
method: 'get'
})
export const queryBillBookApi = (): Promise<IBillBook[]> =>
cloudService({
url: '/home-service/bill/book',
url: '/home-api/bill/book',
method: 'get'
})
export const queryBillCategoryApi = (): Promise<IBillCategory[]> =>
cloudService({
url: '/home-service/bill/category',
url: '/home-api/bill/category',
method: 'get'
})
export const addBillPayApi = (billPay: IBillPay): Promise<boolean> =>
cloudService({
url: '/home-service/bill-manage/pay',
url: '/home-api/bill-manage/pay',
method: 'post',
data: billPay
})
export const updateBillPayApi = (id: number, billPay: IBillPay): Promise<boolean> =>
cloudService({
url: `/home-service/bill-manage/pay/${id}`,
url: `/home-api/bill-manage/pay/${id}`,
method: 'put',
data: billPay
})
export const addBillBookApi = (billBook: IBillBook): Promise<boolean> =>
cloudService({
url: '/home-service/bill-manage/book',
url: '/home-api/bill-manage/book',
method: 'post',
data: billBook
})
export const updateBillBookApi = (id: number, billBook: IBillBook): Promise<boolean> =>
cloudService({
url: `/home-service/bill-manage/book/${id}`,
url: `/home-api/bill-manage/book/${id}`,
method: 'put',
data: billBook
})
export const addBillCategoryApi = (billCategory: IBillCategory): Promise<boolean> =>
cloudService({
url: '/home-service/bill-manage/category',
url: '/home-api/bill-manage/category',
method: 'post',
data: billCategory
})
export const updateBillCategoryApi = (id: number, billCategory: IBillCategory): Promise<boolean> =>
cloudService({
url: `/home-service/bill-manage/category/${id}`,
url: `/home-api/bill-manage/category/${id}`,
method: 'put',
data: billCategory
})

View File

@@ -1,18 +1,19 @@
import { cloudService } from './index'
import { IBlog, IBlogCategory, IBlogStats, INewBlog, IQueryBlog } from '@/types/blog'
import { IBlog, IBlogCategory, IBlogComment, IBlogStats, IQueryBlog } from '@/types/blog'
import qs from 'qs'
import { IPageRecord } from '@/types'
export const queryBlogApi = (currentPage: number, pageSize: number): Promise<any> =>
export const queryBlogApi = (currentPage: number, pageSize: number): Promise<IPageRecord<IBlog>> =>
cloudService({
url: '/blog-service/blog/page',
url: '/blog-api/blog/page',
method: 'get',
params: { currentPage, pageSize }
})
export const queryBlogByConditionApi = (query: IQueryBlog): Promise<IBlog[]> =>
cloudService({
url: '/blog-service/blog/condition',
url: '/blog-api/blog/condition',
method: 'get',
params: query,
paramsSerializer: (params) => {
@@ -22,70 +23,30 @@ export const queryBlogByConditionApi = (query: IQueryBlog): Promise<IBlog[]> =>
export const queryBlogByIdApi = (id: number): Promise<IBlog> =>
cloudService({
url: `/blog-service/blog/content/${id}`,
url: `/blog-api/blog/${id}/content`,
method: 'get'
})
export const queryBlogCategoryApi = (): Promise<IBlogCategory[]> =>
cloudService({
url: '/blog-service/blog/category',
url: '/blog-api/blog/category',
method: 'get'
})
export const queryBlogStatsApi = (): Promise<IBlogStats> =>
cloudService({
url: '/blog-service/blog/stats',
url: '/blog-api/blog/stats',
method: 'get'
})
export const queryLatestBlogApi = (): Promise<IBlog[]> =>
cloudService({
url: '/blog-service/blog/latest',
url: '/blog-api/blog/latest',
method: 'get'
})
export const queryAdminBlogApi = (currentPage: number, pageSize: number): Promise<any> =>
export const queryBlogCommentApi = (blogId: number): Promise<IBlogComment[]> =>
cloudService({
url: '/admin-service/blog-manage/page',
method: 'get',
params: { currentPage, pageSize }
})
export const queryAdminBlogByIdApi = (id: number): Promise<IBlog> =>
cloudService({
url: `/admin-service/blog-manage/content/${id}`,
url: `/blog-api/blog/${blogId}/comment`,
method: 'get'
})
export const queryAdminBlogCategoryApi = (): Promise<IBlogCategory[]> =>
cloudService({
url: '/admin-service/blog-manage/category',
method: 'get'
})
export const addBlogApi = (blog: INewBlog): Promise<boolean> =>
cloudService({
url: '/admin-service/blog-manage',
method: 'post',
data: blog
})
export const updateBlogApi = (id: number, blog: INewBlog): Promise<boolean> =>
cloudService({
url: `/admin-service/blog-manage/${id}`,
method: 'put',
data: blog
})
export const deleteBlogApi = (id: number): Promise<boolean> =>
cloudService({
url: `/admin-service/blog-manage/${id}`,
method: 'DELETE'
})
export const queryBlogVisitApi = (currentPage: number, pageSize: number): Promise<any> =>
cloudService({
url: '/admin-service/blog-manage/visit',
method: 'get',
params: { currentPage, pageSize }
})

View File

@@ -1,49 +0,0 @@
import { cloudService } from './index'
import { IBudgetInfo } from '@/types/budget'
export const addBudgetApi = (budget: IBudgetInfo): Promise<any> =>
cloudService({
url: '/home-service/budget',
method: 'post',
data: budget
})
export const updateBudgetApi = (id: number, budget: IBudgetInfo): Promise<any> =>
cloudService({
url: `/home-service/budget/${id}`,
method: 'put',
data: budget
})
export const deleteBudgetApi = (id: number): Promise<any> =>
cloudService({
url: `/home-service/budget/${id}`,
method: 'DELETE'
})
export const queryBudgetApi = (category: string): Promise<any> =>
cloudService({
url: '/home-service/budget',
method: 'get',
params: { category }
})
export const queryBudgetCategory = (): Promise<any> =>
cloudService({
url: '/home-service/budget/category',
method: 'get'
})
export const queryBudgetRankStats = (category: string): Promise<any> =>
cloudService({
url: '/home-service/budget/stats/rank',
method: 'get',
params: { category }
})
export const queryBudgetProjectStats = (category: string): Promise<any> =>
cloudService({
url: '/home-service/budget/stats/project',
method: 'get',
params: { category }
})

29
src/apis/car.ts Normal file
View File

@@ -0,0 +1,29 @@
import { cloudService } from './index'
import { ICarEvent, ICarEventQuery } from '@/types/car.ts'
export const addCarEventApi = (carEvent: ICarEvent): Promise<boolean> =>
cloudService({
url: '/home-api/car',
method: 'post',
data: carEvent
})
export const updateCarEventApi = (id: number, carEvent: ICarEvent): Promise<boolean> =>
cloudService({
url: `/home-api/car/${id}`,
method: 'put',
data: carEvent
})
export const deleteCarEventApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-api/car/${id}`,
method: 'delete'
})
export const queryCarEventListApi = (query: ICarEventQuery): Promise<ICarEvent[]> =>
cloudService({
url: '/home-api/car',
method: 'get',
params: query
})

View File

@@ -1,9 +1,10 @@
import { cloudService } from './index'
import { IFileInfo } from '@/types/file'
import type { AxiosProgressEvent } from 'axios'
export const uploadFileApi = (form: FormData, path: string, md5: string): Promise<any> =>
cloudService({
url: '/home-service/file',
url: '/home-api/file',
headers: {
'Content-Type': 'multipart/form-data'
},
@@ -12,38 +13,22 @@ export const uploadFileApi = (form: FormData, path: string, md5: string): Promis
data: form
})
/**
* 上传图片到Hello图床
* @param form 表单
* @param token token
*/
export const uploadHelloImageApi = (form: FormData, token: string): Promise<any> =>
export const uploadAListFileApi = (form: FormData,
progress: (event: AxiosProgressEvent) => void): Promise<string> =>
cloudService({
url: '/hello-service/upload',
url: '/home-api/file/alist',
headers: {
Authorization: token,
'Content-Type': 'multipart/form-data'
},
onUploadProgress: progress,
timeout: 0,
method: 'post',
data: form
})
/**
* 获取Hello图床相册
* @param token token
*/
export const getHelloAlbumsApi = (token: string): Promise<any> =>
cloudService({
url: '/hello-service/albums',
headers: {
Authorization: token
},
method: 'get'
})
export const getFolderInfoApi = (folderName: string): Promise<IFileInfo[]> =>
cloudService({
url: '/home-service/file/catalog',
url: '/home-api/file/catalog',
method: 'get',
params: { folderName }
})

View File

@@ -1,81 +1,89 @@
import { cloudService } from './index'
import { IFoodQuery, IFoodRecipe, IFoodRecord, IFoodStats } from '@/types/food'
import { IPageRecord } from '@/types'
export const addFoodRecipeApi = (foodRecipe: IFoodRecipe): Promise<boolean> =>
cloudService({
url: '/home-service/food/recipe',
url: '/home-api/food/recipe',
method: 'post',
data: foodRecipe
})
export const updateFoodRecipeApi = (id: number, foodRecipe: IFoodRecipe): Promise<boolean> =>
cloudService({
url: `/home-service/food/recipe/${id}`,
url: `/home-api/food/recipe/${id}`,
method: 'put',
data: foodRecipe
})
export const deleteFoodRecipeApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/food/recipe/${id}`,
url: `/home-api/food/recipe/${id}`,
method: 'DELETE'
})
export const queryFoodRecipeByIdApi = (id: number): Promise<any> =>
export const queryFoodRecipeByIdApi = (id: number): Promise<IFoodRecipe> =>
cloudService({
url: `/home-service/food/recipe/${id}`,
url: `/home-api/food/recipe/${id}`,
method: 'get'
})
export const queryFoodRecipeApi = (currentPage: number, pageSize: number,
query: IFoodQuery): Promise<any> =>
query: IFoodQuery): Promise<IPageRecord<IFoodRecipe>> =>
cloudService({
url: '/home-service/food/recipe/page',
url: '/home-api/food/recipe/page',
method: 'get',
params: { currentPage, pageSize, ...query }
})
export const searchFoodRecipeApi = (name: string): Promise<IFoodRecipe> =>
cloudService({
url: '/home-api/food/recipe/search',
method: 'get',
params: { name }
})
export const queryFoodNameListApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/food/recipe/name',
url: '/home-api/food/recipe/name',
method: 'get'
})
export const addFoodRecordApi = (foodRecord: IFoodRecord): Promise<boolean> =>
cloudService({
url: '/home-service/food/record',
url: '/home-api/food/record',
method: 'post',
data: foodRecord
})
export const updateFoodRecordApi = (id: number, foodRecord: IFoodRecord): Promise<boolean> =>
cloudService({
url: `/home-service/food/record/${id}`,
url: `/home-api/food/record/${id}`,
method: 'put',
data: foodRecord
})
export const deleteFoodRecordApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/food/record/${id}`,
url: `/home-api/food/record/${id}`,
method: 'DELETE'
})
export const queryFoodRecordApi = (startDate: string, endDate: string): Promise<IFoodRecord[]> =>
cloudService({
url: '/home-service/food/record',
url: '/home-api/food/record',
method: 'get',
params: { startDate, endDate }
})
export const queryFoodCategoryApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/food/category',
url: '/home-api/food/category',
method: 'get'
})
export const queryFoodStatsApi = (): Promise<IFoodStats> =>
cloudService({
url: '/home-service/food/stats',
url: '/home-api/food/stats',
method: 'get'
})

16
src/apis/health.ts Normal file
View File

@@ -0,0 +1,16 @@
import { cloudService } from './index'
import { IHealth, IHealthQuery } from '@/types/health'
export const updateHealthApi = (id: number, health: IHealth): Promise<boolean> =>
cloudService({
url: `/home-api/health/${id}`,
method: 'put',
data: health
})
export const queryHealthApi = (query: IHealthQuery): Promise<IHealth[]> =>
cloudService({
url: '/home-api/health',
method: 'get',
params: query
})

View File

@@ -4,33 +4,33 @@ import { IJournal, IJournalQuery } from '@/types/journal.ts'
export const addJournalApi = (journal: IJournal): Promise<boolean> =>
cloudService({
url: '/home-service/journal',
url: '/home-api/journal',
method: 'post',
data: journal
})
export const updateJournalApi = (id: number, journal: IJournal): Promise<boolean> =>
cloudService({
url: `/home-service/journal/${id}`,
url: `/home-api/journal/${id}`,
method: 'put',
data: journal
})
export const deleteJournalApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/journal/${id}`,
url: `/home-api/journal/${id}`,
method: 'delete'
})
export const queryJournalApi = (query: IJournalQuery): Promise<IJournal[]> =>
cloudService({
url: '/home-service/journal',
url: '/home-api/journal',
method: 'get',
params: { ...query }
})
export const queryJournalCategoryApi = (): Promise<IStatsChart[]> =>
cloudService({
url: '/home-service/journal/category',
url: '/home-api/journal/category',
method: 'get'
})

View File

@@ -3,27 +3,27 @@ import { IKpi, IKpiQuery } from '@/types/kpi'
export const addKpiApi = (kpi: IKpi): Promise<boolean> =>
cloudService({
url: '/home-service/kpi',
url: '/home-api/kpi',
method: 'post',
data: kpi
})
export const updateKpiApi = (id: number, kpi: IKpi): Promise<boolean> =>
cloudService({
url: `/home-service/kpi/${id}`,
url: `/home-api/kpi/${id}`,
method: 'put',
data: kpi
})
export const deleteKpiApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/kpi/${id}`,
url: `/home-api/kpi/${id}`,
method: 'delete'
})
export const queryKpiApi = (query: IKpiQuery): Promise<IKpi[]> =>
cloudService({
url: '/home-service/kpi',
url: '/home-api/kpi',
method: 'get',
params: { ...query }
})

View File

@@ -3,33 +3,33 @@ import { ILedgerRecord } from '@/types/ledger'
export const addLedgerApi = (ledger: ILedgerRecord): Promise<boolean> =>
cloudService({
url: '/home-service/ledger',
url: '/home-api/ledger',
method: 'post',
data: ledger
})
export const updateLedgerApi = (id: number, ledger: ILedgerRecord): Promise<boolean> =>
cloudService({
url: `/home-service/ledger/${id}`,
url: `/home-api/ledger/${id}`,
method: 'put',
data: ledger
})
export const deleteLedgerApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/ledger/${id}`,
url: `/home-api/ledger/${id}`,
method: 'delete'
})
export const queryLedgerApi = (book: string): Promise<ILedgerRecord[]> =>
cloudService({
url: '/home-service/ledger',
url: '/home-api/ledger',
method: 'get',
params: { book }
})
export const queryLedgerBookApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/ledger/book',
url: '/home-api/ledger/book',
method: 'get'
})

View File

@@ -1,35 +1,35 @@
import { cloudService } from './index'
import { IPassword, IQueryPassword } from '@/types/password'
import { IPassword, IPasswordQuery } from '@/types/password'
export const addPasswordApi = (password: IPassword): Promise<boolean> =>
cloudService({
url: '/home-service/password',
url: '/home-api/password',
method: 'post',
data: password
})
export const updatePasswordApi = (id: number, password: IPassword): Promise<boolean> =>
cloudService({
url: `/home-service/password/${id}`,
url: `/home-api/password/${id}`,
method: 'put',
data: password
})
export const deletePasswordApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/password/${id}`,
url: `/home-api/password/${id}`,
method: 'delete'
})
export const queryPasswordApi = (query: IQueryPassword): Promise<IPassword[]> =>
export const queryPasswordApi = (query: IPasswordQuery): Promise<IPassword[]> =>
cloudService({
url: '/home-service/password',
url: '/home-api/password',
method: 'get',
params: query
})
export const queryPasswordCategoryApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/password/category',
url: '/home-api/password/category',
method: 'get'
})

View File

@@ -1,37 +1,43 @@
import { cloudService } from './index'
import { IPeriodDay, IPeriodSetting } from '@/types/period.ts'
import { IMenstrualPeriod, IPeriodDay, IPeriodSetting } from '@/types/period.ts'
export const updatePeriodDayApi = (id: number, periodDay: IPeriodDay): Promise<boolean> =>
cloudService({
url: `/home-service/period/day/${id}`,
url: `/home-api/period/day/${id}`,
method: 'put',
data: periodDay
})
export const queryPeriodDayApi = (month: string): Promise<IPeriodDay[]> =>
cloudService({
url: '/home-service/period/day',
url: '/home-api/period/day',
method: 'get',
params: { month }
})
export const getMenstrualDaysApi = (): Promise<IMenstrualPeriod[]> =>
cloudService({
url: '/home-api/period//menstruate/days',
method: 'get'
})
export const getMenstruateFirstDateApi = (month: string): Promise<string> =>
cloudService({
url: '/home-service/period/menstruate',
url: '/home-api/period/menstruate',
method: 'get',
params: { month }
})
export const updatePeriodSettingApi = (id: number, setting: IPeriodSetting): Promise<boolean> =>
cloudService({
url: `/home-service/period/setting/${id}`,
url: `/home-api/period/setting/${id}`,
method: 'put',
data: setting
})
export const queryPeriodSettingApi = (): Promise<IPeriodSetting> =>
cloudService({
url: '/home-service/period/setting',
url: '/home-api/period/setting',
method: 'get'
})

View File

@@ -3,40 +3,40 @@ import { IPlanInfo, IPlanQuery } from '@/types/plan.ts'
export const addPlanApi = (plan: IPlanInfo): Promise<any> =>
cloudService({
url: '/home-service/plan',
url: '/home-api/plan',
method: 'post',
data: plan
})
export const updatePlanApi = (id: number, plan: IPlanInfo): Promise<any> =>
cloudService({
url: `/home-service/plan/${id}`,
url: `/home-api/plan/${id}`,
method: 'put',
data: plan
})
export const setPlanStatusApi = (id: number, status: boolean): Promise<any> =>
cloudService({
url: `/home-service/plan/${id}/status`,
url: `/home-api/plan/${id}/status`,
method: 'put',
params: { status }
})
export const deletePlanApi = (id: number): Promise<any> =>
cloudService({
url: `/home-service/plan/${id}`,
url: `/home-api/plan/${id}`,
method: 'delete'
})
export const queryPlanApi = (query: IPlanQuery): Promise<any> =>
cloudService({
url: '/home-service/plan',
url: '/home-api/plan',
method: 'get',
params: query
})
export const queryPlanStatsApi = (): Promise<any> =>
cloudService({
url: '/home-service/plan/stats',
url: '/home-api/plan/stats',
method: 'get'
})

View File

@@ -1,41 +1,41 @@
import { cloudService } from './index'
import { IProduct, IQueryProduct } from '@/types/product.ts'
import { IProduct, IProductQuery } from '@/types/product.ts'
export const addProductApi = (product: IProduct): Promise<boolean> =>
cloudService({
url: '/home-service/product',
url: '/home-api/product',
method: 'post',
data: product
})
export const updateProductApi = (id: number, product: IProduct): Promise<boolean> =>
cloudService({
url: `/home-service/product/${id}`,
url: `/home-api/product/${id}`,
method: 'put',
data: product
})
export const deleteProductApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/product/${id}`,
url: `/home-api/product/${id}`,
method: 'delete'
})
export const queryProductApi = (id: number): Promise<IProduct> =>
cloudService({
url: `/home-service/product/${id}`,
url: `/home-api/product/${id}`,
method: 'get'
})
export const queryProductListApi = (query: IQueryProduct): Promise<IProduct[]> =>
export const queryProductListApi = (query: IProductQuery): Promise<IProduct[]> =>
cloudService({
url: '/home-service/product',
url: '/home-api/product',
method: 'get',
params: query
})
export const queryProductCategoryApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/product/category',
url: '/home-api/product/category',
method: 'get'
})

View File

@@ -3,6 +3,6 @@ import { IQuartzInfo } from '@/types/quartz'
export const queryQuartzApi = (): Promise<IQuartzInfo[]> =>
cloudService({
url: '/home-service/quartz',
url: '/home-api/quartz',
method: 'get'
})

View File

@@ -3,14 +3,14 @@ import { ISession } from '@/types/auth'
export const loginHomeApi = (name: string, password: string): Promise<ISession> =>
cloudService({
url: '/home-service/session',
url: '/home-api/session',
method: 'post',
params: { name, password }
})
export const loginAdminApi = (name: string, password: string): Promise<ISession> =>
cloudService({
url: '/admin-service/session',
url: '/admin-api/session',
method: 'post',
params: { name, password }
})

View File

@@ -3,80 +3,80 @@ import { ITravelQuery, ITravelRecord, ITravelSpot, ITravelStats } from '@/types/
export const addTravelSpotApi = (travelSpot: ITravelSpot): Promise<boolean> =>
cloudService({
url: '/home-service/travel/spot',
url: '/home-api/travel/spot',
method: 'post',
data: travelSpot
})
export const updateTravelSpotApi = (id: number, travelSpot: ITravelSpot): Promise<boolean> =>
cloudService({
url: `/home-service/travel/spot/${id}`,
url: `/home-api/travel/spot/${id}`,
method: 'put',
data: travelSpot
})
export const deleteTravelSpotApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/travel/spot/${id}`,
url: `/home-api/travel/spot/${id}`,
method: 'DELETE'
})
export const queryTravelSpotApi = (id: number): Promise<ITravelSpot> =>
cloudService({
url: `/home-service/travel/spot/${id}`,
url: `/home-api/travel/spot/${id}`,
method: 'get'
})
export const queryTravelSpotListApi = (currentPage: number, pageSize: number,
query: ITravelQuery): Promise<any> =>
cloudService({
url: '/home-service/travel/spot/page',
url: '/home-api/travel/spot/page',
method: 'get',
params: { currentPage, pageSize, ...query }
})
export const queryTravelSpotNameListApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/travel/spot/name',
url: '/home-api/travel/spot/name',
method: 'get'
})
export const addTravelRecordApi = (travelRecord: ITravelRecord): Promise<boolean> =>
cloudService({
url: '/home-service/travel/record',
url: '/home-api/travel/record',
method: 'post',
data: travelRecord
})
export const updateTravelRecordApi = (id: number, travelRecord: ITravelRecord): Promise<boolean> =>
cloudService({
url: `/home-service/travel/record/${id}`,
url: `/home-api/travel/record/${id}`,
method: 'put',
data: travelRecord
})
export const deleteTravelRecordApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-service/travel/record/${id}`,
url: `/home-api/travel/record/${id}`,
method: 'DELETE'
})
export const queryTravelRecordApi = (startDate: string, endDate: string):
Promise<ITravelRecord[]> =>
cloudService({
url: '/home-service/travel/record',
url: '/home-api/travel/record',
method: 'get',
params: { startDate, endDate }
})
export const queryTravelCategoryApi = (): Promise<string[]> =>
cloudService({
url: '/home-service/travel/category',
url: '/home-api/travel/category',
method: 'get'
})
export const queryTravelStatsApi = (): Promise<ITravelStats> =>
cloudService({
url: '/home-service/travel/stats',
url: '/home-api/travel/stats',
method: 'get'
})

View File

@@ -1,31 +0,0 @@
import { cloudService } from './index'
export const getAllTravelRecord = (): any =>
cloudService({
url: '/travelRecord/getAllTravelRecord',
method: 'get'
})
export const getAllTravelRecordByPage = (currentPage: number, pageSize: number): any =>
cloudService({
url: '/travelRecord/getAllTravelRecordByPage',
method: 'get',
params: { currentPage, pageSize }
})
export const addTravelRecord = (travelJournal): any =>
cloudService({
url: '/travelRecord/addTravelRecord',
method: 'post',
data: travelJournal
})
export const addTravelRecordImage = (imageList, date, place): any =>
cloudService({
url: '/travelRecord/addTravelRecordImage',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
params: { date, place },
data: imageList
})

View File

@@ -3,13 +3,13 @@ import { IUser } from '@/types/auth.ts'
export const queryUserApi = (id: number): Promise<IUser> =>
cloudService({
url: `/home-service/user/${id}`,
url: `/home-api/user/${id}`,
method: 'get'
})
export const updateUserApi = (id: number, user: IUser): Promise<boolean> =>
cloudService({
url: `/home-service/user/${id}`,
url: `/home-api/user/${id}`,
method: 'put',
data: user
})

Binary file not shown.

Binary file not shown.

View File

@@ -29,8 +29,7 @@ import { defineProps, PropType } from 'vue'
import { useRouter } from 'vue-router'
import { useAnniversaryStore } from '@/store/anniversary.ts'
import { IAnniversary } from '@/types/anniversary.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { getAnniversaryRemainDays } from '@/utils/home/anniversaryUtil'
import { getAnniversaryRemainDays } from '@/utils/home/anniversaryUtil.ts'
const router = useRouter()
const anniversaryStore = useAnniversaryStore()
@@ -45,10 +44,10 @@ const props = defineProps({
const selectDateCardClick = async () => {
anniversaryStore.anniversaryApiType = 'UPDATE'
if (isMobile() && props.dateItem?.category !== '节假日') {
if (props.dateItem?.category !== '节假日') {
anniversaryStore.currentAnniversary = props.dateItem as IAnniversary
const { id } = anniversaryStore.currentAnniversary
await router.push({ name: 'MobileHomeAnniversaryDetailId', params: { id } })
await router.push({ name: 'AnniversaryDetailId', params: { id } })
}
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="mobile-common-query card-item">
<div class="query-item">
<span>分类</span>
<el-segmented v-model="anniversaryStore.queryInfo.category"
@@ -7,27 +7,11 @@
@change="anniversaryStore.queryAnniversaryList()"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="anniversaryStore.queryInfo.category"
placeholder="请选择纪念日类别" clearable
@change="anniversaryStore.queryAnniversaryList()"
style="width: 180px">
<el-option
v-for="(item, index) in queryCategoryList"
:key="index" :label="item.label" :value="item.value"
/>
</el-select>
<el-button :icon="Refresh" circle @click="refreshClick"/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useAnniversaryStore } from '@/store/anniversary.ts'
import { Refresh } from '@element-plus/icons-vue'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ICategory } from '@/types'
const anniversaryStore = useAnniversaryStore()
@@ -39,11 +23,6 @@ onMounted(async () => {
initCategoryList()
})
const refreshClick = async () => {
anniversaryStore.queryInfo.category = ''
await anniversaryStore.queryAnniversaryList()
}
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({

View File

@@ -9,10 +9,10 @@
<script setup lang="ts">
import { defineProps, PropType } from 'vue'
import { IAnniversaryCategory } from '@/types/anniversary'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { useAnniversaryStore } from '@/store/anniversary'
import { IAnniversaryCategory } from '@/types/anniversary.ts'
import { useAnniversaryStore } from '@/store/anniversary.ts'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
const props = defineProps({
categoryItem: {
@@ -29,9 +29,11 @@ const router = useRouter()
const anniversaryStore = useAnniversaryStore()
const selectCategoryClick = () => {
if (isMobile()) {
if (props.count) {
anniversaryStore.queryInfo.category = props.categoryItem?.name as string
router.push({ name: 'MobileHomeAnniversaryList' })
router.push({ name: 'AnniversaryList' })
} else {
ElMessage.info('暂无纪念日')
}
}
</script>

View File

@@ -33,11 +33,9 @@
<script setup lang="ts">
import { SvgIcon } from 'vue3-common'
import { defineProps, PropType } from 'vue'
import { IAsset, IAssetItem } from '@/types/asset'
import { IAsset, IAssetItem } from '@/types/asset.ts'
import { formatAmount } from 'vue3-common/utils/numberUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { useAssetStore } from '@/store/asset'
import { useAssetStore } from '@/store/asset.ts'
import { useRouter } from 'vue-router'
const props = defineProps({
@@ -62,15 +60,7 @@ const editClick = async (asset: IAssetItem) => {
assetStore.assetApiType = 'UPDATE'
assetStore.currentAssetRecord = assetStore.assetRecordList.find((item) => item.id === asset.id)
if (isMobile()) {
await router.push({ name: 'MobileHomeAssetDetailId', params: { id: asset.id } })
} else {
assetStore.assetDialog = {
title: '编辑资产',
visible: true,
data: deepCopyObject(assetStore.currentAssetRecord)
}
}
await router.push({ name: 'AssetDetailId', params: { id: asset.id } })
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="mobile-common-query card-item">
<div class="query-item">
<span>分类</span>
<el-segmented v-model="assetStore.queryInfo.category"
@@ -7,27 +7,11 @@
@change="assetStore.refreshInfo()"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="assetStore.queryInfo.category"
placeholder="请选择纪念日类别" clearable
@change="assetStore.refreshInfo()"
style="width: 180px">
<el-option
v-for="(item, index) in queryCategoryList"
:key="index" :label="item.label" :value="item.value"
/>
</el-select>
<el-button :icon="Refresh" circle @click="refreshClick"/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useAssetStore } from '@/store/asset.ts'
import { Refresh } from '@element-plus/icons-vue'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ICategory } from '@/types'
const assetStore = useAssetStore()
@@ -39,11 +23,6 @@ onMounted(async () => {
initCategoryList()
})
const refreshClick = async () => {
assetStore.queryInfo.category = ''
await assetStore.queryAssetList()
}
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({

View File

@@ -0,0 +1,97 @@
<template>
<div class="life-record-card card-item">
<el-image v-if="props.item?.imageList.length > 0"
:src="`${props.item?.imageList[0]}${ThumbImageQuery}`"
fit="contain" alt="暂无图片" @click="viewImageClick">
<template #error>
<div class="image-slot">
<span>暂无图片</span>
</div>
</template>
</el-image>
<el-empty v-else description="暂无图片" />
<van-swipe-cell>
<div class="record-info" @click="editClick()">
<div class="top-container">
<div class="info-list">
<div class="info-item">
<span class="title">{{ props.item.name }}</span>
</div>
</div>
</div>
<div class="bottom-container">
<div class="info-list">
<div class="info-item">
<i class="fa-solid fa-location-dot" style="color: #6A5ACD"></i>
{{ props.item.location }}
</div>
</div>
<div class="info-list">
<div class="info-item">
<i class="fa-solid fa-flag"></i>
{{ props.item.owner }}
</div>
<div class="info-item">
<i class="fa-regular fa-calendar-check"></i>
{{ props.item.date }}
</div>
</div>
</div>
</div>
<template #right>
<van-button square type="primary" text="编辑" style="height: 100%;"
@click="editClick"/>
<van-button square type="danger" text="删除" style="height: 100%;"
@click="deleteClick()"/>
</template>
</van-swipe-cell>
</div>
</template>
<script setup lang="ts">
import { useAwardStore } from '@/store/award.ts'
import { defineProps, PropType } from 'vue'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { useRouter } from 'vue-router'
import { RawImageQuery, ThumbImageQuery } from '@/utils'
import { showImagePreview } from 'vant'
import { IAward } from '@/types/award.ts'
const awardStore = useAwardStore()
const router = useRouter()
const props = defineProps({
item: {
required: true,
type: Object as PropType<IAward>
}
})
const viewImageClick = () => {
const imageList = props.item.imageList.map((item) => `${item}${RawImageQuery}`)
showImagePreview({ images: imageList, closeable: true })
}
const editClick = async () => {
const id = props.item?.id
awardStore.awardApiType = 'UPDATE'
awardStore.currentAward = props.item as IAward
await router.push({ name: 'AwardDetailId', params: { id } })
}
const deleteClick = () => {
awardStore.awardApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该记录: ${props.item?.name}?`).then(() => {
awardStore.handleAwardApi(props.item as IAward)
})
}
</script>
<style lang="scss">
@use "@/styles/home/life.record.module";
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div class="mobile-common-query card-item">
<div class="query-item">
<span>类型</span>
<el-segmented v-model="awardStore.queryInfo.type"
:options="queryTypeList"
@change="awardStore.queryAwardList()"/>
</div>
</div>
</template>
<script setup lang="ts">
import { useAwardStore } from '@/store/award.ts'
const awardStore = useAwardStore()
const queryTypeList = [
{
label: '全部',
value: ''
},
{
label: '获奖',
value: '获奖'
},
{
label: '技能',
value: '技能'
}, {
label: '身份',
value: '身份'
}, {
label: '财产',
value: '财产'
}
]
</script>

View File

@@ -12,13 +12,13 @@
</template>
<script setup lang="ts">
import BillDailyItem from '@/components/Home/Bill/BillDailyItem.vue'
import BillDailyItem from '@/components/Bill/BillDailyItem.vue'
import { reactive, watch, ref, onMounted } from 'vue'
import { useBillStore } from '@/store/bill.ts'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { CalendarDay, Page } from 'v-calendar/dist/types/src/utils/page'
import { getCalendarDateList } from '@/utils/home/billUtil.ts'
import { useAppStore } from '@/store/app'
import { useAppStore } from '@/store/app.ts'
const calendarRef = ref()
const billStore = useBillStore()
@@ -67,7 +67,7 @@ const getCalendarAttrDates = () => {
})
dailyInfo.attrs.push({
dot: 'red',
dates: getCalendarDateList(billStore.billRecordList, 'expensive')
dates: getCalendarDateList(billStore.billRecordList, 'expense')
})
dailyInfo.attrs.push({
dot: 'green',

View File

@@ -1,5 +1,5 @@
<template>
<div class="bill-chart"
<div class="bill-chart common-chart"
v-loading="billStore.isLoading"
element-loading-text="正在加载中...">
<div class="chart-container card-item">
@@ -8,19 +8,22 @@
</div>
<div class="chart" ref="billDailyChartRef"/>
</div>
<div class="chart-container card-item">
<div class="content">
<span class="title">📊类别统计</span>
</div>
<div class="chart" ref="billCategoryChartRef"/>
</div>
<div class="chart-container card-item">
<div class="content">
<span class="title">📊账本统计</span>
</div>
<div class="chart" ref="billBookChartRef"/>
</div>
<div v-if="isMobile()" class="chart-container card-item">
<div class="chart-container card-item">
<div class="content">
<span class="title">📊排行榜</span>
</div>
@@ -28,24 +31,17 @@
<bill-rank />
</div>
</div>
<div v-else class="chart-container card-item">
<div class="content">
<span class="title">📊排行榜</span>
</div>
<div class="chart" ref="billRankChartRef"/>
</div>
</div>
</template>
<script setup lang="ts">
import BillRank from '@/components/Home/Bill/BillRank.vue'
import BillRank from '@/components/Bill/BillRank.vue'
import { onMounted, ref, watch } from 'vue'
import * as echarts from 'echarts'
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
import { useBillStore } from '@/store/bill'
import { useBillStore } from '@/store/bill.ts'
import { useAppStore } from '@/store/app.ts'
import type { IStatsChart } from 'vue3-common/types'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { formatDate } from 'vue3-common/utils/dateUtil'
const billStore = useBillStore()
const appStore = useAppStore()
@@ -53,12 +49,10 @@ const appStore = useAppStore()
const billDailyChartRef = ref<HTMLElement>()
const billBookChartRef = ref<HTMLElement>()
const billCategoryChartRef = ref<HTMLElement>()
const billRankChartRef = ref<HTMLElement>()
let billDailyChart: echarts.ECharts
let billBookChart: echarts.ECharts
let billCategoryChart: echarts.ECharts
let billRankChart: echarts.ECharts
watch(() => billStore.isRefresh, () => {
updateChart()
@@ -68,29 +62,29 @@ onMounted(() => {
billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
billBookChart = echarts.init(billBookChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
billCategoryChart = echarts.init(billCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
if (!isMobile()) {
billRankChart = echarts.init(billRankChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
}
})
const updateChart = () => {
const MAX_CATEGORY_COUNT = 5
const MAX_RANK_COUNT = 10
//
const dailyXAxis = billStore.billDateStatsList.map((item) => item.name)
const dailyXAxis = billStore.billDateStatsList.map((item) => {
return formatDate(item.name, 'MM-DD')
})
const dailyYAxis = billStore.billDateStatsList.map((item) => item.value)
billDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '金额(元)', dailyYAxis))
billDailyChart.setOption({
series: [{
itemStyle: {
color: billStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
color: billStore.billType === 'expense' ? '#CA6C65' : '#6FBB69'
},
markPoint: {
label: {
color: '#FFFFFF'
}
},
label: {
show: false
}
}]
})
@@ -102,7 +96,7 @@ const updateChart = () => {
billBookChart.setOption({
series: [{
itemStyle: {
color: billStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
color: billStore.billType === 'expense' ? '#CA6C65' : '#6FBB69'
},
markPoint: {
label: {
@@ -116,7 +110,6 @@ const updateChart = () => {
const billCategoryList = billStore.billCategoryStatsList.slice(0, MAX_CATEGORY_COUNT)
billCategoryChart.setOption(pieChartOptions('rank', '元', billCategoryList))
if (isMobile()) {
billCategoryChart.setOption({
series: [{
radius: ['20%', '50%'],
@@ -126,19 +119,4 @@ const updateChart = () => {
}]
})
}
//
const billRankList: IStatsChart[] = []
const resultList = billStore.billRecordList.filter((item) => item.type === billStore.billType)
resultList.sort((a, b) => b.amount - a.amount).forEach((item) => {
billRankList.push({
name: item.content,
value: item.amount
})
})
if (!isMobile()) {
billRankChart.setOption(pieChartOptions('rank', '元', billRankList.slice(0, MAX_RANK_COUNT)))
}
}
</script>

View File

@@ -5,7 +5,7 @@
<daily-summary :date="dailyItemInfo.date">
<template #extra>
<span>收入{{ formatAmount(dailyItemInfo.totalIncome, true) }}</span>
<span>支出{{ formatAmount(dailyItemInfo.totalExpensive, true) }}</span>
<span>支出{{ formatAmount(dailyItemInfo.totalExpense, true) }}</span>
</template>
</daily-summary>
@@ -36,20 +36,16 @@
</template>
<script setup lang="ts">
import DailySummary from '@/components/Home/DailySummary.vue'
import DailyCard from '@/components/Home/DailyCard.vue'
import DailySummary from '@/components/DailySummary.vue'
import DailyCard from '@/components/DailyCard.vue'
import { defineProps, onMounted, reactive, watch } from 'vue'
import { useBillStore } from '@/store/bill.ts'
import { useRouter } from 'vue-router'
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
import { IBillRecord } from '@/types/bill.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { formatAmount } from 'vue3-common/utils/numberUtil'
import { getCalendarDailyCount, getCategoryByName } from '@/utils/home/billUtil.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
const billStore = useBillStore()
const router = useRouter()
const props = defineProps({
date: {
@@ -72,7 +68,7 @@ onMounted(() => {
const dailyItemInfo = reactive({
date: '',
totalExpensive: 0,
totalExpense: 0,
totalIncome: 0,
itemList: [] as IBillRecord[]
})
@@ -88,7 +84,7 @@ const getBillDailyByDate = (date: string) => {
const calendarDaily = getCalendarDailyCount(billStore.billRecordList, date)
dailyItemInfo.itemList = calendarDaily.dailyRecordList
dailyItemInfo.totalIncome = calendarDaily.totalIncome
dailyItemInfo.totalExpensive = calendarDaily.totalExpensive
dailyItemInfo.totalExpense = calendarDaily.totalExpense
} else {
dailyItemInfo.itemList = []
}
@@ -101,19 +97,7 @@ const getBillDailyByDate = (date: string) => {
const selectBillRecordClick = async (billRecord: IBillRecord) => {
await billStore.queryBillRecord(billRecord.id as number)
billStore.billApiType = 'UPDATE'
if (isMobile()) {
await router.push({
name: 'MobileHomeBillDetailId',
params: { id: billStore.currentBillRecord.id }
})
} else {
billStore.billDialog = {
title: '编辑账单',
visible: true,
data: deepCopyObject(billStore.currentBillRecord)
}
}
billStore.billDrawer = true
}
</script>

View File

@@ -16,7 +16,7 @@
</template>
<script setup lang="ts">
import BillDailyItem from '@/components/Home/Bill/BillDailyItem.vue'
import BillDailyItem from '@/components/Bill/BillDailyItem.vue'
import { reactive, watch } from 'vue'
import { useBillStore } from '@/store/bill.ts'

View File

@@ -0,0 +1,263 @@
<template>
<el-drawer v-model="billStore.billDrawer" destroy-on-close
:with-header="false" direction="btt" size="95%">
<div class="common-mobile-view bill-detail">
<el-form ref="formRef" :model="billStore.currentBillRecord"
:rules="rules" label-position="top" label-width="80px"
class="form-item bill-form">
<div class="item-list">
<el-select v-model="billStore.currentBillRecord.bookName"
@change="changeBillBookNameEvent" style="width: 100px">
<el-option
v-for="item in billStore.billBookList"
:key="item.id" :label="item.name" :value="item.name"
/>
</el-select>
<el-select v-model="billStore.currentBillRecord.type"
@change="changeBillCategoryEvent" style="width: 100px">
<el-option
v-for="(item, index) in billInfo.billTypeList"
:key="index" :label="getBillTypeName(item)" :value="item"
/>
</el-select>
<el-select v-model="billStore.currentBillRecord.payAccount"
@change="changeBillCategoryEvent" style="width: 100px">
<el-option
v-for="item in billStore.billPayList"
:key="item.id" :label="item.name" :value="item.name"
/>
</el-select>
</div>
<el-radio-group v-model="billStore.currentBillRecord.category" class="bill-category-group">
<el-radio-button v-for="(item, index) in billInfo.billCategoryList"
:value="item.name" :key="index">
<template #default>
<i :class="item.icon"/>
<span style="margin-left: 5px;">{{ item.name }}</span>
</template>
</el-radio-button>
</el-radio-group>
<div class="bill-gap"></div>
<div class="item-list">
<el-date-picker
v-model="billStore.currentBillRecord.date"
type="date" :editable="false" placeholder="请选择账单时间"
value-format="YYYY-MM-DD"/>
<el-input v-model="billStore.currentBillRecord.location" autocomplete="off"
placeholder="请输入账单地点" clearable
show-word-limit maxlength="10">
<template #prefix>
<el-icon class="el-input__icon"><location/></el-icon>
</template>
</el-input>
</div>
<el-input v-model="billStore.currentBillRecord.content" autocomplete="off"
placeholder="请输入账单内容" clearable
show-word-limit maxlength="10">
<template #prefix>
<el-icon class="el-input__icon"><document/></el-icon>
</template>
</el-input>
<el-input v-model="billStore.currentBillRecord.remark" type="textarea"
placeholder="请输入账单备注" clearable
:rows="3" show-word-limit maxlength="50">
</el-input>
<number-input v-model:amount="billInfo.amountStr" class="amount-input" />
</el-form>
</div>
<template #footer>
<div style="flex: auto">
<el-button type="primary" @click="confirmClick">确认</el-button>
<el-button v-if="billStore.billApiType === 'UPDATE'"
type="danger" @click="deleteClick">删除</el-button>
<el-button type="info" @click="cancelClick">取消</el-button>
</div>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { useBillStore } from '@/store/bill.ts'
import { Location, Document } from '@element-plus/icons-vue'
import { ElMessage, FormRules, UploadUserFile } from 'element-plus'
import { billRecordRules } from '@/utils/element/elRules.ts'
import { nextTick, reactive, ref, watch } from 'vue'
import { IBillCategory } from '@/types/bill.ts'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import NumberInput from '@/components/NumberInput.vue'
const formRef = ref()
const rules = reactive<FormRules>(billRecordRules)
const billStore = useBillStore()
const imageFileList = ref<UploadUserFile[]>([])
const billInfo = reactive({
billTypeList: [] as string[],
billCategoryList: [] as IBillCategory[],
amountStr: ''
})
watch(() => billStore.billDrawer, async (value) => {
if (value) {
await initBillRecord()
initImageFileList()
billInfo.amountStr = billStore.currentBillRecord.amount.toString()
}
})
const initBillRecord = async () => {
// 如果是新增账单 默认选择第一个账本
if (billStore.billApiType !== 'UPDATE') {
billStore.currentBillRecord.bookName = billStore.billBookList[0].name
}
getBillTypeByBookName(billStore.currentBillRecord.bookName)
// 如果是新增账单 默认选择一个类别
if (billStore.billApiType !== 'UPDATE') {
billStore.currentBillRecord.type = billInfo.billTypeList[0]
}
getBillCategory()
// 如果是新增账单 默认选择一个分类
if (billStore.billApiType !== 'UPDATE') {
billStore.currentBillRecord.category = billInfo.billCategoryList[0].name
billStore.currentBillRecord.payAccount = billStore.billPayList[0].name
}
await nextTick(() => {
formRef.value.clearValidate()
})
}
const initImageFileList = () => {
imageFileList.value = []
billStore.currentBillRecord.imageList.forEach((image) => {
imageFileList.value.push({
name: 'image',
url: image
})
})
}
/**
* 获取账单类型名称
* @param type 账单类型
*/
const getBillTypeName = (type: string) => {
return type === 'expense' ? '支出' : '收入'
}
/**
* 选择账本事件
* @param value 账本名称
*/
const changeBillBookNameEvent = (value: string) => {
getBillTypeByBookName(value)
billStore.currentBillRecord.type = billInfo.billTypeList[0]
getBillCategory()
billStore.currentBillRecord.category = billInfo.billCategoryList[0].name
}
/**
* 选择账单类别事件
*/
const changeBillCategoryEvent = () => {
getBillCategory()
billStore.currentBillRecord.category = billInfo.billCategoryList[0].name
}
/**
* 通过账本获取账单类型
* @param bookName 账本
*/
const getBillTypeByBookName = (bookName: string) => {
billInfo.billTypeList = []
billStore.billCategoryList.forEach((item) => {
if (item.bookName === bookName && !billInfo.billTypeList.includes(item.type)) {
billInfo.billTypeList.push(item.type)
}
})
}
/**
* 获取账单类别
*/
const getBillCategory = () => {
billInfo.billCategoryList = billStore.billCategoryList.filter((value) => {
return value.bookName === billStore.currentBillRecord.bookName
&& value.type === billStore.currentBillRecord.type
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
if (billStore.currentBillRecord.content.trim()) {
formRef.value.validate().then(async () => {
billStore.currentBillRecord.amount = Number(billInfo.amountStr)
await billStore.handleBillApi(billStore.currentBillRecord)
billStore.billDrawer = false
})
} else {
ElMessage.error('请输入账单内容')
}
}
const cancelClick = () => {
billStore.billDrawer = false
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该账单内容?').then(async () => {
billStore.billApiType = 'DELETE'
await billStore.handleBillApi(billStore.currentBillRecord)
billStore.billDrawer = false
})
}
</script>
<style lang="scss">
.bill-detail {
.bill-form {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 10px;
.item-list {
display: flex;
justify-content: space-between;
gap: 5px;
}
.bill-category-group {
display: grid;
grid-template-columns: repeat(4, 1fr);
.el-radio-button {
.el-radio-button__inner {
padding: 8px 10px;
border: none;
}
}
}
.bill-gap {
flex-grow: 1;
}
}
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="common-query">
<el-select v-model="billStore.billQueryForm.billBook"
@change="billStore.refreshInfo()" style="width: 120px">
@change="billStore.refreshInfo(isStats)">
<el-option
v-for="(item, index) in billQuery.billBookList"
:key="index" :label="item.label" :value="item.name"
@@ -9,34 +9,32 @@
</el-select>
<el-select v-model="billStore.billDateType"
@change="billStore.refreshInfo()"
style="width: 120px;align-self: center;">
@change="billStore.refreshInfo(isStats)">
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
:label="item.label" :value="item.value"/>
</el-select>
<el-switch
v-model="billStore.billType"
inline-prompt
active-value="income" inactive-value="expensive"
active-text="收入账单" inactive-text="支出账单"
@change="billStore.refreshInfo()"
style="--el-switch-on-color: #6FBB69; --el-switch-off-color: #CA6C65;"
/>
<el-select v-if="isStats" v-model="billStore.billType"
@change="billStore.refreshInfo(isStats)">
<el-option v-for="(item, index) in billTypeOptions" :key="index"
:label="item.label" :value="item.value"/>
</el-select>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive } from 'vue'
import { computed, onMounted, reactive } from 'vue'
import { useBillStore } from '@/store/bill.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { IBillBook } from '@/types/bill.ts'
import { useRoute } from 'vue-router'
const billStore = useBillStore()
const route = useRoute()
const isStats = computed(() => route.path.includes('chart'))
onMounted(async () => {
await billStore.initBillInfo()
billQuery.billBookList = deepCopyObject(billStore.billBookList)
billQuery.billBookList.push({
color: '',
@@ -56,6 +54,15 @@ const dateTypeOptions = [{
value: 'year'
}]
const billTypeOptions = [{
label: '收入',
value: 'income'
},
{
label: '支出',
value: 'expense'
}]
const billQuery = reactive({
query: '',
billBookList: [] as IBillBook[]

View File

@@ -3,14 +3,15 @@
:key="index" class="rank-item">
<span class="rank-index">{{ index+1 }}.</span>
<span class="rank-content">{{ item.content }}</span>
<span class="rank-amount">{{ item.amount }}</span>
<span class="rank-amount">{{ formatAmount(item.amount) }}</span>
</div>
</template>
<script setup lang="ts">
import { useBillStore } from '@/store/bill'
import { useBillStore } from '@/store/bill.ts'
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
import { computed } from 'vue'
import { formatAmount } from 'vue3-common/utils/numberUtil'
const billStore = useBillStore()

View File

@@ -0,0 +1,34 @@
<template>
<div class="common-query">
<el-form :model="billStore.billSearchForm">
<el-form-item label="账单内容">
<el-input v-model="billStore.billSearchForm.content"
@input="billStore.searchInfo()"
placeholder="请输入账单内容" clearable/>
</el-form-item>
<el-form-item label="账单类别">
<el-input v-model="billStore.billSearchForm.category"
@input="billStore.searchInfo()"
placeholder="请输入账单类别" clearable/>
</el-form-item>
<el-form-item label="账单地址">
<el-input v-model="billStore.billSearchForm.location"
@input="billStore.searchInfo()"
placeholder="请输入账单地址" clearable/>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue'
import { useBillStore } from '@/store/bill.ts'
const billStore = useBillStore()
onMounted(async () => {
await billStore.searchInfo()
})
</script>

View File

@@ -2,7 +2,7 @@
<div class="common-statistics">
<stats-card
title="当前支出" color="#CA6C65" unit="元" icon="fa-solid fa-sack-xmark"
:value="formatAmount(billStats.expensive)" />
:value="formatAmount(billStats.expense)" />
<stats-card
title="当前收入" color="#6FBB69" unit="元" icon="fa-solid fa-sack-dollar"
@@ -19,11 +19,11 @@
</template>
<script setup lang="ts">
import StatsCard from '@/components/Home/StatsCard.vue'
import { useBillStore } from '@/store/bill'
import StatsCard from '@/components/StatsCard.vue'
import { useBillStore } from '@/store/bill.ts'
import { ref, watch } from 'vue'
import { getBillSummaryStats } from '@/utils/home/billUtil'
import { IBillSummaryStats } from '@/types/bill'
import { getBillSummaryStats } from '@/utils/home/billUtil.ts'
import { IBillSummaryStats } from '@/types/bill.ts'
import { formatAmount } from 'vue3-common/utils/numberUtil'
const billStore = useBillStore()
@@ -34,7 +34,7 @@ watch(() => billStore.isRefresh, () => {
const billStats = ref<IBillSummaryStats>({
balance: 0,
expensive: 0,
expense: 0,
income: 0,
rate: 0
})

View File

@@ -1,5 +1,5 @@
<template>
<div class="blog-archive blog-section">
<div class="blog-archive blog-section card-item">
<h4 class="module-title">文章归档</h4>
<el-date-picker
@@ -53,10 +53,10 @@ const changeDateEvent = async (value: Date) => {
*/
const viewBlogClick = async (index: number) => {
const blogId = blogStore.blogList[index].id
await router.push({ name: 'BlogDetail', params: { id: blogId } })
await router.push({ name: 'DetailId', params: { id: blogId } })
}
</script>
<style scoped lang="scss">
@use "@/styles/blog/blog.archive.module.scss";
@use "@/styles/blog/blog.archive.module";
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="blog-category blog-section">
<div class="blog-category blog-section card-item">
<h4 class="module-title">文章分类</h4>
<span class="total">目前共计{{ blogStore.blogCategoryList.length }}个分类</span>
@@ -31,10 +31,10 @@ onMounted(() => {
* @param name 类别名称
*/
const viewBlogCategoryClick = async (name: string) => {
await router.push({ name: 'BlogCategoryName', params: { name } })
await router.push({ name: 'CategoryName', params: { name } })
}
</script>
<style scoped lang="scss">
@use "@/styles/blog/blog.category.module.scss";
@use "@/styles/blog/blog.category.module";
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="blog-category-detail blog-section">
<div class="blog-category-detail blog-section card-item">
<span class="name">{{ route.params?.name }}</span>
<div class="category-detail-list">
@@ -32,10 +32,10 @@ onMounted(async () => {
const viewBlogClick = async (index: number) => {
const blogId = blogStore.blogList[index].id
await router.push({ name: 'BlogDetailId', params: { id: blogId } })
await router.push({ name: 'DetailId', params: { id: blogId } })
}
</script>
<style scoped lang="scss">
@use "@/styles/blog/blog.category.detail.module.scss";
@use "@/styles/blog/blog.category.detail.module";
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div class="blog-comment card-item">
<div class="comment-total">
<span> {{ blogStore.blogNestedCommentList.length }} 条评论</span>
</div>
<div class="comment-list">
<blog-comment-item v-for="(item, index) in blogStore.blogNestedCommentList" :key="index"
:comment="item.comment" :replyList="item.replies"/>
</div>
</div>
</template>
<script setup lang="ts">
import BlogCommentItem from '@/components/Comment/BlogCommentItem.vue'
import { useBlogStore } from '@/store/blog.ts'
const blogStore = useBlogStore()
</script>
<style lang="scss">
.blog-comment {
width: 100%;
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
</style>

View File

@@ -1,12 +1,14 @@
<template>
<div class="blog-detail blog-section" id="blog-section">
<div class="blog-detail blog-section card-item" id="blog-section">
<div class="blog-title">
<span class="title">{{ blogStore.currentBlog.title }}</span>
<blog-label :basic-blog="getBasicBlog(blogStore.currentBlog)"/>
</div>
<div class="blog-md">
<MdPreview editorId="blog-id" v-model="blogStore.currentBlog.content" />
<div class="blog-md card-item">
<MdPreview editorId="blog-id"
v-model="blogStore.currentBlog.content"
:theme="appStore.isDark ? 'dark' : 'light'"/>
</div>
<div class="blog-copyright">
@@ -28,28 +30,38 @@
<div class="blog-ending">
<span>-------------本文结束感谢您的阅读给个五星好评吧~~-------------</span>
</div>
<blog-comment-edit :parent-id="0"/>
<blog-comment />
</div>
</template>
<script setup lang="ts">
import BlogLabel from '@/components/Blog/Content/BlogLabel.vue'
import BlogLabel from '@/components/Content/BlogLabel.vue'
import BlogCommentEdit from '@/components/Comment/BlogCommentEdit.vue'
import BlogComment from '@/components/Comment/BlogComment.vue'
import { MdPreview } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import { useBlogStore } from '@/store/blog.ts'
import { useAppStore } from '@/store/app.ts'
import { getBasicBlog } from '@/utils/blog/blogUtil.ts'
import { useRoute } from 'vue-router'
import { onMounted } from 'vue'
const blogStore = useBlogStore()
const appStore = useAppStore()
const route = useRoute()
const currentUrl = new URL(window.location.href)
onMounted(async () => {
await blogStore.queryBlogById(route.params.id as number)
const blogId = route.params.id as number
blogStore.currentBlogId = blogId
await blogStore.queryBlogById(blogId)
})
</script>
<style scoped lang="scss">
@use "@/styles/blog/blog.detail.module.scss";
@use "@/styles/blog/blog.detail.module";
</style>

View File

@@ -88,5 +88,5 @@ const viewBlogCategoryClick = async () => {
</script>
<style scoped lang="scss">
@use "@/styles/blog/blog.label.module.scss";
@use "@/styles/blog/blog.label.module";
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="blog-content-list blog-section">
<div class="blog-content-list blog-section card-item">
<div v-if="blogStore.blogList.length === 0"
class="blog-content blog-content-empty" >
</div>
@@ -29,7 +29,7 @@
</template>
<script setup lang="ts">
import BlogLabel from '@/components/Blog/Content/BlogLabel.vue'
import BlogLabel from '@/components/Content/BlogLabel.vue'
import { useRouter } from 'vue-router'
import { onMounted } from 'vue'
import { useBlogStore } from '@/store/blog.ts'
@@ -48,21 +48,19 @@ onMounted(async () => {
*/
const readBlogClick = async (index: number) => {
const blogId = blogStore.blogList[index].id
await router.push({ name: 'BlogDetailId', params: { id: blogId } })
await router.push({ name: 'DetailId', params: { id: blogId } })
}
/**
* 点击切换博客页数按钮
* @param page 页码
* @param pageSize 每页大小
*/
const changePageClick = async (page: number, pageSize: number) => {
const changePageClick = async (page: number) => {
blogStore.pageInfo.currentPage = page
blogStore.pageInfo.pageSize = pageSize
await blogStore.queryBlog()
}
</script>
<style lang="scss">
@use "@/styles/blog/blog.overview.module.scss";
@use "@/styles/blog/blog.overview.module";
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div class="life-record-card card-item">
<el-image :src="`${props.carItem?.imageUrl}${ThumbImageQuery}`"
fit="contain" alt="暂无图片" @click="viewImageClick">
<template #error>
<div class="image-slot">
<span>暂无图片</span>
</div>
</template>
</el-image>
<van-swipe-cell>
<div class="record-info" @click="editClick()">
<div class="top-container">
<div class="info-list">
<div class="info-item">
<span class="title">{{ props.carItem.name }}</span>
</div>
<div class="info-item">
<i class="fa-solid fa-file-lines" style="color: #8e44ad"></i>
{{ props.carItem.description }}
</div>
</div>
<el-tag type="primary" effect="dark">
{{ props.carItem.type }}
</el-tag>
</div>
<div class="bottom-container">
<div class="info-list">
<div class="info-item">
<i class="fa-solid fa-gauge" style="color: #3498db"></i>
{{ props.carItem.mileage }} M
</div>
<div class="info-item">
<i class="fa-solid fa-coins" style="color: #27ae60"></i>
{{ props.carItem.cost }}
</div>
<div class="info-item">
<i class="fa-solid fa-location-dot" style="color: #e67e22"></i>
{{ props.carItem.location }}
</div>
</div>
<div class="info-list">
<div class="info-item">
<i class="fa-solid fa-calendar" style="color: #26a69a"></i>
{{ props.carItem.date }}
</div>
</div>
</div>
</div>
<template #right>
<van-button square type="primary" text="编辑" style="height: 100%;"
@click="editClick"/>
<van-button square type="danger" text="删除" style="height: 100%;"
@click="deleteClick()"/>
</template>
</van-swipe-cell>
</div>
</template>
<script setup lang="ts">
import { useCarStore } from '@/store/car.ts'
import { defineProps, PropType } from 'vue'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { useRouter } from 'vue-router'
import { ThumbImageQuery } from '@/utils'
import { showImagePreview } from 'vant'
import { ICarEvent } from '@/types/car.ts'
const carStore = useCarStore()
const router = useRouter()
const props = defineProps({
carItem: {
required: true,
type: Object as PropType<ICarEvent>
}
})
const viewImageClick = () => {
showImagePreview({ images: [props.carItem?.imageUrl], closeable: true })
}
const editClick = async () => {
const id = props.carItem?.id
carStore.carEventApiType = 'UPDATE'
carStore.currentCarEvent = props.carItem as ICarEvent
await router.push({ name: 'CarDetailId', params: { id } })
}
const deleteClick = () => {
carStore.carEventApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该事件: ${props.carItem?.description}?`).then(() => {
carStore.handleCarEventApi(props.carItem as ICarEvent)
})
}
</script>
<style lang="scss">
@use "@/styles/home/life.record.module";
</style>

View File

@@ -0,0 +1,33 @@
<template>
<div class="mobile-common-query card-item">
<div class="query-item">
<span>类别</span>
<el-segmented v-model="carStore.queryInfo.type"
:options="queryTypeList"
@change="carStore.queryCarEventList()"/>
</div>
</div>
</template>
<script setup lang="ts">
import { useCarStore } from '@/store/car.ts'
const carStore = useCarStore()
const queryTypeList = [
{
label: '全部',
value: ''
},
{
label: '保养',
value: '保养'
}, {
label: '事故',
value: '事故'
}, {
label: '违章',
value: '违章'
}
]
</script>

View File

@@ -16,5 +16,5 @@ const appStore = useAppStore()
</script>
<style lang="scss">
@use "@/styles/app.loading.module.scss";
@use "@/styles/app.loading.module";
</style>

View File

@@ -12,13 +12,22 @@
{{ appStore.isDark ? '🌙' : '☀️' }}
</el-button>
</div>
<div class="setting-item">
<span>我的</span>
<el-button circle :icon="UserFilled" type="primary" @click="viewProfileClick"/>
</div>
</div>
</template>
<script setup lang="ts">
import { UserFilled } from '@element-plus/icons-vue'
import { useAppStore } from '@/store/app.ts'
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
const appStore = useAppStore()
const router = useRouter()
onMounted(() => {
appStore.themeColor = appStore.getThemeColor()
@@ -27,6 +36,11 @@ onMounted(() => {
const changeThemeColorEvent = (value: string) => {
appStore.setThemeColor(value)
}
const viewProfileClick = () => {
router.push('/profile/index')
appStore.isShowSetting = false
}
</script>
<style scoped lang="scss">

View File

@@ -50,7 +50,7 @@
</template>
<script setup lang="ts">
import WeatherIcon from '@/components/Mobile/WeatherIcon.vue'
import WeatherIcon from '@/components/Common/WeatherIcon.vue'
import { onMounted, onUnmounted, reactive } from 'vue'
import axios from 'axios'
import { Solar } from 'lunar-javascript'

View File

@@ -12,9 +12,8 @@
<el-card class="record-card">
<span class="record-content">完成人{{ item.person }}</span>
<el-image v-if="item.imageUrl"
:src="serviceFileRootPath + item.imageUrl"
:preview-src-list="[serviceFileRootPath + item.imageUrl]"
fit="contain" alt="暂无图片" class="record-image"/>
:src="`${item.imageUrl}${ThumbImageQuery}`" fit="cover" alt="暂无图片"
@click="viewImageClick(item)" class="record-image"/>
</el-card>
</el-timeline-item>
</el-timeline>
@@ -25,8 +24,14 @@
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { serviceFileRootPath } from '@/utils'
import { ThumbImageQuery } from '@/utils'
import { SvgIcon } from 'vue3-common'
import { IFoodRecord } from '@/types/food.ts'
import { showImagePreview } from 'vant'
const foodStore = useFoodStore()
const viewImageClick = (item: IFoodRecord) => {
showImagePreview({ images: [item.imageUrl], closeable: true })
}
</script>

View File

@@ -12,7 +12,7 @@
<div class="step-body">
<span class="step-content">{{ item.content }}</span>
<img v-if="item.imageUrl"
:src="serviceFileRootPath + item.imageUrl" alt="暂无图片" class="step-image"/>
:src="item.imageUrl" alt="暂无图片" class="step-image"/>
</div>
</div>
</div>
@@ -21,7 +21,6 @@
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { serviceFileRootPath } from '@/utils'
import { SvgIcon } from 'vue3-common'
const foodStore = useFoodStore()

View File

@@ -10,12 +10,12 @@
</template>
<script setup lang="ts">
import FoodDailyItem from '@/components/Home/Food/FoodDailyItem.vue'
import FoodDailyItem from '@/components/Food/FoodDailyItem.vue'
import { reactive, watch, ref, onMounted } from 'vue'
import { useFoodStore } from '@/store/food.ts'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
import { useAppStore } from '@/store/app'
import { useAppStore } from '@/store/app.ts'
const calendarRef = ref()
const foodStore = useFoodStore()

View File

@@ -0,0 +1,115 @@
<template>
<div class="life-record-card card-item">
<el-image v-if="props.foodItem?.recordList.length > 0"
:src="`${props.foodItem?.recordList[0].imageUrl}${ThumbImageQuery}`"
fit="contain" alt="暂无成果" @click="viewImageClick">
<template #error>
<div class="image-slot">
<span>暂无图片</span>
</div>
</template>
</el-image>
<el-empty v-else description="暂无成果" />
<van-swipe-cell>
<div class="record-info" @click="viewClick()">
<div class="top-container">
<div class="info-list">
<div class="info-item">
<span class="title">{{ props.foodItem.name }}</span>
</div>
</div>
</div>
<div class="bottom-container">
<div class="info-list">
<div class="info-item category-item">
<i class="fa-solid fa-utensils"></i>
{{ props.foodItem.category }}
</div>
</div>
<div v-if="props.foodItem?.recordList.length > 0" class="info-list">
<div class="info-item">
<i class="fa-solid fa-flag"></i>
{{ props.foodItem.recordList[foodCardInfo.currentFoodIndex].person}}
</div>
<div class="info-item">
<i class="fa-regular fa-calendar-check"></i>
{{ formatDate(props.foodItem.recordList[foodCardInfo.currentFoodIndex].date) }}
</div>
</div>
<div v-else>
<span>暂无成果</span>
</div>
</div>
</div>
<template #right>
<van-button square type="primary" text="编辑" style="height: 100%;"
@click="editClick"/>
<van-button square type="danger" text="删除" style="height: 100%;"
@click="deleteClick()"/>
</template>
</van-swipe-cell>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { defineProps, onMounted, PropType, reactive } from 'vue'
import { IFoodRecipe } from '@/types/food.ts'
import { useRouter } from 'vue-router'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { showImagePreview } from 'vant'
import { RawImageQuery, ThumbImageQuery } from '@/utils'
const router = useRouter()
const foodStore = useFoodStore()
const foodCardInfo = reactive({
recommendRate: 0,
currentFoodIndex: 0
})
const props = defineProps({
foodItem: {
required: true,
type: Object as PropType<IFoodRecipe>
}
})
onMounted(() => {
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
})
const viewImageClick = () => {
const imageList = props.foodItem?.recordList.map((item) => `${item.imageUrl}${RawImageQuery}`)
showImagePreview({ images: imageList, closeable: true })
}
const editClick = async () => {
const id = props.foodItem?.id
foodStore.foodRecipeApiType = 'UPDATE'
await foodStore.queryFoodRecipe(id as number)
await router.push({ name: 'FoodRecipeForm' })
}
const viewClick = async () => {
await router.push({ name: 'FoodDetailId', params: { id: props.foodItem?.id } })
}
const deleteClick = () => {
foodStore.foodRecipeApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
foodStore.handleFoodRecipeApi(props.foodItem)
})
}
</script>
<style lang="scss">
@use "@/styles/home/life.record.module";
</style>

View File

@@ -22,16 +22,14 @@
</template>
<script setup lang="ts">
import DailySummary from '@/components/Home/DailySummary.vue'
import DailyCard from '@/components/Home/DailyCard.vue'
import DailySummary from '@/components/DailySummary.vue'
import DailyCard from '@/components/DailyCard.vue'
import { defineProps, onMounted, reactive, watch } from 'vue'
import { useFoodStore } from '@/store/food.ts'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/app.ts'
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
import { IFoodRecord } from '@/types/food.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
const foodStore = useFoodStore()
const appStore = useAppStore()
@@ -85,18 +83,10 @@ const getFoodDailyByDate = (date: string) => {
*/
const selectFoodRecordClick = async (foodRecord: IFoodRecord) => {
foodStore.foodRecordApiType = 'UPDATE'
if (isMobile()) {
appStore.isShowMobileBack = true
foodStore.currentFoodRecord = foodRecord
await router.push({ name: 'MobileHomeFoodRecordForm' })
} else {
foodStore.foodRecordDialog = {
title: '编辑记录',
visible: true,
data: deepCopyObject(foodRecord)
}
}
appStore.isShowMobileBack = true
await router.push({ name: 'FoodRecordForm' })
}
</script>

View File

@@ -0,0 +1,39 @@
<template>
<div class="common-query">
<el-select
v-model="foodStore.queryRecipe.name"
filterable clearable placeholder="请选择菜谱名称"
style="width: 150px;"
@change="changeFoodNameEvent">
<el-option
v-for="(item, index) in foodStore.foodNameList"
:key="index" :label="item" :value="item"
/>
</el-select>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { onMounted } from 'vue'
import { searchFoodRecipeApi } from '@/apis/food.ts'
const foodStore = useFoodStore()
onMounted(async () => {
await foodStore.queryFoodNameList()
foodStore.queryRecipe.name = ''
})
const changeFoodNameEvent = async (value: string) => {
if (value) {
const result = await searchFoodRecipeApi(value)
foodStore.foodRecipeList = [result]
} else {
foodStore.pageInfo.currentPage = 1
foodStore.pageInfo.pageSize = 3
foodStore.foodRecipeList = []
await foodStore.queryMobileFoodRecipeList()
}
}
</script>

View File

@@ -19,9 +19,9 @@
</template>
<script setup lang="ts">
import StatsCard from '@/components/Home/StatsCard.vue'
import StatsCard from '@/components/StatsCard.vue'
import { onMounted } from 'vue'
import { useFoodStore } from '@/store/food'
import { useFoodStore } from '@/store/food.ts'
const foodStore = useFoodStore()
@@ -47,7 +47,7 @@ const getAverageCount = () => {
}
})
return foodStore.foodRecordList.length / Object.keys(result).length
return (foodStore.foodRecordList.length / Object.keys(result).length).toFixed(2)
}
}
</script>

View File

@@ -1,13 +1,13 @@
<template>
<el-dialog v-model="foodStore.foodMaterialDialog.visible"
:title="foodStore.foodMaterialDialog.title"
<el-dialog v-model="foodStore.foodMaterialDialogVisible"
:title="foodStore.foodMaterialApiType === 'ADD' ? '新增食材' : '编辑食材'"
center @open="openDialogEvent"
class="food-material-dialog">
<el-form ref="formRef" :model="foodStore.foodMaterialDialog.data"
:rules="rules">
<el-form ref="formRef" :model="foodStore.currentFoodMaterial"
:rules="rules" label-width="auto">
<el-form-item label="类型" prop="type">
<el-select
v-model="foodStore.foodMaterialDialog.data.type"
v-model="foodStore.currentFoodMaterial.type"
placeholder="请选择类型">
<el-option
v-for="(item, index) in foodMaterialTypeList"
@@ -17,14 +17,14 @@
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="foodStore.foodMaterialDialog.data.name" autocomplete="off"
<el-input v-model="foodStore.currentFoodMaterial.name" autocomplete="off"
placeholder="请输入名称" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="分量" prop="amount">
<el-input v-model="foodStore.foodMaterialDialog.data.amount" autocomplete="off"
<el-input v-model="foodStore.currentFoodMaterial.amount" autocomplete="off"
placeholder="请输入分量1斤/2勺/适量)" clearable
show-word-limit maxlength="20">
</el-input>
@@ -41,10 +41,10 @@
</template>
<script lang="ts" setup>
import { useFoodStore } from '@/store/food'
import { useFoodStore } from '@/store/food.ts'
import { nextTick, reactive, ref } from 'vue'
import { FormRules } from 'element-plus'
import { foodMaterialRules } from '@/utils/element/elRules'
import { foodMaterialRules } from '@/utils/element/elRules.ts'
const rules = reactive<FormRules>(foodMaterialRules)
const formRef = ref()
@@ -68,22 +68,22 @@ const confirmClick = () => {
formRef.value.validate().then(async () => {
if (foodStore.foodMaterialApiType === 'UPDATE') {
const { editFoodMaterialIndex: index } = foodStore
foodStore.foodRecipeDialog.data.materialList[index] = foodStore.foodMaterialDialog.data
foodStore.currentFoodRecipe.materialList[index] = foodStore.currentFoodMaterial
} else {
foodStore.foodRecipeDialog.data.materialList.push(foodStore.foodMaterialDialog.data)
foodStore.currentFoodRecipe.materialList.push(foodStore.currentFoodMaterial)
}
foodStore.foodMaterialDialog.visible = false
foodStore.foodMaterialDialogVisible = false
})
}
const cancelClick = () => {
foodStore.foodMaterialDialog.visible = false
foodStore.foodMaterialDialogVisible = false
}
</script>
<style lang="scss">
.food-material-dialog {
width: 30%;
width: 80%;
.el-dialog__body {
// height: 50vh;

View File

@@ -0,0 +1,51 @@
<template>
<div class="material-form">
<div style="align-self: center;">
<el-button type="primary" @click="handleAddClick">新增</el-button>
</div>
<el-table :data="foodStore.currentFoodRecipe.materialList"
border stripe class="material-table">
<el-table-column prop="type" label="类型" align="center" min-width="15%"/>
<el-table-column prop="name" label="名称" align="center" min-width="20%"/>
<el-table-column prop="amount" label="分量" align="center" min-width="20%"/>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button type="primary" text style="margin: 0 10px 0 0;padding: 0;"
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
<el-button type="danger" text style="margin: 0;padding: 0;"
@click="handleDeleteClick(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { IFoodMaterial } from '@/types/food.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const foodStore = useFoodStore()
const handleAddClick = () => {
foodStore.foodMaterialApiType = 'ADD'
foodStore.currentFoodMaterial = foodStore.getEmptyFoodMaterial()
foodStore.foodMaterialDialogVisible = true
}
const handleEditClick = (index: number, material: IFoodMaterial) => {
foodStore.editFoodMaterialIndex = index
foodStore.foodMaterialApiType = 'UPDATE'
foodStore.currentFoodMaterial = deepCopyObject(material)
foodStore.foodMaterialDialogVisible = true
}
const handleDeleteClick = (index: number, material: IFoodMaterial) => {
foodStore.foodMaterialApiType = 'DELETE'
commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => {
foodStore.currentFoodRecipe.materialList.splice(index, 1)
})
}
</script>

View File

@@ -0,0 +1,139 @@
<template>
<el-dialog v-model="foodStore.foodStepDialogVisible"
:title="foodStore.foodStepApiType === 'ADD' ? '新增步骤' : '编辑步骤'"
center @open="openDialogEvent"
class="food-step-dialog">
<el-form ref="formRef" :model="foodStore.currentFoodStep"
:rules="rules" label-width="auto">
<el-form-item label="内容" prop="content">
<el-input v-model="foodStore.currentFoodStep.content" autocomplete="off"
placeholder="请输入内容" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="图片">
<el-upload
v-model:file-list="imageFileList"
list-type="picture-card"
:accept="acceptImageTypes"
:limit="1"
:before-upload="beforeUpload"
:http-request="httpRequest"
:on-success="handleSuccess"
:on-exceed="handleExceed"
:before-remove="beforeRemove"
:on-remove="handleRemove"
>
<el-icon><Plus /></el-icon>
<template #tip>
<div class="el-upload__tip">
请上传小于10M的图片
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { useFoodStore } from '@/store/food.ts'
import { ref, reactive, nextTick, onMounted } from 'vue'
import { ElMessage, ElMessageBox, FormRules, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus'
import { foodStepRules } from '@/utils/element/elRules.ts'
import { acceptImageTypes } from '@/utils/file.ts'
import { Plus } from '@element-plus/icons-vue'
import { beforeUploadImage, httpRequestFile } from '@/utils/element/elUpload.ts'
const rules = reactive<FormRules>(foodStepRules)
const formRef = ref()
const foodStore = useFoodStore()
const imageFileList = ref<UploadUserFile[]>([])
onMounted(async () => {
initImageFileList()
})
const initImageFileList = () => {
imageFileList.value = []
const { imageUrl } = foodStore.currentFoodStep
if (imageUrl) {
imageFileList.value.push({
name: 'image',
url: imageUrl
})
}
}
const beforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
return beforeUploadImage(rawFile)
}
const httpRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
return await httpRequestFile(foodStore.foodImagePath, options)
}
const handleSuccess = (response: string) => {
foodStore.currentFoodStep.imageUrl = response
}
const handleExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('最多上传1张图片')
}
const handleRemove: UploadProps['onRemove'] = () => {
foodStore.currentFoodStep.imageUrl = ''
}
const beforeRemove: UploadProps['beforeRemove'] = () => {
return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
}
/**
* 打开对话框事件
*/
const openDialogEvent = () => {
nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate()
.then(async () => {
if (foodStore.foodStepApiType === 'UPDATE') {
const { editFoodStepIndex: index } = foodStore
foodStore.currentFoodRecipe.stepList[index] = foodStore.currentFoodStep
} else {
foodStore.currentFoodRecipe.stepList.push(foodStore.currentFoodStep)
}
foodStore.foodStepDialogVisible = false
})
}
const cancelClick = () => {
foodStore.foodStepDialogVisible = false
}
</script>
<style lang="scss">
.food-step-dialog {
width: 80%;
.el-dialog__body {
// height: 50vh;
}
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="step-form">
<div style="align-self: center;">
<el-button type="primary" @click="handleAddClick">新增</el-button>
</div>
<el-table :data="foodStore.currentFoodRecipe.stepList"
border class="food-step-table">
<el-table-column prop="content" label="内容" align="center" min-width="20%" />
<el-table-column prop="imageUrl" label="图片" align="center" min-width="20%">
<template #default="scope">
<el-image v-if="scope.row.imageUrl" :src="scope.row.imageUrl" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button type="primary" text style="margin: 0 10px 0 0;padding: 0;"
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
<el-button type="danger" text style="margin: 0;padding: 0;"
@click="handleDeleteClick(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { IFoodStep } from '@/types/food.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const foodStore = useFoodStore()
const handleAddClick = () => {
foodStore.foodStepApiType = 'ADD'
foodStore.currentFoodStep = foodStore.getEmptyFoodStep()
foodStore.foodStepDialogVisible = true
}
const handleEditClick = (index: number, step: IFoodStep) => {
foodStore.editFoodStepIndex = index
foodStore.foodStepApiType = 'UPDATE'
foodStore.currentFoodStep = deepCopyObject(step)
foodStore.foodStepDialogVisible = true
}
const handleDeleteClick = (index: number) => {
commonElMessageBox('确认删除该步骤?').then(() => {
foodStore.foodStepApiType = 'DELETE'
foodStore.currentFoodRecipe.stepList.splice(index, 1)
foodStore.currentFoodRecipe.stepList.forEach((item) => {
if (item.sort > index) {
item.sort -= 1
}
})
})
}
</script>
<style lang="scss">
.food-step-table {
.el-image {
width: 100%;
height: 100%;
max-height: 200px;
max-width: 200px;
}
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<div class="bill-chart common-chart"
v-loading="healthStore.isLoading"
element-loading-text="正在加载中...">
<div class="chart-container card-item">
<div class="content">
<span class="title">📊体重趋势</span>
</div>
<div class="chart" ref="weightDailyChartRef"/>
</div>
<div class="chart-container card-item">
<div class="content">
<span class="title">📊睡眠时长统计图</span>
</div>
<div class="chart" ref="sleepDailyChartRef"/>
</div>
<div class="chart-container card-item">
<div class="content">
<span class="title">📊运动时长统计</span>
</div>
<div class="chart" ref="healthDurationChartRef"/>
</div>
<div class="chart-container card-item">
<div class="content">
<span class="title">📊运动类别统计</span>
</div>
<div class="chart" ref="healthCategoryChartRef"/>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import * as echarts from 'echarts'
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
import { useHealthStore } from '@/store/health'
import { useAppStore } from '@/store/app.ts'
import { formatDate } from 'vue3-common/utils/dateUtil'
const healthStore = useHealthStore()
const appStore = useAppStore()
const weightDailyChartRef = ref<HTMLElement>()
const sleepDailyChartRef = ref<HTMLElement>()
const healthDurationChartRef = ref<HTMLElement>()
const healthCategoryChartRef = ref<HTMLElement>()
let weightDailyChart: echarts.ECharts
let sleepDailyChart: echarts.ECharts
let healthDurationChart: echarts.ECharts
let healthCategoryChart: echarts.ECharts
watch(() => healthStore.isRefresh, () => {
updateChart()
})
onMounted(() => {
weightDailyChart = echarts.init(weightDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
sleepDailyChart = echarts.init(sleepDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
healthDurationChart = echarts.init(healthDurationChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
healthCategoryChart = echarts.init(healthCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
})
const updateChart = () => {
const MAX_CATEGORY_COUNT = 5
// 体重趋势统计图
const weightRecordList = healthStore.healthRecordList
.filter((item) => item.weight !== 0)
const weightXAxis = weightRecordList.map((item) => {
return formatDate(item.date, 'MM-DD')
}).reverse()
const weightYAxis = weightRecordList.map((item) => item.weight).reverse()
const sortedRecordList = weightRecordList.sort((a, b) => a.weight - b.weight)
weightDailyChart.setOption(lineChartOptions('日期', weightXAxis, '体重(千克)', weightYAxis))
if (sortedRecordList.length > 0) {
weightDailyChart.setOption({
yAxis: {
min: Math.floor(sortedRecordList[0].weight),
max: Math.ceil(sortedRecordList[sortedRecordList.length - 1].weight)
}
})
}
// 睡眠时长统计图
const sleepRecordList = healthStore.healthRecordList
.filter((item) => item.sleepDuration !== null)
.sort((a, b) => (a.date > b.date ? -1 : 1))
const sleepXAxis = sleepRecordList.map((item) => {
return formatDate(item.date, 'MM-DD')
}).reverse()
const sleepYAxis = sleepRecordList.map((item) => item.sleepDuration).reverse() as number[]
sleepDailyChart.setOption(barChartOptions('日期', sleepXAxis, '时间(小时)', sleepYAxis))
// 运动量统计图
const sportRecordList = healthStore.healthRecordList
.filter((item) => item.sportProject !== '')
.sort((a, b) => (a.date > b.date ? -1 : 1))
const durationXAxis = sportRecordList.map((item) => {
return formatDate(item.date, 'MM-DD')
}).reverse()
const durationYAxis = sportRecordList.map((item) => item.sportDuration).reverse()
healthDurationChart.setOption(barChartOptions('日期', durationXAxis, '时间(分钟)', durationYAxis))
// 类别统计图
const healthCategoryList = healthStore.healthRecordList.reduce((acc, item) => {
if (item.sportProject && item.sportDuration > 0) {
const existing = acc.find((project) => project.name === item.sportProject)
if (existing) {
existing.value += item.sportDuration
} else {
acc.push({ name: item.sportProject, value: item.sportDuration })
}
}
return acc
}, [])
healthCategoryChart.setOption(pieChartOptions('名称', '分钟', healthCategoryList.slice(0, MAX_CATEGORY_COUNT)))
}
</script>

View File

@@ -0,0 +1,179 @@
<template>
<el-form :model="healthStore.currentHealth" class="health-form card-item">
<el-form-item>
<template #label>
<i class="fa-regular fa-calendar-check"></i>
<span>日期</span>
</template>
<el-date-picker v-model="healthStore.currentHealth.date"
type="date" :disabled="true"
style="width: 150px;"/>
</el-form-item>
<el-form-item>
<template #label>
<i class="fa-solid fa-person-running"></i>
<span>运动项目</span>
</template>
<el-select v-model="healthStore.currentHealth.sportProject"
placeholder="请选择运行项目" clearable style="width: 150px;">
<el-option v-for="(item, index) in projectList" :key="index"
:label="item" :value="item"/>
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<i class="fa-solid fa-clock"></i>
<span>运动时长</span>
</template>
<el-input-number v-model="healthStore.currentHealth.sportDuration"
:min="0" :max="300" :precision="0"
style="width: 120px;"></el-input-number>
<span style="margin-left: 5px;">分钟</span>
</el-form-item>
<el-form-item>
<template #label>
<i class="fa-solid fa-weight-scale"></i>
<span>体重</span>
</template>
<el-input-number v-model="healthStore.currentHealth.weight"
:min="0" :max="200"
style="width: 120px;"></el-input-number>
<span style="margin-left: 5px;">千克</span>
</el-form-item>
<el-form-item>
<template #label>
<i class="fa-solid fa-moon"></i>
<span>睡眠开始时间</span>
</template>
<el-time-picker
v-model="healthStore.currentHealth.sleepStartTime"
clearable :editable="false"
placeholder="请选择睡眠开始时间"
style="width: 180px"
format="HH:mm"
value-format="HH:mm"
@clear="healthStore.currentHealth.sleepStartTime = ''"
/>
</el-form-item>
<el-form-item>
<template #label>
<i class="fa-solid fa-sun"></i>
<span>睡眠结束时间</span>
</template>
<el-time-picker
v-model="healthStore.currentHealth.sleepEndTime"
clearable :editable="false"
placeholder="请选择睡眠结束时间"
style="width: 180px"
format="HH:mm"
value-format="HH:mm"
@clear="healthStore.currentHealth.sleepEndTime = ''"
/>
</el-form-item>
<el-form-item>
<template #label>
<i class="fa-solid fa-hourglass-start"></i>
<span>睡眠时长</span>
</template>
<span>{{ healthStore.currentHealth.sleepDuration }}</span>
<span style="margin-left: 5px;">小时</span>
</el-form-item>
<el-form-item style="align-self: center;">
<el-button type="primary" @click="onSubmit"
:disabled="isBeforeDate(formatDate(new Date()), healthStore.selectDate)">
确认
</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { useHealthStore } from '@/store/health'
import { formatDate, isBeforeDate } from 'vue3-common/utils/dateUtil'
import { ElMessage } from 'element-plus'
import dayjs from 'dayjs'
const healthStore = useHealthStore()
const projectList = ['羽毛球', '乒乓球', '跑步', '其他']
const onSubmit = async () => {
const { sportProject, sportDuration, weight,
sleepStartTime, sleepEndTime } = healthStore.currentHealth
if (sportProject === '' && sportDuration === 0 && weight === 0
&& sleepStartTime === '' && sleepEndTime === '') {
return
}
if (sportDuration) {
if (!sportProject) {
ElMessage.error('请选择运动项目')
return
}
}
if (sleepStartTime && sleepEndTime) {
healthStore.currentHealth.sleepDuration = calculateSleepDuration(sleepStartTime, sleepEndTime)
}
await healthStore.updateHealthDay()
healthStore.currentHealth = healthStore.getHealthDay(healthStore.selectDate)
}
const calculateSleepDuration = (startTime: string, endTime: string) => {
const start = dayjs(startTime, 'HH:mm')
let end = dayjs(endTime, 'HH:mm')
// 处理跨天
if (end.isSameOrBefore(start)) {
end = end.add(1, 'day')
}
// 计算时间差(小时)
return end.diff(start, 'hour', true)
}
</script>
<style lang="scss">
.health-form {
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
.el-form-item {
justify-content: space-between;
.el-form-item__label {
display: grid;
grid-template-columns: 20px 1fr;
align-items: center;
gap: 5px;
i {
font-size: 1.2rem;
color: #DE3163;
}
}
.el-form-item__content {
flex: none;
}
}
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="common-statistics">
<stats-card
title="平均体重" color="#CA6C65" unit="千克" icon="fa-solid fa-weight-scale"
:value="formatAmount(healthStats.averageWeight)" />
<stats-card
title="运动时长" color="#6FBB69" unit="分钟" icon="fa-solid fa-person-walking"
:value="healthStats.totalDuration" />
</div>
</template>
<script setup lang="ts">
import StatsCard from '@/components/StatsCard.vue'
import { useHealthStore } from '@/store/health'
import { ref, watch } from 'vue'
import { formatAmount } from 'vue3-common/utils/numberUtil'
import record from '@/views/travel/record.vue'
import { max } from '@popperjs/core/lib/utils/math'
const healthStore = useHealthStore()
watch(() => healthStore.isRefresh, () => {
updateStats()
})
const healthStats = ref({
averageWeight: 0,
totalDuration: 0
})
const updateStats = () => {
const recordList = healthStore.healthRecordList
const totalWeight = recordList.reduce((sum, item) => sum + item.weight, 0)
if (recordList.length > 0) {
healthStats.value.averageWeight = totalWeight / recordList.length
healthStats.value.totalDuration = recordList.reduce((sum, item) => sum + item.sportDuration, 0)
}
}
</script>

View File

@@ -1,32 +0,0 @@
<template>
<div class="date-list">
<!-- <el-empty v-if="planStore.planList.length === 0" />-->
<el-scrollbar>
<date-card v-for="(item, index) in 4"
:key="index" :plan="item">
</date-card>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import DateCard from '@/components/Home/Anniversary/AnniversaryCard.vue'
</script>
<style lang="scss">
@use "@/styles/home.variable.module.scss";
.date-list {
height: calc(100vh - 120px);
.el-scrollbar {
.el-scrollbar__view {
display: flex;
flex-direction: column;
gap: 2vh;
}
}
}
</style>

View File

@@ -1,166 +0,0 @@
<template>
<div class="bill-setting">
<div class="button-container">
<el-button type="primary" @click="addNewBookClick">新增账本</el-button>
</div>
<el-table
:data="billStore.billBookList"
border stripe
:header-cell-style="tableHeaderStyle()"
:cell-style="tableCellStyle()"
:row-style="tableRowStyle()"
style="width: 100%"
class="card-item">
<el-table-column type="index" align="center" width="50"/>
<el-table-column prop="name" label="名称" align="center" width="100"/>
<el-table-column label="图标" align="center" width="80">
<template #default="scope">
<i :class="scope.row.icon" :style="{color: scope.row.color, fontSize: '24px'}"></i>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" @click="editBookClick(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="deleteBookClick(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="billInfo.billBookDialog.visible"
:title="billInfo.billBookDialog.title"
center width="80%" @open="openDialogEvent">
<el-form ref="formRef" :model="billInfo.billBookDialog.data"
:rules="rules" label-position="top" label-width="100px">
<el-form-item label="账本名称" prop="name">
<el-input v-model="billInfo.billBookDialog.data.name" autocomplete="off"
placeholder="请输入类型名称"
:maxlength="10" show-word-limit/>
</el-form-item>
<el-form-item label="类型图标" prop="icon">
<el-input v-model="billInfo.billBookDialog.data.icon" autocomplete="off"
placeholder="请输入类型图标"
:maxlength="40" show-word-limit/>
</el-form-item>
<el-form-item label="图标颜色" prop="color">
<el-color-picker v-model="billInfo.billBookDialog.data.color" />
</el-form-item>
<el-form-item label="图标预览">
<i :class="billInfo.billBookDialog.data.icon"
:style="{color: billInfo.billBookDialog.data.color, fontSize: '24px'}"></i>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { useBillStore } from '@/store/bill'
import { ElMessage, FormRules } from 'element-plus'
import { IBillBook } from '@/types/bill'
import { billBookRules } from '@/utils/element/elRules'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import type { IDialog, IHandleApi } from 'vue3-common/types'
import { addBillBookApi, updateBillBookApi } from '@/apis/bill'
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
const formRef = ref()
const rules = reactive<FormRules>(billBookRules)
const billStore = useBillStore()
const billInfo = reactive({
setBillBookType: 'ADD' as IHandleApi,
billBookDialog: {
title: '',
visible: false,
data: {}
} as IDialog<IBillBook>
})
onMounted(async () => {
await billStore.queryBillBook()
})
/**
* 点击新增账本按钮
*/
const addNewBookClick = () => {
billInfo.setBillBookType = 'ADD'
billInfo.billBookDialog = {
title: '新增账本',
visible: true,
data: {
name: '',
icon: '',
color: ''
}
}
}
/**
* 点击编辑账本按钮
* @param book 账本
*/
const editBookClick = async (book: IBillBook) => {
billInfo.setBillBookType = 'UPDATE'
billInfo.billBookDialog = {
title: '编辑账本',
visible: true,
data: deepCopyObject(book)
}
}
/**
* 点击删除账本按钮
* @param book 账本
*/
const deleteBookClick = (book: IBillBook) => {
billInfo.setBillBookType = 'DELETE'
ElMessage.error('暂不支持该功能')
}
const openDialogEvent = () => {
nextTick(() => {
formRef.value.clearValidate()
})
}
const setBillBookApi = async (id?: number) => {
switch (billInfo.setBillBookType) {
case 'ADD':
await addBillBookApi(billInfo.billBookDialog.data)
ElMessage.success('新增账本成功')
break
case 'UPDATE':
await updateBillBookApi(id as number, billInfo.billBookDialog.data)
ElMessage.success('更新账本成功')
break
case 'DELETE':
ElMessage.success('删除账本成功')
break
default:
break
}
await billStore.queryBillBook()
billInfo.billBookDialog.visible = false
}
const cancelClick = () => {
billInfo.billBookDialog.visible = false
}
const confirmClick = async () => {
await formRef.value.validate(async (valid) => {
if (valid) {
await setBillBookApi(billInfo.billBookDialog.data.id)
}
})
}
</script>

View File

@@ -1,222 +0,0 @@
<template>
<div class="bill-setting">
<div class="button-container">
<el-select
v-model="billInfo.bookName"
placeholder="请选择账本名称"
@focus="focusBookNameEvent"
@change="selectBookNameEvent"
style="width: 150px;">
<el-option
v-for="(item, index) in billInfo.billBookList"
:key="index" :label="item.name" :value="item.name"
/>
</el-select>
<el-button type="primary" @click="addNewCategoryClick">新增类别</el-button>
</div>
<el-table
:data="billInfo.billCategoryList"
border stripe
:header-cell-style="tableHeaderStyle()"
:cell-style="tableCellStyle()"
:row-style="tableRowStyle()"
class="card-item">
<el-table-column type="index" align="center" width="50"/>
<el-table-column prop="name" label="名称" align="center" width="80"/>
<el-table-column label="类型" align="center" width="80">
<template #default="scope">
<el-tag v-if="scope.row.type === 'expensive'" effect="dark" color="#CA6C65">支出</el-tag>
<el-tag v-else effect="dark" color="#6FBB69">收入</el-tag>
</template>
</el-table-column>
<el-table-column label="图标" align="center" width="80">
<template #default="scope">
<i :class="scope.row.icon" :style="{color: scope.row.color, fontSize: '24px'}"></i>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" size="small" @click="editCategoryClick(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="deleteCategoryClick(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="billInfo.billCategoryDialog.visible"
:title="billInfo.billCategoryDialog.title"
center width="80%" @open="openDialogEvent">
<el-form ref="formRef" :model="billInfo.billCategoryDialog.data"
:rules="rules" label-position="top" label-width="100px">
<el-form-item label="账本名称" prop="bookName">
<el-select
v-model="billInfo.billCategoryDialog.data.bookName"
placeholder="请选择账本名称"
@focus="focusBookNameEvent">
<el-option
v-for="(item, index) in billInfo.billBookList"
:key="index" :label="item.name" :value="item.name"
/>
</el-select>
</el-form-item>
<el-form-item label="类别名称" prop="name">
<el-input v-model="billInfo.billCategoryDialog.data.name" autocomplete="off"
placeholder="请输入类型名称"
:maxlength="10" show-word-limit/>
</el-form-item>
<el-form-item label="类型名称" prop="type">
<el-radio-group v-model="billInfo.billCategoryDialog.data.type">
<el-radio value="expensive">支出</el-radio>
<el-radio value="income">收入</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="类别图标" prop="icon">
<el-input v-model="billInfo.billCategoryDialog.data.icon" autocomplete="off"
placeholder="请输入类型图标"
:maxlength="40" show-word-limit/>
</el-form-item>
<el-form-item label="图标颜色" prop="color">
<el-color-picker v-model="billInfo.billCategoryDialog.data.color" />
</el-form-item>
<el-form-item label="图标预览">
<i :class="billInfo.billCategoryDialog.data.icon"
:style="{color: billInfo.billCategoryDialog.data.color, fontSize: '24px'}"></i>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { useBillStore } from '@/store/bill'
import { IBillBook, IBillCategory } from '@/types/bill'
import { ElMessage, FormRules } from 'element-plus'
import { billCategoryRules } from '@/utils/element/elRules'
import { addBillCategoryApi, updateBillCategoryApi } from '@/apis/bill'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { tableHeaderStyle, tableRowStyle, tableCellStyle } from 'vue3-common/utils/elUtil'
import type { IDialog, IHandleApi } from 'vue3-common/types'
const formRef = ref()
const rules = reactive<FormRules>(billCategoryRules)
const billStore = useBillStore()
const billInfo = reactive({
bookName: '日常账本',
setBillCategoryType: 'ADD' as IHandleApi,
billCategoryDialog: {
title: '',
visible: false,
data: {}
} as IDialog<IBillCategory>,
billBookList: [] as IBillBook[],
billCategoryList: [] as IBillCategory[]
})
onMounted(async () => {
await billStore.queryBillCategory()
queryBookCategoryByBookName(billInfo.bookName)
})
const selectBookNameEvent = (value: string) => {
queryBookCategoryByBookName(value)
}
const queryBookCategoryByBookName = (bookName: string) => {
billInfo.billCategoryList = billStore.billCategoryList.filter((item) => {
return item.bookName === bookName
})
}
/**
* 点击新增类别按钮
*/
const addNewCategoryClick = () => {
billInfo.setBillCategoryType = 'ADD'
billInfo.billCategoryDialog = {
title: '新增账单类别',
visible: true,
data: {
bookName: '',
name: '',
type: '',
icon: '',
color: ''
}
}
}
/**
* 点击编辑类别按钮
* @param category 类别
*/
const editCategoryClick = async (category: IBillCategory) => {
billInfo.setBillCategoryType = 'UPDATE'
billInfo.billCategoryDialog = {
title: '编辑账单类别',
visible: true,
data: deepCopyObject(category)
}
}
/**
* 点击删除类别按钮
* @param category 类别
*/
const deleteCategoryClick = (category: IBillCategory) => {
billInfo.setBillCategoryType = 'DELETE'
ElMessage.error('暂不支持该功能')
}
const openDialogEvent = () => {
nextTick(() => {
formRef.value.clearValidate()
})
}
const focusBookNameEvent = async () => {
await billStore.queryBillBook()
billInfo.billBookList = billStore.billBookList
}
const setBillCategoryApi = async (id?: number) => {
switch (billInfo.setBillCategoryType) {
case 'ADD':
await addBillCategoryApi(billInfo.billCategoryDialog.data)
ElMessage.success('新增账单类别成功')
break
case 'UPDATE':
await updateBillCategoryApi(id as number, billInfo.billCategoryDialog.data)
ElMessage.success('更新账单类别成功')
break
case 'DELETE':
ElMessage.success('删除账单类别成功')
break
default:
break
}
await billStore.queryBillCategory()
billInfo.billCategoryDialog.visible = false
}
const cancelClick = () => {
billInfo.billCategoryDialog.visible = false
}
const confirmClick = async () => {
await formRef.value.validate(async (valid) => {
if (valid) {
await setBillCategoryApi(billInfo.billCategoryDialog.data.id)
queryBookCategoryByBookName(billInfo.bookName)
}
})
}
</script>

View File

@@ -1,235 +0,0 @@
<template>
<el-dialog v-model="billStore.billDialog.visible"
title="账单内容" center class="bill-dialog"
@open="openDialogEvent">
<el-form ref="formRef" :model="billStore.billDialog.data"
:rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="账本类型" prop="bookName">
<el-radio-group v-model="billStore.billDialog.data.bookName"
@change="changeBillBookNameEvent">
<el-radio v-for="(item, index) in billStore.billBookList"
:value="item.name" :key="index">
<template #default>
<i :class="item.icon" :style="{color: item.color}"/>
<span style="margin-left: 5px;">{{ item.name }}</span>
</template>
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="账单类别" prop="type">
<el-radio-group v-model="billStore.billDialog.data.type"
@change="changeBillCategoryEvent">
<el-radio v-for="(item, index) in billInfo.billTypeList"
:value="item" :key="index">
{{ getBillTypeName(item) }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="账单分类" prop="category">
<el-radio-group v-model="billStore.billDialog.data.category">
<el-radio v-for="(item, index) in billInfo.billCategoryList"
:value="item.name" :key="index">
<template #default>
<i :class="item.icon" :style="{color: item.color}"/>
<span style="margin-left: 5px;">{{ item.name }}</span>
</template>
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="账单时间" prop="date">
<el-date-picker
v-model="billStore.billDialog.data.date"
type="date" :editable="false" placeholder="请选择账单时间"
value-format="YYYY-MM-DD"/>
</el-form-item>
<el-form-item label="账单地点" prop="location">
<el-input v-model="billStore.billDialog.data.location" autocomplete="off"
placeholder="请输入账单地点" clearable
show-word-limit maxlength="10">
<template #prefix>
<el-icon class="el-input__icon"><location/></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="账单账户" prop="payAccount">
<el-radio-group v-model="billStore.billDialog.data.payAccount">
<el-radio v-for="(item, index) in billStore.billPayList"
:value="item.name" :key="index">
<template #default>
<i :class="item.icon" :style="{color: item.color}"/>
<span style="margin-left: 5px;">{{ item.name }}</span>
</template>
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="账单金额" prop="amount">
<amount-input v-model="billStore.billDialog.data.amount"/>
</el-form-item>
<el-form-item label="账单内容" prop="content">
<el-input v-model="billStore.billDialog.data.content" autocomplete="off"
placeholder="请输入账单内容" clearable
show-word-limit maxlength="10">
<template #prefix>
<el-icon class="el-input__icon"><document/></el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="账单备注">
<el-input v-model="billStore.billDialog.data.remark" type="textarea"
placeholder="请输入账单备注" clearable
:rows="5" show-word-limit maxlength="200">
</el-input>
</el-form-item>
<el-form-item label="账单图片">
<upload-image v-model="billStore.billDialog.data.imageList"
:service-url="fileServiceUrl"
:service-file-root-path="serviceFileRootPath"
:image-path="billStore.billImagePath"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">
{{ billStore.billApiType === 'UPDATE' ? '编辑' : '新增' }}
</el-button>
<el-button v-if="billStore.billApiType === 'UPDATE'"
type="danger" @click="deleteClick">
删除
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { UploadImage, AmountInput } from 'vue3-common'
import { useBillStore } from '@/store/bill'
import { Location, Document } from '@element-plus/icons-vue'
import { FormRules } from 'element-plus'
import { billRecordRules } from '@/utils/element/elRules'
import { nextTick, reactive, ref } from 'vue'
import { IBillCategory } from '@/types/bill'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
const formRef = ref()
const rules = reactive<FormRules>(billRecordRules)
const billStore = useBillStore()
const billInfo = reactive({
billTypeList: [] as string[],
billCategoryList: [] as IBillCategory[]
})
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
// 如果是新增账单 默认选择第一个账本
if (billStore.billApiType !== 'UPDATE') {
billStore.billDialog.data.bookName = billStore.billBookList[0].name
}
getBillTypeByBookName(billStore.billDialog.data.bookName)
// 如果是新增账单 默认选择一个类别
if (billStore.billApiType !== 'UPDATE') {
billStore.billDialog.data.type = billInfo.billTypeList[0]
}
getBillCategory()
// 如果是新增账单 默认选择一个分类
if (billStore.billApiType !== 'UPDATE') {
billStore.billDialog.data.category = billInfo.billCategoryList[0].name
billStore.billDialog.data.payAccount = billStore.billPayList[0].name
}
await nextTick(() => {
formRef.value.clearValidate()
})
}
/**
* 获取账单类型名称
* @param type 账单类型
*/
const getBillTypeName = (type: string) => {
return type === 'expensive' ? '支出' : '收入'
}
/**
* 选择账本事件
* @param value 账本名称
*/
const changeBillBookNameEvent = (value: string) => {
getBillTypeByBookName(value)
billStore.billDialog.data.type = billInfo.billTypeList[0]
getBillCategory()
billStore.billDialog.data.category = billInfo.billCategoryList[0].name
}
/**
* 选择账单类别事件
*/
const changeBillCategoryEvent = () => {
getBillCategory()
billStore.billDialog.data.category = billInfo.billCategoryList[0].name
}
/**
* 通过账本获取账单类型
* @param bookName 账本
*/
const getBillTypeByBookName = (bookName: string) => {
billInfo.billTypeList = []
billStore.billCategoryList.forEach((item) => {
if (item.bookName === bookName && !billInfo.billTypeList.includes(item.type)) {
billInfo.billTypeList.push(item.type)
}
})
}
/**
* 获取账单类别
*/
const getBillCategory = () => {
billInfo.billCategoryList = billStore.billCategoryList.filter((value) => {
return value.bookName === billStore.billDialog.data.bookName
&& value.type === billStore.billDialog.data.type
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(() => {
billStore.handleBillApi(billStore.billDialog.data)
})
}
const cancelClick = () => {
billStore.billDialog.visible = false
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该账单内容?').then(() => {
billStore.billApiType = 'DELETE'
billStore.handleBillApi(billStore.billDialog.data)
})
}
</script>

View File

@@ -1,166 +0,0 @@
<template>
<div class="bill-setting">
<div class="button-container">
<el-button type="primary" @click="addNewPayClick">新增账户</el-button>
</div>
<el-table
:data="billStore.billPayList"
border stripe
:header-cell-style="tableHeaderStyle()"
:cell-style="tableCellStyle()"
:row-style="tableRowStyle()"
style="width: 100%"
class="card-item">
<el-table-column type="index" align="center" width="50" />
<el-table-column prop="name" label="名称" align="center" width="100"/>
<el-table-column label="图标" align="center" width="80">
<template #default="scope">
<i :class="scope.row.icon" :style="{color: scope.row.color, fontSize: '24px'}"></i>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" @click="editPayClick(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="deletePayClick(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="billInfo.billPayDialog.visible"
:title="billInfo.billPayDialog.title"
center width="80%" @open="openDialogEvent">
<el-form ref="formRef" :model="billInfo.billPayDialog.data"
:rules="rules" label-position="top" label-width="100px">
<el-form-item label="账户名称" prop="name">
<el-input v-model="billInfo.billPayDialog.data.name" autocomplete="off"
placeholder="请输入类型名称"
:maxlength="10" show-word-limit/>
</el-form-item>
<el-form-item label="账户图标" prop="icon">
<el-input v-model="billInfo.billPayDialog.data.icon" autocomplete="off"
placeholder="请输入类型图标"
:maxlength="40" show-word-limit/>
</el-form-item>
<el-form-item label="图标颜色" prop="color">
<el-color-picker v-model="billInfo.billPayDialog.data.color" />
</el-form-item>
<el-form-item label="图标预览">
<i :class="billInfo.billPayDialog.data.icon"
:style="{color: billInfo.billPayDialog.data.color, fontSize: '24px'}"></i>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { useBillStore } from '@/store/bill'
import { ElMessage, FormRules } from 'element-plus'
import { IBillPay } from '@/types/bill'
import { billPayRules } from '@/utils/element/elRules'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { addBillPayApi, updateBillPayApi } from '@/apis/bill'
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
import type { IDialog, IHandleApi } from 'vue3-common/types'
const formRef = ref()
const rules = reactive<FormRules>(billPayRules)
const billStore = useBillStore()
const billInfo = reactive({
setBillPayType: 'ADD' as IHandleApi,
billPayDialog: {
title: '',
visible: false,
data: {}
} as IDialog<IBillPay>
})
onMounted(async () => {
await billStore.queryBillPay()
})
/**
* 点击新增账户按钮
*/
const addNewPayClick = () => {
billInfo.setBillPayType = 'ADD'
billInfo.billPayDialog = {
title: '新增账户',
visible: true,
data: {
name: '',
icon: '',
color: ''
}
}
}
/**
* 点击编辑账户按钮
* @param pay 账户
*/
const editPayClick = async (pay: IBillPay) => {
billInfo.setBillPayType = 'UPDATE'
billInfo.billPayDialog = {
title: '编辑账户',
visible: true,
data: deepCopyObject(pay)
}
}
/**
* 点击删除账户按钮
* @param pay 账户
*/
const deletePayClick = (pay: IBillPay) => {
billInfo.setBillPayType = 'DELETE'
ElMessage.error('暂不支持该功能')
}
const openDialogEvent = () => {
nextTick(() => {
formRef.value.clearValidate()
})
}
const setBillBookApi = async (id?: number) => {
switch (billInfo.setBillPayType) {
case 'ADD':
await addBillPayApi(billInfo.billPayDialog.data)
ElMessage.success('新增账户成功')
break
case 'UPDATE':
await updateBillPayApi(id as number, billInfo.billPayDialog.data)
ElMessage.success('更新账户成功')
break
case 'DELETE':
ElMessage.success('删除账户成功')
break
default:
break
}
await billStore.queryBillPay()
billInfo.billPayDialog.visible = false
}
const cancelClick = () => {
billInfo.billPayDialog.visible = false
}
const confirmClick = async () => {
await formRef.value.validate((valid) => {
if (valid) {
setBillBookApi(billInfo.billPayDialog.data.id)
}
})
}
</script>

View File

@@ -1,40 +0,0 @@
<template>
<div class="budget-chart">
<div class="budget-rank-chart card-item" ref="budgetRankChartRef"/>
<div class="budget-project-chart card-item" ref="budgetProjectChartRef"/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import * as echarts from 'echarts'
import { useBudgetStore } from '@/store/budget'
import { barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
const budgetStore = useBudgetStore()
const budgetRankChartRef = ref<HTMLElement>()
const budgetProjectChartRef = ref<HTMLElement>()
let budgetRankChart: echarts.ECharts
let budgetProjectChart: echarts.ECharts
watch(() => budgetStore.isRefresh, () => {
updateChart()
})
onMounted(() => {
budgetRankChart = echarts.init(budgetRankChartRef.value as HTMLElement)
budgetProjectChart = echarts.init(budgetProjectChartRef.value as HTMLElement)
})
const updateChart = () => {
// 预算排行统计图
const rankNameList = budgetStore.budgetRankStatsList.map((item) => item.name)
const rankValueList = budgetStore.budgetRankStatsList.map((item) => item.value)
budgetRankChart.setOption(barChartOptions('📊预算排行', '项目', rankNameList, '金额', rankValueList))
// 预算项目统计图
budgetProjectChart.setOption(pieChartOptions('📊预算项目', '项目', '', budgetStore.budgetProjectStatsList))
}
</script>

View File

@@ -1,114 +0,0 @@
<template>
<el-dialog v-model="budgetStore.budgetDialog.visible"
:title="budgetStore.budgetDialog.title"
center @open="openDialogEvent"
width="50%" class="budget-dialog">
<el-form ref="formRef" :model="budgetStore.budgetDialog.data"
:rules="rules" label-width="100px">
<el-form-item label="预算类别" prop="category">
<el-select
v-model="budgetStore.budgetDialog.data.category"
allow-create filterable placeholder="请输入或选择预算类别">
<el-option
v-for="(item, index) in budgetStore.budgetCategoryList"
:key="index" :label="item" :value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="预算项目" prop="project">
<el-input v-model="budgetStore.budgetDialog.data.project" autocomplete="off"
placeholder="请输入预算项目" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="预算内容" prop="content">
<el-input v-model="budgetStore.budgetDialog.data.content" autocomplete="off"
placeholder="请输入预算内容" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="供应商" prop="vendor">
<el-input v-model="budgetStore.budgetDialog.data.vendor" autocomplete="off"
placeholder="请输入预算供应商" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="预算支出" prop="budget">
<el-input
v-model="budgetStore.budgetDialog.data.budget"
placeholder="请输入预算支出" clearable
:formatter="(value: string) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
:parser="(value: string) => value.replace(/\$\s?|(,*)/g, '')"
/>
</el-form-item>
<el-form-item label="实际支出" prop="actual">
<el-input
v-model="budgetStore.budgetDialog.data.actual"
placeholder="请输入实际支出" clearable
:formatter="(value: string) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
:parser="(value: string) => value.replace(/\$\s?|(,*)/g, '')"
/>
</el-form-item>
<el-form-item label="支付时间">
<el-date-picker
v-model="budgetStore.budgetDialog.data.payTime"
type="datetime" placeholder="请选择支付时间"
value-format="YYYY-MM-DD HH:mm:ss"/>
</el-form-item>
<el-form-item label="备注" prop="remake">
<el-input v-model="budgetStore.budgetDialog.data.remake" autocomplete="off"
placeholder="请输入备注" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">确认</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { useBudgetStore } from '@/store/budget'
import { FormRules } from 'element-plus'
import { budgetRules } from '@/utils/element/elRules'
import { nextTick, reactive, ref } from 'vue'
const formRef = ref()
const rules = reactive<FormRules>(budgetRules)
const budgetStore = useBudgetStore()
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
await nextTick(() => {
formRef.value.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(async () => {
await budgetStore.handleBlogApi(budgetStore.budgetDialog.data.id)
budgetStore.queryInfo.category = budgetStore.budgetDialog.data.category
})
}
const cancelClick = () => {
budgetStore.budgetDialog.visible = false
}
</script>

View File

@@ -1,54 +0,0 @@
<template>
<div class="budget-query common-query">
<el-switch
v-model="budgetStore.showDataType"
inline-prompt
style="--el-switch-on-color: #CA6C65; --el-switch-off-color: #009FA8"
active-value="CHART" inactive-value="DATA"
active-text="图表统计" inactive-text="数据显示"
@change="changeShowDataTypeEvent"
/>
<el-select
v-model="budgetStore.queryInfo.category"
placeholder="请选择类别" style="width: 120px"
@focus="focusCategoryEvent" @change="changeCategoryEvent">
<el-option
v-for="(item, index) in budgetStore.budgetCategoryList"
:key="index" :label="item" :value="item"
/>
</el-select>
<el-button v-if="budgetStore.showDataType === 'DATA'"
type="primary" @click="addBudgeClick">新增预算
</el-button>
</div>
</template>
<script lang="ts" setup>
import { useBudgetStore } from '@/store/budget'
const budgetStore = useBudgetStore()
const focusCategoryEvent = async () => {
await budgetStore.queryBudgetCategory()
}
const changeCategoryEvent = async () => {
await budgetStore.refreshInfo()
}
const addBudgeClick = () => {
budgetStore.budgetApiType = 'ADD'
budgetStore.budgetDialog = {
title: '新增预算',
visible: true,
data: budgetStore.getEmptyBudget()
}
budgetStore.budgetDialog.data.category = budgetStore.queryInfo.category
}
const changeShowDataTypeEvent = async () => {
await budgetStore.refreshInfo()
}
</script>

View File

@@ -1,98 +0,0 @@
<template>
<el-table :data="budgetStore.budgetList"
border stripe show-summary
:span-method="spanMethod" :summary-method="summaryMethod"
:header-cell-style="tableHeaderStyle()"
:row-style="tableRowStyle()" :cell-style="tableCellStyle()"
class="budget-table card-item">
<el-table-column type="index" align="center" width="80"/>
<el-table-column prop="project" label="项目" align="center" min-width="5%"/>
<el-table-column prop="content" label="内容" align="center" min-width="10%"/>
<el-table-column prop="vendor" label="供应商" align="center" min-width="10%"/>
<el-table-column prop="budget" label="预算支出(元)" align="center" min-width="10%">
<template #default="scope">
{{ formatAmount(scope.row.budget) }}
</template>
</el-table-column>
<el-table-column prop="actual" label="实际支出(元)" align="center" min-width="10%">
<template #default="scope">
{{ formatAmount(scope.row.actual) }}
</template>
</el-table-column>
<el-table-column prop="save" label="节省预算(元)" align="center" min-width="10%">
<template #default="scope">
{{ formatAmount(scope.row.budget - scope.row.actual) }}
</template>
</el-table-column>
<el-table-column prop="percent" label="支出占比" align="center" min-width="10%">
<template #default="scope">
{{ `${((scope.row.actual / totalActual) * 100).toFixed(2)}%` }}
</template>
</el-table-column>
<el-table-column prop="remake" label="备注" align="center" min-width="10%"/>
<el-table-column fixed="right" label="操作" align="center" width="250">
<template #default="scope">
<el-button type="primary" @click="editBudget(scope.row)">编辑</el-button>
<el-button type="primary" @click="editBudget(scope.row)">账单</el-button>
<el-button type="danger" @click="deleteBudget(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script lang="ts" setup>
import { useBudgetStore } from '@/store/budget'
import { IBudgetInfo } from '@/types/budget.ts'
import { computed } from 'vue'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { formatAmount } from 'vue3-common/utils/numberUtil'
import { getRowspan, getSummaryData } from 'vue3-common/utils/elUtil'
import { commonElMessageBox, tableHeaderStyle, tableCellStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
const budgetStore = useBudgetStore()
const spanIndex = 1
const summaryColumnList = ['budget', 'actual', 'save']
const totalActual = computed(() => {
return budgetStore.budgetList.reduce((sum, item: IBudgetInfo) => {
return sum + Number(item.actual || 0)
}, 0)
})
const spanMethod = (data: { row: any; column: any; rowIndex: number; columnIndex: number }) => {
// 需要合并的列索引
if (data.columnIndex === spanIndex) {
return getRowspan(budgetStore.budgetList, data.column.property, data.rowIndex, data.row)
}
}
const summaryMethod = (data: { columns: any[]; data: any[] }) => {
const sums = getSummaryData(data.columns, data.data, summaryColumnList)
// 格式化输出
sums[6] = formatAmount(parseFloat(sums[4] as string) - parseFloat(sums[5] as string))
sums[4] = formatAmount(parseFloat(sums[4] as string))
sums[5] = formatAmount(parseFloat(sums[5] as string))
sums[7] = '100%'
return sums
}
const editBudget = (budget: IBudgetInfo) => {
budgetStore.budgetApiType = 'UPDATE'
budgetStore.budgetDialog = {
title: '编辑预算',
visible: true,
data: deepCopyObject(budget)
}
}
const deleteBudget = (budget: IBudgetInfo) => {
budgetStore.budgetApiType = 'DELETE'
commonElMessageBox('是否删除该预算?').then(() => {
budgetStore.handleBlogApi(budget.id as number)
})
}
</script>

View File

@@ -1,134 +0,0 @@
<template>
<div class="life-record-card card-item">
<el-carousel v-if="props.foodItem?.recordList.length > 0"
arrow="always" @change="changeFoodEvent">
<el-carousel-item v-for="(item, index) in getFoodRecord(props.foodItem?.recordList)"
:key="index">
<el-image :src="serviceFileRootPath + item.imageUrl"
fit="contain" alt="暂无成果">
<template #error>
<div class="image-slot">
<span>暂无图片</span>
</div>
</template>
</el-image>
</el-carousel-item>
</el-carousel>
<el-empty v-else description="暂无成果" />
<div class="record-info">
<div class="top-container">
<div class="info-list">
<div class="info-item">
<span class="title">{{ props.foodItem.name }}</span>
</div>
</div>
<div class="button-info">
<el-button v-if="!isMobile()"
type="primary" :icon="Edit" circle :size="'small'"
@click="editFoodRecipeClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
@click="viewFoodRecipeClick()" />
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
@click="deleteFoodRecipeClick()" />
</div>
</div>
<div class="bottom-container">
<div class="info-list">
<div class="info-item category-item">
<i class="fa-solid fa-utensils"></i>
{{ props.foodItem.category }}
</div>
</div>
<div v-if="props.foodItem?.recordList.length > 0" class="info-list">
<div class="info-item">
<i class="fa-solid fa-flag"></i>
{{ props.foodItem.recordList[foodCardInfo.currentFoodIndex].person}}
</div>
<div class="info-item">
<i class="fa-regular fa-calendar-check"></i>
{{ formatDate(props.foodItem.recordList[foodCardInfo.currentFoodIndex].date) }}
</div>
</div>
<div v-else>
<span>暂无成果</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
import { useFoodStore } from '@/store/food'
import { defineProps, onMounted, PropType, reactive } from 'vue'
import { IFoodRecipe, IFoodRecord } from '@/types/food.ts'
import { useRouter } from 'vue-router'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils'
import { isMobile } from 'vue3-common/utils/layoutUtil'
const router = useRouter()
const foodStore = useFoodStore()
const foodCardInfo = reactive({
recommendRate: 0,
currentFoodIndex: 0
})
const props = defineProps({
foodItem: {
required: true,
type: Object as PropType<IFoodRecipe>
}
})
onMounted(() => {
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
})
const getFoodRecord = (recordList: IFoodRecord[]) => {
return recordList.filter((item) => item.imageUrl)
}
const editFoodRecipeClick = async () => {
const id = props.foodItem?.id
foodStore.foodRecipeApiType = 'UPDATE'
await foodStore.queryFoodRecipe(id as number)
foodStore.foodRecipeDialog = {
title: '编辑菜谱',
visible: true,
data: deepCopyObject(foodStore.currentFoodRecipe)
}
}
const viewFoodRecipeClick = async () => {
if (isMobile()) {
await router.push({ name: 'MobileHomeFoodDetailId', params: { id: props.foodItem?.id } })
} else {
await router.push({ name: 'HomeLifeFoodId', params: { id: props.foodItem?.id } })
}
}
const deleteFoodRecipeClick = () => {
foodStore.foodRecipeApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
foodStore.handleFoodRecipeApi(props.foodItem?.id as number)
})
}
const changeFoodEvent = (current: number) => {
foodCardInfo.currentFoodIndex = current
}
</script>
<style lang="scss">
@use "@/styles/home/life.record.module.scss";
</style>

View File

@@ -1,57 +0,0 @@
<template>
<div class="food-daily-list">
<el-date-picker v-model="foodStore.queryRecord.year"
type="year" :editable="false" style="width: 120px;"
@change="foodStore.refreshInfo()"/>
<el-empty v-if="foodDailyInfo.dateList.length === 0"/>
<el-scrollbar>
<food-daily-item v-for="(item, index) in foodDailyInfo.dateList"
:key="index" :date="item"/>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import FoodDailyItem from '@/components/Home/Food/FoodDailyItem.vue'
import { reactive, watch } from 'vue'
import { useFoodStore } from '@/store/food.ts'
const foodStore = useFoodStore()
watch(() => foodStore.isRefresh, () => {
updateInfo()
})
const foodDailyInfo = reactive({
dateList: [] as string[]
})
const updateInfo = () => {
const dateList = foodStore.foodRecordList.map((item) => item.date)
foodDailyInfo.dateList = [...new Set(dateList)] as string[]
}
</script>
<style lang="scss">
.food-daily-list {
height: calc(100vh - 120px);
overflow: auto;
display: flex;
flex-direction: column;
gap: 1vh;
.el-date-editor {
align-self: center;
}
.el-scrollbar {
.el-scrollbar__view {
display: flex;
flex-direction: column;
gap: 2vh;
}
}
}
</style>

View File

@@ -1,116 +0,0 @@
<template>
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
center @open="openDialogEvent" class="food-dialog">
<el-form ref="formRef" :model="dialogData.data"
:rules=rules label-width="80px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="美食名称" prop="name">
<el-input v-model="dialogData.data.name" autocomplete="off"
placeholder="请输入美食名称" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="美食菜系" prop="category">
<el-select v-model="dialogData.data.category">
<el-option v-for="(item, index) in foodCategoryList"
:key="index" :label="item" :value="item"/>
</el-select>
</el-form-item>
<el-form-item label="推荐指数" prop="recommendRate">
<el-rate v-model="dialogData.data.recommendRate"
:colors="foodStore.rateColorList"/>
</el-form-item>
<el-form-item label="食材">
<food-material-form />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="步骤">
<food-step-form />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="dialogData.data.remark" autocomplete="off"
placeholder="请输入备注" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import FoodMaterialForm from '@/components/Home/Food/Form/FoodMaterialForm.vue'
import FoodStepForm from '@/components/Home/Food/Form/FoodStepForm.vue'
import { useFoodStore } from '@/store/food'
import { FormInstance, FormRules } from 'element-plus'
import { computed, nextTick, ref, reactive } from 'vue'
import { foodRules } from '@/utils/element/elRules'
const rules = reactive<FormRules>(foodRules)
const formRef = ref<FormInstance>()
const foodStore = useFoodStore()
const foodCategoryList = ['家常菜', '淮扬菜', '鲁菜', '川菜', '粤菜', '闽菜', '浙菜', '湘菜', '徽菜', '其他']
const dialogData = computed(() => foodStore.foodRecipeDialog)
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
await nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value?.validate().then(() => {
foodStore.handleFoodRecipeApi()
})
}
const cancelClick = () => {
dialogData.value.visible = false
}
</script>
<style lang="scss">
.food-dialog {
width: 75%;
.el-dialog__body {
height: 65vh;
.el-button.is-text {
padding: 0;
}
.material-table {
height: 45vh;
}
.food-step-table {
height: 55vh;
}
}
}
</style>

View File

@@ -1,68 +0,0 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="query-item">
<span>分类</span>
<el-segmented v-model="foodStore.queryRecipe.category"
:options="queryCategoryList"
@change="queryFoodSegmented"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="foodStore.queryDateType"
@change="foodStore.refreshInfo()"
style="width: 120px;align-self: center;">
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
:label="item.label" :value="item.value"/>
</el-select>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { onMounted, ref } from 'vue'
import { ICategory } from '@/types'
const foodStore = useFoodStore()
const queryCategoryList = ref([] as ICategory[])
onMounted(async () => {
await foodStore.queryFoodCategory()
initCategoryList()
})
const dateTypeOptions = [{
label: '按月选择',
value: 'month'
},
{
label: '按年选择',
value: 'year'
}]
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({
label: '全部',
value: ''
})
foodStore.foodCategoryList.forEach((item) => {
queryCategoryList.value.push({
label: item,
value: item
})
})
}
const queryFoodSegmented = async () => {
foodStore.pageInfo = {
currentPage: 1,
pageSize: 3,
totalSize: 1
}
foodStore.foodRecipeList = []
await foodStore.queryMobileFoodRecipeList()
}
</script>

View File

@@ -1,45 +0,0 @@
<template>
<div class="food-list card-item">
<el-empty v-if="foodStore.foodRecipeList.length === 0" description="暂无美食记录"/>
<div v-else class="list">
<food-card v-for="(item, index) in foodStore.foodRecipeList"
:key="index" :food-item="item"/>
</div>
<el-pagination
v-model:current-page="foodStore.pageInfo.currentPage"
:page-size="foodStore.pageInfo.pageSize"
layout="total, prev, pager, next"
:total="foodStore.pageInfo.totalSize"
@change="changePageEvent"
/>
<el-button v-if="isLargeScreen()"
type="primary" :icon="Plus" circle
@click="addFoodRecipeClick" class="add-new" />
</div>
</template>
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import FoodCard from '@/components/Home/Food/FoodCard.vue'
import { useFoodStore } from '@/store/food.ts'
import { isLargeScreen } from '@/utils'
const foodStore = useFoodStore()
const addFoodRecipeClick = () => {
foodStore.foodRecipeApiType = 'ADD'
foodStore.foodRecipeDialog = {
title: '新增菜谱',
visible: true,
data: foodStore.getEmptyFoodRecipe()
}
}
const changePageEvent = (value: number) => {
foodStore.pageInfo.currentPage = value
foodStore.queryFoodRecipeList()
}
</script>

Some files were not shown because too many files have changed in this diff Show More