Compare commits
50 Commits
930202a6b6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a71bef851 | |||
| 8ecc38befd | |||
| e205d1ddf4 | |||
| be05e4695c | |||
| 4b5f853603 | |||
| 6ff52e9d50 | |||
| 3535d33c4b | |||
| 0ec0bba2fa | |||
| 98f2842869 | |||
| f7e98f2a28 | |||
| b9f4936dcc | |||
| 3201a1c32b | |||
| 6aed1854ca | |||
| f8a605b8c0 | |||
| 08999c608d | |||
| 7eeac7c465 | |||
| 420f250bac | |||
| 3e59b58f84 | |||
| ac914f6c05 | |||
| 11887bf3d5 | |||
| 1fb76fc220 | |||
| d7d167f3e2 | |||
| 5b7dcd614a | |||
| 4dc541da85 | |||
| 223a59cd8a | |||
| 2fbae7f80f | |||
| 8940b3972a | |||
| 1b8bd5a7e2 | |||
| 45342fb55c | |||
| e5e1e5e2b3 | |||
| 94270b923c | |||
| aa7abbdde3 | |||
| 7b96e60b8d | |||
| 71639cad51 | |||
| d87732dbf7 | |||
| c0cfbbaa15 | |||
| f5e75b745f | |||
| 4d7946759c | |||
| 2bcc61fdaa | |||
| 63a1e126ad | |||
| f62613205c | |||
| 5ec0781a53 | |||
| e875a8e2d5 | |||
| 7032070d3f | |||
| ee20c19b72 | |||
| 5f3a7ca5b4 | |||
| ed0c8e6664 | |||
| 355a6c256d | |||
| 9e0b8ead79 | |||
| cc33d3028b |
1
.env.development
Normal file
1
.env.development
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_ALIST_PATH=/local/test
|
||||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_ALIST_PATH=/local
|
||||||
114
components.d.ts
vendored
Normal file
114
components.d.ts
vendored
Normal 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']
|
||||||
|
}
|
||||||
|
}
|
||||||
14
nginx.conf
14
nginx.conf
@@ -8,6 +8,8 @@ server {
|
|||||||
|
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
|
client_max_body_size 500M;
|
||||||
|
|
||||||
location /sweet-hut/ {
|
location /sweet-hut/ {
|
||||||
alias /usr/share/nginx/html/;
|
alias /usr/share/nginx/html/;
|
||||||
try_files $uri $uri/ /sweet-hut/index.html;
|
try_files $uri $uri/ /sweet-hut/index.html;
|
||||||
@@ -17,10 +19,22 @@ server {
|
|||||||
proxy_pass http://host.docker.internal:8081/;
|
proxy_pass http://host.docker.internal:8081/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /alist-api/ {
|
||||||
|
proxy_pass http://host.docker.internal:5244/;
|
||||||
|
}
|
||||||
|
|
||||||
location /blog-api/ {
|
location /blog-api/ {
|
||||||
proxy_pass http://host.docker.internal:8082/;
|
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 / {
|
location / {
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -2,6 +2,7 @@
|
|||||||
"name": "sweet-hut-ui",
|
"name": "sweet-hut-ui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts-liquidfill": "^3.1.0",
|
"echarts-liquidfill": "^3.1.0",
|
||||||
"element-china-area-data": "^6.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",
|
"file-icons-vue": "^1.3.4",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"lunar-javascript": "^1.7.2",
|
"lunar-javascript": "^1.7.2",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"ua-parser-js": "^2.0.3",
|
"ua-parser-js": "^2.0.3",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
|
"vant": "^4.9.22",
|
||||||
"vue": "3.5.12",
|
"vue": "3.5.12",
|
||||||
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
|
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
|
||||||
"vue3-emoji-picker": "^1.1.8",
|
"vue3-emoji-picker": "^1.1.8",
|
||||||
@@ -38,22 +40,16 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.19.86",
|
"@types/node": "^18.19.86",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@vant/auto-import-resolver": "^1.3.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vitejs/plugin-vue": "5.2.1",
|
|
||||||
"autoprefixer": "^10.4.21",
|
"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",
|
"postcss": "^8.5.3",
|
||||||
"sass": "^1.86.3",
|
"sass": "^1.86.3",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
|
"unplugin-vue-components": "^32.1.0",
|
||||||
"vfonts": "^0.0.3",
|
"vfonts": "^0.0.3",
|
||||||
"vite": "5.2.0",
|
"vite": "^5.2.0",
|
||||||
"vite-plugin-mock": "^3.0.2",
|
"vite-plugin-mock": "^3.0.2",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^1.8.27"
|
||||||
|
|||||||
350
pnpm-lock.yaml
generated
350
pnpm-lock.yaml
generated
@@ -54,7 +54,7 @@ importers:
|
|||||||
specifier: ^6.1.0
|
specifier: ^6.1.0
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
element-plus:
|
element-plus:
|
||||||
specifier: ~2.9.7
|
specifier: ^2.9.7
|
||||||
version: 2.9.7(vue@3.5.12(typescript@5.8.3))
|
version: 2.9.7(vue@3.5.12(typescript@5.8.3))
|
||||||
file-icons-vue:
|
file-icons-vue:
|
||||||
specifier: ^1.3.4
|
specifier: ^1.3.4
|
||||||
@@ -71,9 +71,15 @@ importers:
|
|||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.3.1
|
specifier: ^2.3.1
|
||||||
version: 2.3.1(typescript@5.8.3)(vue@3.5.12(typescript@5.8.3))
|
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:
|
v-calendar:
|
||||||
specifier: ^3.1.2
|
specifier: ^3.1.2
|
||||||
version: 3.1.2(@popperjs/core@2.11.8)(vue@3.5.12(typescript@5.8.3))
|
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:
|
vue:
|
||||||
specifier: 3.5.12
|
specifier: 3.5.12
|
||||||
version: 3.5.12(typescript@5.8.3)
|
version: 3.5.12(typescript@5.8.3)
|
||||||
@@ -90,36 +96,15 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^18.19.86
|
specifier: ^18.19.86
|
||||||
version: 18.19.86
|
version: 18.19.86
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@vant/auto-import-resolver':
|
||||||
specifier: ^5.62.0
|
specifier: ^1.3.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)
|
version: 1.3.0
|
||||||
'@typescript-eslint/parser':
|
|
||||||
specifier: ^5.62.0
|
|
||||||
version: 5.62.0(eslint@7.32.0)(typescript@5.8.3)
|
|
||||||
'@vitejs/plugin-vue':
|
'@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))
|
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:
|
autoprefixer:
|
||||||
specifier: ^10.4.21
|
specifier: ^10.4.21
|
||||||
version: 10.4.21(postcss@8.5.3)
|
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:
|
postcss:
|
||||||
specifier: ^8.5.3
|
specifier: ^8.5.3
|
||||||
version: 8.5.3
|
version: 8.5.3
|
||||||
@@ -132,11 +117,14 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.8.3
|
specifier: ^5.8.3
|
||||||
version: 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:
|
vfonts:
|
||||||
specifier: ^0.0.3
|
specifier: ^0.0.3
|
||||||
version: 0.0.3
|
version: 0.0.3
|
||||||
vite:
|
vite:
|
||||||
specifier: 5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.2.0(@types/node@18.19.86)(sass@1.86.3)
|
version: 5.2.0(@types/node@18.19.86)(sass@1.86.3)
|
||||||
vite-plugin-mock:
|
vite-plugin-mock:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
@@ -489,6 +477,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@jridgewell/remapping@2.3.5':
|
||||||
|
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2':
|
'@jridgewell/resolve-uri@3.1.2':
|
||||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -500,6 +491,9 @@ packages:
|
|||||||
'@jridgewell/sourcemap-codec@1.5.0':
|
'@jridgewell/sourcemap-codec@1.5.0':
|
||||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||||
|
|
||||||
@@ -996,6 +990,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
|
resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
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':
|
'@vavt/util@2.1.0':
|
||||||
resolution: {integrity: sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ==}
|
resolution: {integrity: sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ==}
|
||||||
|
|
||||||
@@ -1064,6 +1069,9 @@ packages:
|
|||||||
'@vue/shared@3.5.13':
|
'@vue/shared@3.5.13':
|
||||||
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
||||||
|
|
||||||
|
'@vue/shared@3.5.28':
|
||||||
|
resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==}
|
||||||
|
|
||||||
'@vueuse/core@13.1.0':
|
'@vueuse/core@13.1.0':
|
||||||
resolution: {integrity: sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==}
|
resolution: {integrity: sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1096,8 +1104,13 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
acorn@8.14.1:
|
acorn@8.15.0:
|
||||||
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
|
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'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -1337,6 +1350,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||||
engines: {node: '>= 14.16.0'}
|
engines: {node: '>= 14.16.0'}
|
||||||
|
|
||||||
|
chokidar@5.0.0:
|
||||||
|
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||||
|
engines: {node: '>= 20.19.0'}
|
||||||
|
|
||||||
class-utils@0.3.6:
|
class-utils@0.3.6:
|
||||||
resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
|
resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1396,6 +1413,12 @@ packages:
|
|||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
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:
|
connect@3.7.0:
|
||||||
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
|
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
@@ -1530,6 +1553,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
detect-europe-js@0.1.2:
|
||||||
|
resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
|
||||||
|
|
||||||
detect-libc@1.0.3:
|
detect-libc@1.0.3:
|
||||||
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
@@ -1829,6 +1855,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
|
resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
exsolve@1.1.1:
|
||||||
|
resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==}
|
||||||
|
|
||||||
extend-shallow@2.0.1:
|
extend-shallow@2.0.1:
|
||||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1860,6 +1889,15 @@ packages:
|
|||||||
fastq@1.19.1:
|
fastq@1.19.1:
|
||||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
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:
|
file-entry-cache@6.0.1:
|
||||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
@@ -2228,6 +2266,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-standalone-pwa@0.1.1:
|
||||||
|
resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
|
||||||
|
|
||||||
is-string@1.1.1:
|
is-string@1.1.1:
|
||||||
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
|
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2354,6 +2395,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
|
resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
|
||||||
engines: {node: '>=4.0.0'}
|
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:
|
lodash-es@4.17.21:
|
||||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||||
|
|
||||||
@@ -2385,6 +2430,9 @@ packages:
|
|||||||
magic-string@0.30.17:
|
magic-string@0.30.17:
|
||||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
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:
|
map-cache@0.2.2:
|
||||||
resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
|
resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2472,6 +2520,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
|
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
mlly@1.8.2:
|
||||||
|
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||||
|
|
||||||
mockjs@1.1.0:
|
mockjs@1.1.0:
|
||||||
resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==}
|
resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2567,6 +2618,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||||
engines: {node: '>= 0.4'}
|
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:
|
on-finished@2.3.0:
|
||||||
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
|
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -2625,6 +2680,9 @@ packages:
|
|||||||
pathe@0.2.0:
|
pathe@0.2.0:
|
||||||
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
|
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
|
||||||
|
|
||||||
|
pathe@2.0.3:
|
||||||
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@@ -2632,6 +2690,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
picomatch@4.0.5:
|
||||||
|
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
pify@2.3.0:
|
pify@2.3.0:
|
||||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2649,6 +2711,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||||
engines: {node: '>= 6'}
|
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:
|
posix-character-classes@0.1.1:
|
||||||
resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
|
resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2750,6 +2818,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
quansync@0.2.11:
|
||||||
|
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||||
|
|
||||||
query-string@4.3.4:
|
query-string@4.3.4:
|
||||||
resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
|
resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2772,6 +2843,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||||
engines: {node: '>= 14.18.0'}
|
engines: {node: '>= 14.18.0'}
|
||||||
|
|
||||||
|
readdirp@5.0.0:
|
||||||
|
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||||
|
engines: {node: '>= 20.19.0'}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3074,6 +3149,10 @@ packages:
|
|||||||
thenify@3.3.1:
|
thenify@3.3.1:
|
||||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
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:
|
to-object-path@0.3.0:
|
||||||
resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
|
resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3148,9 +3227,19 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
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:
|
uc.micro@2.1.0:
|
||||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||||
|
|
||||||
|
ufo@1.6.4:
|
||||||
|
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
|
||||||
|
|
||||||
unbox-primitive@1.1.0:
|
unbox-primitive@1.1.0:
|
||||||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3170,6 +3259,53 @@ packages:
|
|||||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||||
engines: {node: '>= 0.8'}
|
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:
|
unset-value@1.0.0:
|
||||||
resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
|
resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3207,6 +3343,11 @@ packages:
|
|||||||
v8-compile-cache@2.4.0:
|
v8-compile-cache@2.4.0:
|
||||||
resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==}
|
resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==}
|
||||||
|
|
||||||
|
vant@4.9.22:
|
||||||
|
resolution: {integrity: sha512-P2PDSj3oB6l3W1OpVlQpapeLmI6bXMSvPqPdrw5rutslC0Y6tSkrVB/iSD57weD7K92GsjGkvgDK0eZlOsXGqw==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.0
|
||||||
|
|
||||||
vary@1.1.2:
|
vary@1.1.2:
|
||||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -3339,6 +3480,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
webpack-virtual-modules@0.6.2:
|
||||||
|
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||||
|
|
||||||
whatwg-mimetype@3.0.0:
|
whatwg-mimetype@3.0.0:
|
||||||
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -3822,12 +3966,19 @@ snapshots:
|
|||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@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/resolve-uri@3.1.2': {}
|
||||||
|
|
||||||
'@jridgewell/set-array@1.2.1': {}
|
'@jridgewell/set-array@1.2.1': {}
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.0': {}
|
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
@@ -4376,6 +4527,14 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 5.62.0
|
'@typescript-eslint/types': 5.62.0
|
||||||
eslint-visitor-keys: 3.4.3
|
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': {}
|
'@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))':
|
'@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))':
|
||||||
@@ -4481,6 +4640,8 @@ snapshots:
|
|||||||
|
|
||||||
'@vue/shared@3.5.13': {}
|
'@vue/shared@3.5.13': {}
|
||||||
|
|
||||||
|
'@vue/shared@3.5.28': {}
|
||||||
|
|
||||||
'@vueuse/core@13.1.0(vue@3.5.12(typescript@5.8.3))':
|
'@vueuse/core@13.1.0(vue@3.5.12(typescript@5.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/web-bluetooth': 0.0.21
|
'@types/web-bluetooth': 0.0.21
|
||||||
@@ -4517,13 +4678,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
acorn: 7.4.1
|
acorn: 7.4.1
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.14.1):
|
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
|
|
||||||
acorn@7.4.1: {}
|
acorn@7.4.1: {}
|
||||||
|
|
||||||
acorn@8.14.1: {}
|
acorn@8.15.0: {}
|
||||||
|
|
||||||
|
acorn@8.18.0: {}
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4802,6 +4965,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 4.1.2
|
readdirp: 4.1.2
|
||||||
|
|
||||||
|
chokidar@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
readdirp: 5.0.0
|
||||||
|
|
||||||
class-utils@0.3.6:
|
class-utils@0.3.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
arr-union: 3.1.0
|
arr-union: 3.1.0
|
||||||
@@ -4861,6 +5028,10 @@ snapshots:
|
|||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
|
confbox@0.1.8: {}
|
||||||
|
|
||||||
|
confbox@0.2.4: {}
|
||||||
|
|
||||||
connect@3.7.0:
|
connect@3.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 2.6.9
|
debug: 2.6.9
|
||||||
@@ -4989,6 +5160,8 @@ snapshots:
|
|||||||
|
|
||||||
delayed-stream@1.0.0: {}
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
|
detect-europe-js@0.1.2: {}
|
||||||
|
|
||||||
detect-libc@1.0.3:
|
detect-libc@1.0.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -5384,8 +5557,8 @@ snapshots:
|
|||||||
|
|
||||||
espree@9.6.1:
|
espree@9.6.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
|
|
||||||
esprima@4.0.1: {}
|
esprima@4.0.1: {}
|
||||||
@@ -5420,6 +5593,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
exsolve@1.1.1: {}
|
||||||
|
|
||||||
extend-shallow@2.0.1:
|
extend-shallow@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extendable: 0.1.1
|
is-extendable: 0.1.1
|
||||||
@@ -5462,6 +5637,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
|
|
||||||
|
fdir@6.5.0(picomatch@4.0.5):
|
||||||
|
optionalDependencies:
|
||||||
|
picomatch: 4.0.5
|
||||||
|
|
||||||
file-entry-cache@6.0.1:
|
file-entry-cache@6.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 3.2.0
|
flat-cache: 3.2.0
|
||||||
@@ -5847,6 +6026,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
|
||||||
|
is-standalone-pwa@0.1.1: {}
|
||||||
|
|
||||||
is-string@1.1.1:
|
is-string@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -5961,6 +6142,12 @@ snapshots:
|
|||||||
emojis-list: 3.0.0
|
emojis-list: 3.0.0
|
||||||
json5: 1.0.2
|
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-es@4.17.21: {}
|
||||||
|
|
||||||
lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
|
lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
|
||||||
@@ -5985,6 +6172,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
map-cache@0.2.2: {}
|
map-cache@0.2.2: {}
|
||||||
|
|
||||||
map-visit@1.0.0:
|
map-visit@1.0.0:
|
||||||
@@ -6087,6 +6278,13 @@ snapshots:
|
|||||||
for-in: 1.0.2
|
for-in: 1.0.2
|
||||||
is-extendable: 1.0.1
|
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:
|
mockjs@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
commander: 13.1.0
|
commander: 13.1.0
|
||||||
@@ -6191,6 +6389,8 @@ snapshots:
|
|||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
obug@2.1.4: {}
|
||||||
|
|
||||||
on-finished@2.3.0:
|
on-finished@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ee-first: 1.1.1
|
ee-first: 1.1.1
|
||||||
@@ -6243,10 +6443,14 @@ snapshots:
|
|||||||
|
|
||||||
pathe@0.2.0: {}
|
pathe@0.2.0: {}
|
||||||
|
|
||||||
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
|
|
||||||
|
picomatch@4.0.5: {}
|
||||||
|
|
||||||
pify@2.3.0: {}
|
pify@2.3.0: {}
|
||||||
|
|
||||||
pinia@2.3.1(typescript@5.8.3)(vue@3.5.12(typescript@5.8.3)):
|
pinia@2.3.1(typescript@5.8.3)(vue@3.5.12(typescript@5.8.3)):
|
||||||
@@ -6261,6 +6465,18 @@ snapshots:
|
|||||||
|
|
||||||
pirates@4.0.7: {}
|
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: {}
|
posix-character-classes@0.1.1: {}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0: {}
|
possible-typed-array-names@1.1.0: {}
|
||||||
@@ -6352,6 +6568,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
|
quansync@0.2.11: {}
|
||||||
|
|
||||||
query-string@4.3.4:
|
query-string@4.3.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
@@ -6375,6 +6593,8 @@ snapshots:
|
|||||||
|
|
||||||
readdirp@4.1.2: {}
|
readdirp@4.1.2: {}
|
||||||
|
|
||||||
|
readdirp@5.0.0: {}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@@ -6780,6 +7000,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
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:
|
to-object-path@0.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
kind-of: 3.2.2
|
kind-of: 3.2.2
|
||||||
@@ -6878,8 +7103,18 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.8.3: {}
|
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: {}
|
uc.micro@2.1.0: {}
|
||||||
|
|
||||||
|
ufo@1.6.4: {}
|
||||||
|
|
||||||
unbox-primitive@1.1.0:
|
unbox-primitive@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -6900,6 +7135,44 @@ snapshots:
|
|||||||
|
|
||||||
unpipe@1.0.0: {}
|
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:
|
unset-value@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-value: 0.3.1
|
has-value: 0.3.1
|
||||||
@@ -6936,6 +7209,13 @@ snapshots:
|
|||||||
|
|
||||||
v8-compile-cache@2.4.0: {}
|
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: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
vfonts@0.0.3: {}
|
vfonts@0.0.3: {}
|
||||||
@@ -7072,6 +7352,8 @@ snapshots:
|
|||||||
|
|
||||||
webidl-conversions@7.0.0: {}
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
|
webpack-virtual-modules@0.6.2: {}
|
||||||
|
|
||||||
whatwg-mimetype@3.0.0: {}
|
whatwg-mimetype@3.0.0: {}
|
||||||
|
|
||||||
which-boxed-primitive@1.1.1:
|
which-boxed-primitive@1.1.1:
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
35
src/apis/award.ts
Normal file
35
src/apis/award.ts
Normal 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
|
||||||
|
})
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
IBillCategory,
|
IBillCategory,
|
||||||
IBillPay,
|
IBillPay,
|
||||||
IBillQuery,
|
IBillQuery,
|
||||||
IBillRecord
|
IBillRecord, IBillSearch
|
||||||
} from '@/types/bill'
|
} from '@/types/bill'
|
||||||
import { IStatsChart } from 'vue3-common/dist/types'
|
import { IStatsChart } from 'vue3-common/dist/types'
|
||||||
|
|
||||||
@@ -41,6 +41,13 @@ export const queryBillSummaryApi = (query: IBillQuery): Promise<IBillRecord[]> =
|
|||||||
params: query
|
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[]> =>
|
export const queryBillStatsApi = (type: string, query: IBillQuery): Promise<IStatsChart[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/home-api/bill/stats/${type}`,
|
url: `/home-api/bill/stats/${type}`,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cloudService } from './index'
|
import { cloudService } from './index'
|
||||||
import { IBlog, IBlogCategory, IBlogComment, IBlogStats, INewBlog, IQueryBlog } from '@/types/blog'
|
import { IBlog, IBlogCategory, IBlogComment, IBlogStats, IQueryBlog } from '@/types/blog'
|
||||||
|
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import { IPageRecord } from '@/types'
|
import { IPageRecord } from '@/types'
|
||||||
@@ -23,7 +23,7 @@ export const queryBlogByConditionApi = (query: IQueryBlog): Promise<IBlog[]> =>
|
|||||||
|
|
||||||
export const queryBlogByIdApi = (id: number): Promise<IBlog> =>
|
export const queryBlogByIdApi = (id: number): Promise<IBlog> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/blog-api/blog/content/${id}`,
|
url: `/blog-api/blog/${id}/content`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,33 +47,6 @@ export const queryLatestBlogApi = (): Promise<IBlog[]> =>
|
|||||||
|
|
||||||
export const queryBlogCommentApi = (blogId: number): Promise<IBlogComment[]> =>
|
export const queryBlogCommentApi = (blogId: number): Promise<IBlogComment[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/blog-api/blog/comment/${blogId}`,
|
url: `/blog-api/blog/${blogId}/comment`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addBlogCommentApi = (blogComment: IBlogComment): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/blog-api/blog/comment',
|
|
||||||
method: 'put',
|
|
||||||
data: blogComment
|
|
||||||
})
|
|
||||||
|
|
||||||
export const addBlogApi = (blog: INewBlog): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/admin-api/blog-manage',
|
|
||||||
method: 'post',
|
|
||||||
data: blog
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateBlogApi = (id: number, blog: INewBlog): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/admin-api/blog-manage/${id}`,
|
|
||||||
method: 'put',
|
|
||||||
data: blog
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteBlogApi = (id: number): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/admin-api/blog-manage/${id}`,
|
|
||||||
method: 'DELETE'
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import { cloudService } from './index'
|
|
||||||
import { IBudgetInfo } from '@/types/budget'
|
|
||||||
|
|
||||||
export const addBudgetApi = (budget: IBudgetInfo): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/budget',
|
|
||||||
method: 'post',
|
|
||||||
data: budget
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateBudgetApi = (id: number, budget: IBudgetInfo): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/home-api/budget/${id}`,
|
|
||||||
method: 'put',
|
|
||||||
data: budget
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteBudgetApi = (id: number): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/home-api/budget/${id}`,
|
|
||||||
method: 'DELETE'
|
|
||||||
})
|
|
||||||
|
|
||||||
export const queryBudgetApi = (category: string): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/budget',
|
|
||||||
method: 'get',
|
|
||||||
params: { category }
|
|
||||||
})
|
|
||||||
|
|
||||||
export const queryBudgetCategory = (): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/budget/category',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
|
|
||||||
export const queryBudgetRankStats = (category: string): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/budget/stats/rank',
|
|
||||||
method: 'get',
|
|
||||||
params: { category }
|
|
||||||
})
|
|
||||||
|
|
||||||
export const queryBudgetProjectStats = (category: string): Promise<any> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/budget/stats/project',
|
|
||||||
method: 'get',
|
|
||||||
params: { category }
|
|
||||||
})
|
|
||||||
29
src/apis/car.ts
Normal file
29
src/apis/car.ts
Normal 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
|
||||||
|
})
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cloudService } from './index'
|
import { cloudService } from './index'
|
||||||
import { IFileInfo } from '@/types/file'
|
import { IFileInfo } from '@/types/file'
|
||||||
|
import type { AxiosProgressEvent } from 'axios'
|
||||||
|
|
||||||
export const uploadFileApi = (form: FormData, path: string, md5: string): Promise<any> =>
|
export const uploadFileApi = (form: FormData, path: string, md5: string): Promise<any> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
@@ -12,6 +13,19 @@ export const uploadFileApi = (form: FormData, path: string, md5: string): Promis
|
|||||||
data: form
|
data: form
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const uploadAListFileApi = (form: FormData,
|
||||||
|
progress: (event: AxiosProgressEvent) => void): Promise<string> =>
|
||||||
|
cloudService({
|
||||||
|
url: '/home-api/file/alist',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
onUploadProgress: progress,
|
||||||
|
timeout: 0,
|
||||||
|
method: 'post',
|
||||||
|
data: form
|
||||||
|
})
|
||||||
|
|
||||||
export const getFolderInfoApi = (folderName: string): Promise<IFileInfo[]> =>
|
export const getFolderInfoApi = (folderName: string): Promise<IFileInfo[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/home-api/file/catalog',
|
url: '/home-api/file/catalog',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cloudService } from './index'
|
import { cloudService } from './index'
|
||||||
import { IFoodQuery, IFoodRecipe, IFoodRecord, IFoodStats } from '@/types/food'
|
import { IFoodQuery, IFoodRecipe, IFoodRecord, IFoodStats } from '@/types/food'
|
||||||
|
import { IPageRecord } from '@/types'
|
||||||
|
|
||||||
export const addFoodRecipeApi = (foodRecipe: IFoodRecipe): Promise<boolean> =>
|
export const addFoodRecipeApi = (foodRecipe: IFoodRecipe): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
@@ -21,20 +22,27 @@ export const deleteFoodRecipeApi = (id: number): Promise<boolean> =>
|
|||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryFoodRecipeByIdApi = (id: number): Promise<any> =>
|
export const queryFoodRecipeByIdApi = (id: number): Promise<IFoodRecipe> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/home-api/food/recipe/${id}`,
|
url: `/home-api/food/recipe/${id}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryFoodRecipeApi = (currentPage: number, pageSize: number,
|
export const queryFoodRecipeApi = (currentPage: number, pageSize: number,
|
||||||
query: IFoodQuery): Promise<any> =>
|
query: IFoodQuery): Promise<IPageRecord<IFoodRecipe>> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/home-api/food/recipe/page',
|
url: '/home-api/food/recipe/page',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { currentPage, pageSize, ...query }
|
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[]> =>
|
export const queryFoodNameListApi = (): Promise<string[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/home-api/food/recipe/name',
|
url: '/home-api/food/recipe/name',
|
||||||
|
|||||||
16
src/apis/health.ts
Normal file
16
src/apis/health.ts
Normal 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
|
||||||
|
})
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cloudService } from './index'
|
import { cloudService } from './index'
|
||||||
import { IPassword, IQueryPassword } from '@/types/password'
|
import { IPassword, IPasswordQuery } from '@/types/password'
|
||||||
|
|
||||||
export const addPasswordApi = (password: IPassword): Promise<boolean> =>
|
export const addPasswordApi = (password: IPassword): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
@@ -21,7 +21,7 @@ export const deletePasswordApi = (id: number): Promise<boolean> =>
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryPasswordApi = (query: IQueryPassword): Promise<IPassword[]> =>
|
export const queryPasswordApi = (query: IPasswordQuery): Promise<IPassword[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/home-api/password',
|
url: '/home-api/password',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cloudService } from './index'
|
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> =>
|
export const updatePeriodDayApi = (id: number, periodDay: IPeriodDay): Promise<boolean> =>
|
||||||
@@ -16,6 +16,12 @@ export const queryPeriodDayApi = (month: string): Promise<IPeriodDay[]> =>
|
|||||||
params: { month }
|
params: { month }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getMenstrualDaysApi = (): Promise<IMenstrualPeriod[]> =>
|
||||||
|
cloudService({
|
||||||
|
url: '/home-api/period//menstruate/days',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
|
||||||
export const getMenstruateFirstDateApi = (month: string): Promise<string> =>
|
export const getMenstruateFirstDateApi = (month: string): Promise<string> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/home-api/period/menstruate',
|
url: '/home-api/period/menstruate',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cloudService } from './index'
|
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> =>
|
export const addProductApi = (product: IProduct): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
@@ -27,7 +27,7 @@ export const queryProductApi = (id: number): Promise<IProduct> =>
|
|||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryProductListApi = (query: IQueryProduct): Promise<IProduct[]> =>
|
export const queryProductListApi = (query: IProductQuery): Promise<IProduct[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/home-api/product',
|
url: '/home-api/product',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isMobile()" class="mobile-common-query card-item">
|
<div class="mobile-common-query card-item">
|
||||||
<div class="query-item">
|
<div class="query-item">
|
||||||
<span>分类:</span>
|
<span>分类:</span>
|
||||||
<el-segmented v-model="anniversaryStore.queryInfo.category"
|
<el-segmented v-model="anniversaryStore.queryInfo.category"
|
||||||
@@ -7,27 +7,11 @@
|
|||||||
@change="anniversaryStore.queryAnniversaryList()"/>
|
@change="anniversaryStore.queryAnniversaryList()"/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useAnniversaryStore } from '@/store/anniversary.ts'
|
import { useAnniversaryStore } from '@/store/anniversary.ts'
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { ICategory } from '@/types'
|
import { ICategory } from '@/types'
|
||||||
|
|
||||||
const anniversaryStore = useAnniversaryStore()
|
const anniversaryStore = useAnniversaryStore()
|
||||||
@@ -39,11 +23,6 @@ onMounted(async () => {
|
|||||||
initCategoryList()
|
initCategoryList()
|
||||||
})
|
})
|
||||||
|
|
||||||
const refreshClick = async () => {
|
|
||||||
anniversaryStore.queryInfo.category = ''
|
|
||||||
await anniversaryStore.queryAnniversaryList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const initCategoryList = () => {
|
const initCategoryList = () => {
|
||||||
queryCategoryList.value = []
|
queryCategoryList.value = []
|
||||||
queryCategoryList.value.push({
|
queryCategoryList.value.push({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { defineProps, PropType } from 'vue'
|
|||||||
import { IAnniversaryCategory } from '@/types/anniversary.ts'
|
import { IAnniversaryCategory } from '@/types/anniversary.ts'
|
||||||
import { useAnniversaryStore } from '@/store/anniversary.ts'
|
import { useAnniversaryStore } from '@/store/anniversary.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
categoryItem: {
|
categoryItem: {
|
||||||
@@ -28,8 +29,12 @@ const router = useRouter()
|
|||||||
const anniversaryStore = useAnniversaryStore()
|
const anniversaryStore = useAnniversaryStore()
|
||||||
|
|
||||||
const selectCategoryClick = () => {
|
const selectCategoryClick = () => {
|
||||||
anniversaryStore.queryInfo.category = props.categoryItem?.name as string
|
if (props.count) {
|
||||||
router.push({ name: 'AnniversaryList' })
|
anniversaryStore.queryInfo.category = props.categoryItem?.name as string
|
||||||
|
router.push({ name: 'AnniversaryList' })
|
||||||
|
} else {
|
||||||
|
ElMessage.info('暂无纪念日')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isMobile()" class="mobile-common-query card-item">
|
<div class="mobile-common-query card-item">
|
||||||
<div class="query-item">
|
<div class="query-item">
|
||||||
<span>分类:</span>
|
<span>分类:</span>
|
||||||
<el-segmented v-model="assetStore.queryInfo.category"
|
<el-segmented v-model="assetStore.queryInfo.category"
|
||||||
@@ -7,27 +7,11 @@
|
|||||||
@change="assetStore.refreshInfo()"/>
|
@change="assetStore.refreshInfo()"/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useAssetStore } from '@/store/asset.ts'
|
import { useAssetStore } from '@/store/asset.ts'
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { ICategory } from '@/types'
|
import { ICategory } from '@/types'
|
||||||
|
|
||||||
const assetStore = useAssetStore()
|
const assetStore = useAssetStore()
|
||||||
@@ -39,11 +23,6 @@ onMounted(async () => {
|
|||||||
initCategoryList()
|
initCategoryList()
|
||||||
})
|
})
|
||||||
|
|
||||||
const refreshClick = async () => {
|
|
||||||
assetStore.queryInfo.category = ''
|
|
||||||
await assetStore.queryAssetList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const initCategoryList = () => {
|
const initCategoryList = () => {
|
||||||
queryCategoryList.value = []
|
queryCategoryList.value = []
|
||||||
queryCategoryList.value.push({
|
queryCategoryList.value.push({
|
||||||
|
|||||||
97
src/components/Award/AwardCard.vue
Normal file
97
src/components/Award/AwardCard.vue
Normal 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>
|
||||||
37
src/components/Award/AwardQuery.vue
Normal file
37
src/components/Award/AwardQuery.vue
Normal 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>
|
||||||
@@ -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.ts'
|
|
||||||
import { ElMessage, FormRules } from 'element-plus'
|
|
||||||
import { IBillBook } from '@/types/bill.ts'
|
|
||||||
import { billBookRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
|
||||||
import { addBillBookApi, updateBillBookApi } from '@/apis/bill.ts'
|
|
||||||
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>
|
|
||||||
@@ -67,7 +67,7 @@ const getCalendarAttrDates = () => {
|
|||||||
})
|
})
|
||||||
dailyInfo.attrs.push({
|
dailyInfo.attrs.push({
|
||||||
dot: 'red',
|
dot: 'red',
|
||||||
dates: getCalendarDateList(billStore.billRecordList, 'expensive')
|
dates: getCalendarDateList(billStore.billRecordList, 'expense')
|
||||||
})
|
})
|
||||||
dailyInfo.attrs.push({
|
dailyInfo.attrs.push({
|
||||||
dot: 'green',
|
dot: 'green',
|
||||||
|
|||||||
@@ -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.ts'
|
|
||||||
import { IBillBook, IBillCategory } from '@/types/bill.ts'
|
|
||||||
import { ElMessage, FormRules } from 'element-plus'
|
|
||||||
import { billCategoryRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { addBillCategoryApi, updateBillCategoryApi } from '@/apis/bill.ts'
|
|
||||||
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>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bill-chart"
|
<div class="bill-chart common-chart"
|
||||||
v-loading="billStore.isLoading"
|
v-loading="billStore.isLoading"
|
||||||
element-loading-text="正在加载中...">
|
element-loading-text="正在加载中...">
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
@@ -8,19 +8,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="billDailyChartRef"/>
|
<div class="chart" ref="billDailyChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊类别统计</span>
|
<span class="title">📊类别统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="billCategoryChartRef"/>
|
<div class="chart" ref="billCategoryChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊账本统计</span>
|
<span class="title">📊账本统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="billBookChartRef"/>
|
<div class="chart" ref="billBookChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isMobile()" class="chart-container card-item">
|
|
||||||
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊排行榜</span>
|
<span class="title">📊排行榜</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,12 +31,6 @@
|
|||||||
<bill-rank />
|
<bill-rank />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -44,8 +41,7 @@ import * as echarts from 'echarts'
|
|||||||
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
||||||
import { useBillStore } from '@/store/bill.ts'
|
import { useBillStore } from '@/store/bill.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import type { IStatsChart } from 'vue3-common/types'
|
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
const billStore = useBillStore()
|
const billStore = useBillStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
@@ -53,12 +49,10 @@ const appStore = useAppStore()
|
|||||||
const billDailyChartRef = ref<HTMLElement>()
|
const billDailyChartRef = ref<HTMLElement>()
|
||||||
const billBookChartRef = ref<HTMLElement>()
|
const billBookChartRef = ref<HTMLElement>()
|
||||||
const billCategoryChartRef = ref<HTMLElement>()
|
const billCategoryChartRef = ref<HTMLElement>()
|
||||||
const billRankChartRef = ref<HTMLElement>()
|
|
||||||
|
|
||||||
let billDailyChart: echarts.ECharts
|
let billDailyChart: echarts.ECharts
|
||||||
let billBookChart: echarts.ECharts
|
let billBookChart: echarts.ECharts
|
||||||
let billCategoryChart: echarts.ECharts
|
let billCategoryChart: echarts.ECharts
|
||||||
let billRankChart: echarts.ECharts
|
|
||||||
|
|
||||||
watch(() => billStore.isRefresh, () => {
|
watch(() => billStore.isRefresh, () => {
|
||||||
updateChart()
|
updateChart()
|
||||||
@@ -68,24 +62,21 @@ onMounted(() => {
|
|||||||
billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||||
billBookChart = echarts.init(billBookChartRef.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)
|
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 updateChart = () => {
|
||||||
const MAX_CATEGORY_COUNT = 5
|
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)
|
const dailyYAxis = billStore.billDateStatsList.map((item) => item.value)
|
||||||
billDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '金额(元)', dailyYAxis))
|
billDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '金额(元)', dailyYAxis))
|
||||||
billDailyChart.setOption({
|
billDailyChart.setOption({
|
||||||
series: [{
|
series: [{
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: billStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
|
color: billStore.billType === 'expense' ? '#CA6C65' : '#6FBB69'
|
||||||
},
|
},
|
||||||
markPoint: {
|
markPoint: {
|
||||||
label: {
|
label: {
|
||||||
@@ -105,7 +96,7 @@ const updateChart = () => {
|
|||||||
billBookChart.setOption({
|
billBookChart.setOption({
|
||||||
series: [{
|
series: [{
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: billStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
|
color: billStore.billType === 'expense' ? '#CA6C65' : '#6FBB69'
|
||||||
},
|
},
|
||||||
markPoint: {
|
markPoint: {
|
||||||
label: {
|
label: {
|
||||||
@@ -119,29 +110,13 @@ const updateChart = () => {
|
|||||||
const billCategoryList = billStore.billCategoryStatsList.slice(0, MAX_CATEGORY_COUNT)
|
const billCategoryList = billStore.billCategoryStatsList.slice(0, MAX_CATEGORY_COUNT)
|
||||||
billCategoryChart.setOption(pieChartOptions('rank', '元', billCategoryList))
|
billCategoryChart.setOption(pieChartOptions('rank', '元', billCategoryList))
|
||||||
|
|
||||||
if (isMobile()) {
|
billCategoryChart.setOption({
|
||||||
billCategoryChart.setOption({
|
series: [{
|
||||||
series: [{
|
radius: ['20%', '50%'],
|
||||||
radius: ['20%', '50%'],
|
label: {
|
||||||
label: {
|
formatter: '{b}: {d}%'
|
||||||
formatter: '{b}: {d}%'
|
}
|
||||||
}
|
}]
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 排行榜统计图
|
|
||||||
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>
|
</script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<daily-summary :date="dailyItemInfo.date">
|
<daily-summary :date="dailyItemInfo.date">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<span>收入:{{ formatAmount(dailyItemInfo.totalIncome, true) }}</span>
|
<span>收入:{{ formatAmount(dailyItemInfo.totalIncome, true) }}</span>
|
||||||
<span>支出:{{ formatAmount(dailyItemInfo.totalExpensive, true) }}</span>
|
<span>支出:{{ formatAmount(dailyItemInfo.totalExpense, true) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</daily-summary>
|
</daily-summary>
|
||||||
|
|
||||||
@@ -40,16 +40,12 @@ import DailySummary from '@/components/DailySummary.vue'
|
|||||||
import DailyCard from '@/components/DailyCard.vue'
|
import DailyCard from '@/components/DailyCard.vue'
|
||||||
import { defineProps, onMounted, reactive, watch } from 'vue'
|
import { defineProps, onMounted, reactive, watch } from 'vue'
|
||||||
import { useBillStore } from '@/store/bill.ts'
|
import { useBillStore } from '@/store/bill.ts'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { IBillRecord } from '@/types/bill.ts'
|
import { IBillRecord } from '@/types/bill.ts'
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||||
import { getCalendarDailyCount, getCategoryByName } from '@/utils/home/billUtil.ts'
|
import { getCalendarDailyCount, getCategoryByName } from '@/utils/home/billUtil.ts'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
const billStore = useBillStore()
|
const billStore = useBillStore()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
date: {
|
date: {
|
||||||
@@ -72,7 +68,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
const dailyItemInfo = reactive({
|
const dailyItemInfo = reactive({
|
||||||
date: '',
|
date: '',
|
||||||
totalExpensive: 0,
|
totalExpense: 0,
|
||||||
totalIncome: 0,
|
totalIncome: 0,
|
||||||
itemList: [] as IBillRecord[]
|
itemList: [] as IBillRecord[]
|
||||||
})
|
})
|
||||||
@@ -88,7 +84,7 @@ const getBillDailyByDate = (date: string) => {
|
|||||||
const calendarDaily = getCalendarDailyCount(billStore.billRecordList, date)
|
const calendarDaily = getCalendarDailyCount(billStore.billRecordList, date)
|
||||||
dailyItemInfo.itemList = calendarDaily.dailyRecordList
|
dailyItemInfo.itemList = calendarDaily.dailyRecordList
|
||||||
dailyItemInfo.totalIncome = calendarDaily.totalIncome
|
dailyItemInfo.totalIncome = calendarDaily.totalIncome
|
||||||
dailyItemInfo.totalExpensive = calendarDaily.totalExpensive
|
dailyItemInfo.totalExpense = calendarDaily.totalExpense
|
||||||
} else {
|
} else {
|
||||||
dailyItemInfo.itemList = []
|
dailyItemInfo.itemList = []
|
||||||
}
|
}
|
||||||
@@ -101,19 +97,7 @@ const getBillDailyByDate = (date: string) => {
|
|||||||
const selectBillRecordClick = async (billRecord: IBillRecord) => {
|
const selectBillRecordClick = async (billRecord: IBillRecord) => {
|
||||||
await billStore.queryBillRecord(billRecord.id as number)
|
await billStore.queryBillRecord(billRecord.id as number)
|
||||||
billStore.billApiType = 'UPDATE'
|
billStore.billApiType = 'UPDATE'
|
||||||
|
billStore.billDrawer = true
|
||||||
if (isMobile()) {
|
|
||||||
await router.push({
|
|
||||||
name: 'BillDetailId',
|
|
||||||
params: { id: billStore.currentBillRecord.id }
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
billStore.billDialog = {
|
|
||||||
title: '编辑账单',
|
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(billStore.currentBillRecord)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -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.ts'
|
|
||||||
import { Location, Document } from '@element-plus/icons-vue'
|
|
||||||
import { FormRules } from 'element-plus'
|
|
||||||
import { billRecordRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { nextTick, reactive, ref } from 'vue'
|
|
||||||
import { IBillCategory } from '@/types/bill.ts'
|
|
||||||
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>
|
|
||||||
263
src/components/Bill/BillDrawer.vue
Normal file
263
src/components/Bill/BillDrawer.vue
Normal 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>
|
||||||
@@ -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.ts'
|
|
||||||
import { ElMessage, FormRules } from 'element-plus'
|
|
||||||
import { IBillPay } from '@/types/bill.ts'
|
|
||||||
import { billPayRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { addBillPayApi, updateBillPayApi } from '@/apis/bill.ts'
|
|
||||||
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>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="common-query">
|
<div class="common-query">
|
||||||
<el-select v-model="billStore.billQueryForm.billBook"
|
<el-select v-model="billStore.billQueryForm.billBook"
|
||||||
@change="billStore.refreshInfo()" style="width: 120px">
|
@change="billStore.refreshInfo(isStats)">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="(item, index) in billQuery.billBookList"
|
v-for="(item, index) in billQuery.billBookList"
|
||||||
:key="index" :label="item.label" :value="item.name"
|
:key="index" :label="item.label" :value="item.name"
|
||||||
@@ -9,34 +9,32 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<el-select v-model="billStore.billDateType"
|
<el-select v-model="billStore.billDateType"
|
||||||
@change="billStore.refreshInfo()"
|
@change="billStore.refreshInfo(isStats)">
|
||||||
style="width: 120px;align-self: center;">
|
|
||||||
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
|
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
|
||||||
:label="item.label" :value="item.value"/>
|
:label="item.label" :value="item.value"/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<el-switch
|
<el-select v-if="isStats" v-model="billStore.billType"
|
||||||
v-model="billStore.billType"
|
@change="billStore.refreshInfo(isStats)">
|
||||||
inline-prompt
|
<el-option v-for="(item, index) in billTypeOptions" :key="index"
|
||||||
active-value="income" inactive-value="expensive"
|
:label="item.label" :value="item.value"/>
|
||||||
active-text="收入账单" inactive-text="支出账单"
|
</el-select>
|
||||||
@change="billStore.refreshInfo()"
|
|
||||||
style="--el-switch-on-color: #6FBB69; --el-switch-off-color: #CA6C65;"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, reactive } from 'vue'
|
import { computed, onMounted, reactive } from 'vue'
|
||||||
import { useBillStore } from '@/store/bill.ts'
|
import { useBillStore } from '@/store/bill.ts'
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||||
import { IBillBook } from '@/types/bill.ts'
|
import { IBillBook } from '@/types/bill.ts'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const billStore = useBillStore()
|
const billStore = useBillStore()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isStats = computed(() => route.path.includes('chart'))
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await billStore.initBillInfo()
|
|
||||||
|
|
||||||
billQuery.billBookList = deepCopyObject(billStore.billBookList)
|
billQuery.billBookList = deepCopyObject(billStore.billBookList)
|
||||||
billQuery.billBookList.push({
|
billQuery.billBookList.push({
|
||||||
color: '',
|
color: '',
|
||||||
@@ -56,6 +54,15 @@ const dateTypeOptions = [{
|
|||||||
value: 'year'
|
value: 'year'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
const billTypeOptions = [{
|
||||||
|
label: '收入',
|
||||||
|
value: 'income'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '支出',
|
||||||
|
value: 'expense'
|
||||||
|
}]
|
||||||
|
|
||||||
const billQuery = reactive({
|
const billQuery = reactive({
|
||||||
query: '',
|
query: '',
|
||||||
billBookList: [] as IBillBook[]
|
billBookList: [] as IBillBook[]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
:key="index" class="rank-item">
|
:key="index" class="rank-item">
|
||||||
<span class="rank-index">{{ index+1 }}.</span>
|
<span class="rank-index">{{ index+1 }}.</span>
|
||||||
<span class="rank-content">{{ item.content }}</span>
|
<span class="rank-content">{{ item.content }}</span>
|
||||||
<span class="rank-amount">¥{{ item.amount }}</span>
|
<span class="rank-amount">¥{{ formatAmount(item.amount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { useBillStore } from '@/store/bill.ts'
|
import { useBillStore } from '@/store/bill.ts'
|
||||||
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
|
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||||
|
|
||||||
const billStore = useBillStore()
|
const billStore = useBillStore()
|
||||||
|
|
||||||
|
|||||||
34
src/components/Bill/BillSearch.vue
Normal file
34
src/components/Bill/BillSearch.vue
Normal 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>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="common-statistics">
|
<div class="common-statistics">
|
||||||
<stats-card
|
<stats-card
|
||||||
title="当前支出" color="#CA6C65" unit="元" icon="fa-solid fa-sack-xmark"
|
title="当前支出" color="#CA6C65" unit="元" icon="fa-solid fa-sack-xmark"
|
||||||
:value="formatAmount(billStats.expensive)" />
|
:value="formatAmount(billStats.expense)" />
|
||||||
|
|
||||||
<stats-card
|
<stats-card
|
||||||
title="当前收入" color="#6FBB69" unit="元" icon="fa-solid fa-sack-dollar"
|
title="当前收入" color="#6FBB69" unit="元" icon="fa-solid fa-sack-dollar"
|
||||||
@@ -34,7 +34,7 @@ watch(() => billStore.isRefresh, () => {
|
|||||||
|
|
||||||
const billStats = ref<IBillSummaryStats>({
|
const billStats = ref<IBillSummaryStats>({
|
||||||
balance: 0,
|
balance: 0,
|
||||||
expensive: 0,
|
expense: 0,
|
||||||
income: 0,
|
income: 0,
|
||||||
rate: 0
|
rate: 0
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="blog-comment-edit card-item">
|
|
||||||
<div class="comment-title">
|
|
||||||
<el-input v-model="blogComment.name"
|
|
||||||
:maxlength="10" show-word-limit
|
|
||||||
placeholder="请输入昵称"/>
|
|
||||||
<el-input v-model="blogComment.website"
|
|
||||||
:maxlength="50" show-word-limit
|
|
||||||
placeholder="请输入个人博客网站"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-input v-model="blogComment.content"
|
|
||||||
placeholder="有什么想和我说的呢" :maxlength="500"
|
|
||||||
:rows="7" show-word-limit type="textarea"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="comment-button">
|
|
||||||
<el-popover ref="emojiPopoverRef" placement="top" :width="300" trigger="hover">
|
|
||||||
<template #default>
|
|
||||||
<div class="emoji-panel">
|
|
||||||
<EmojiPicker :native="true" @select="onSelectEmoji" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #reference>
|
|
||||||
<el-button circle>
|
|
||||||
<i class="fa-regular fa-face-smile"></i>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
|
|
||||||
<el-button type="primary"
|
|
||||||
:disabled="blogComment.content.length === 0"
|
|
||||||
@click="sendCommentClick"
|
|
||||||
class="send-button">发送</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import EmojiPicker from 'vue3-emoji-picker'
|
|
||||||
import 'vue3-emoji-picker/css'
|
|
||||||
import { defineProps, ref, onMounted } from 'vue'
|
|
||||||
import { useBlogStore } from '@/store/blog.ts'
|
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { IBlogComment } from '@/types/blog.ts'
|
|
||||||
|
|
||||||
const blogStore = useBlogStore()
|
|
||||||
const emojiPopoverRef = ref()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
parentId: {
|
|
||||||
required: true,
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const blogComment = ref<IBlogComment>({
|
|
||||||
id: 0,
|
|
||||||
blogId: 0,
|
|
||||||
name: '',
|
|
||||||
website: '',
|
|
||||||
content: '',
|
|
||||||
ipAddress: '',
|
|
||||||
userAgent: '',
|
|
||||||
parentId: 0,
|
|
||||||
isApproved: true,
|
|
||||||
createTime: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
blogComment.value = deepCopyObject(blogStore.getEmptyBlogComment())
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSelectEmoji = (emoji) => {
|
|
||||||
if (blogComment.value.content.length <= 498) {
|
|
||||||
blogComment.value.content += emoji.i
|
|
||||||
}
|
|
||||||
|
|
||||||
emojiPopoverRef.value.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendCommentClick = async () => {
|
|
||||||
blogComment.value.parentId = props.parentId
|
|
||||||
blogComment.value.blogId = blogStore.currentBlogId
|
|
||||||
|
|
||||||
if (!blogComment.value.name) {
|
|
||||||
blogComment.value.name = 'Anonymous'
|
|
||||||
}
|
|
||||||
|
|
||||||
await blogStore.addBlogComment(blogComment.value)
|
|
||||||
blogComment.value = deepCopyObject(blogStore.getEmptyBlogComment())
|
|
||||||
blogStore.isShowBlogCommentEdit = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.blog-comment-edit {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.comment-title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-button {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.send-button {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="blog-comment-card">
|
|
||||||
<el-avatar :src="`https://api.dicebear.com/6.x/adventurer/svg?seed=${props.comment.name}`"
|
|
||||||
:size="50" alt="未加载"/>
|
|
||||||
|
|
||||||
<div class="blog-comment-content">
|
|
||||||
<div class="basic-info">
|
|
||||||
<span>{{ props.comment.name }}</span>
|
|
||||||
<span class="content">{{ getBrowser(props.comment.userAgent) }}</span>
|
|
||||||
<span class="content">{{ getOsInfo(props.comment.userAgent) }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sub-info">
|
|
||||||
<span class="content">{{ props.comment.createTime }}</span>
|
|
||||||
<el-button v-if="!blogComment.isShowEdit"
|
|
||||||
type="primary" text @click="replyClick">回复</el-button>
|
|
||||||
<el-button v-else type="primary"
|
|
||||||
text @click="cancelClick">取消</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>{{ props.comment.content }}</p>
|
|
||||||
|
|
||||||
<blog-comment-edit v-if="blogComment.isShowEdit"
|
|
||||||
:parent-id="props.comment.id"/>
|
|
||||||
|
|
||||||
<div class="reply-list">
|
|
||||||
<div class="blog-comment-card" v-for="(item, index) in props.replyList" :key="index">
|
|
||||||
<el-avatar :src="`https://api.dicebear.com/6.x/adventurer/svg?seed=${item.name}`"
|
|
||||||
:size="50" alt="未加载"/>
|
|
||||||
|
|
||||||
<div class="blog-comment-content">
|
|
||||||
<div class="basic-info">
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
<span class="content">{{ getBrowser(props.comment.userAgent) }}</span>
|
|
||||||
<span class="content">{{ getOsInfo(props.comment.userAgent) }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sub-info">
|
|
||||||
<span class="content">{{ item.createTime }}</span>
|
|
||||||
</div>
|
|
||||||
<p>{{ item.content }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import BlogCommentEdit from '@/components/Comment/BlogCommentEdit.vue'
|
|
||||||
import 'vue3-emoji-picker/css'
|
|
||||||
import { defineProps, PropType, reactive } from 'vue'
|
|
||||||
import { IBlogComment } from '@/types/blog.ts'
|
|
||||||
import { useBlogStore } from '@/store/blog.ts'
|
|
||||||
import { UAParser } from 'ua-parser-js'
|
|
||||||
|
|
||||||
const blogStore = useBlogStore()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
comment: {
|
|
||||||
required: true,
|
|
||||||
type: Object as PropType<IBlogComment>
|
|
||||||
},
|
|
||||||
replyList: {
|
|
||||||
required: true,
|
|
||||||
type: Object as PropType<IBlogComment[]>
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const blogComment = reactive({
|
|
||||||
isShowEdit: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const replyClick = () => {
|
|
||||||
blogStore.currentBlogComment = blogStore.getEmptyBlogComment()
|
|
||||||
blogComment.isShowEdit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelClick = () => {
|
|
||||||
blogComment.isShowEdit = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOsInfo = (userAgent: string) => {
|
|
||||||
const uaParse = new UAParser(userAgent)
|
|
||||||
return `${uaParse.getOS().name} ${uaParse.getOS().version}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBrowser = (userAgent: string) => {
|
|
||||||
const uaParse = new UAParser(userAgent)
|
|
||||||
return `${uaParse.getBrowser().name} ${uaParse.getBrowser().version}`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.blog-comment-card {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 60px 1fr;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.blog-comment-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.basic-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
font-size: small;
|
|
||||||
color: #666666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
108
src/components/Car/CarCard.vue
Normal file
108
src/components/Car/CarCard.vue
Normal 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>
|
||||||
33
src/components/Car/CarQuery.vue
Normal file
33
src/components/Car/CarQuery.vue
Normal 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>
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-button ref="buttonRef" type="primary" :icon="Plus"
|
|
||||||
circle size="large" :style="style" style="font-size: 1.8rem;"
|
|
||||||
@mousedown="startDrag" @touchstart="startDrag"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, defineEmits, onMounted, reactive, ref } from 'vue'
|
|
||||||
import { Plus } from '@element-plus/icons-vue'
|
|
||||||
|
|
||||||
const buttonRef = ref()
|
|
||||||
|
|
||||||
const buttonPos = reactive({
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const startPos = reactive({
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const offsetPos = reactive({
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 最小拖到阈值
|
|
||||||
*/
|
|
||||||
const dragThreshold = 5
|
|
||||||
|
|
||||||
const emit = defineEmits(['click'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实时更新位置
|
|
||||||
*/
|
|
||||||
const style = computed(() => ({
|
|
||||||
position: 'absolute',
|
|
||||||
left: `${buttonPos.x}px`,
|
|
||||||
top: `${buttonPos.y}px`,
|
|
||||||
touchAction: 'none'
|
|
||||||
}))
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initButtonPosition()
|
|
||||||
})
|
|
||||||
|
|
||||||
const initButtonPosition = () => {
|
|
||||||
buttonPos.x = window.innerWidth - 50
|
|
||||||
buttonPos.y = window.innerHeight - 120
|
|
||||||
buttonPos.width = buttonRef.value.$el.offsetWidth
|
|
||||||
buttonPos.height = buttonRef.value.$el.offsetHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDrag = (event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const touch = event.touches ? event.touches[0] : event
|
|
||||||
|
|
||||||
startPos.x = touch.clientX
|
|
||||||
startPos.y = touch.clientY
|
|
||||||
|
|
||||||
offsetPos.x = touch.clientX - buttonPos.x
|
|
||||||
offsetPos.y = touch.clientY - buttonPos.y
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', onDrag)
|
|
||||||
document.addEventListener('mouseup', stopDrag)
|
|
||||||
document.addEventListener('touchmove', onDrag, { passive: false })
|
|
||||||
document.addEventListener('touchend', stopDrag)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDrag = (event) => {
|
|
||||||
const touch = event.touches ? event.touches[0] : event
|
|
||||||
|
|
||||||
// 计算新位置,并限制在边界内
|
|
||||||
const newX = touch.clientX - offsetPos.x
|
|
||||||
const newY = touch.clientY - offsetPos.y
|
|
||||||
|
|
||||||
buttonPos.x = Math.max(0, Math.min(newX, window.innerWidth - buttonPos.width))
|
|
||||||
buttonPos.y = Math.max(40, Math.min(newY, window.innerHeight - buttonPos.height - 70))
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopDrag = (event) => {
|
|
||||||
document.removeEventListener('mousemove', onDrag)
|
|
||||||
document.removeEventListener('mouseup', stopDrag)
|
|
||||||
document.removeEventListener('touchmove', onDrag)
|
|
||||||
document.removeEventListener('touchend', stopDrag)
|
|
||||||
|
|
||||||
const touch = event.changedTouches ? event.changedTouches[0] : event
|
|
||||||
const deltaX = Math.abs(touch.clientX - startPos.x)
|
|
||||||
const deltaY = Math.abs(touch.clientY - startPos.y)
|
|
||||||
|
|
||||||
// 此时为点击
|
|
||||||
if (deltaX <= dragThreshold && deltaY <= dragThreshold) {
|
|
||||||
emit('click', event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -12,13 +12,22 @@
|
|||||||
{{ appStore.isDark ? '🌙' : '☀️' }}
|
{{ appStore.isDark ? '🌙' : '☀️' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<span>我的</span>
|
||||||
|
<el-button circle :icon="UserFilled" type="primary" @click="viewProfileClick"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { UserFilled } from '@element-plus/icons-vue'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
appStore.themeColor = appStore.getThemeColor()
|
appStore.themeColor = appStore.getThemeColor()
|
||||||
@@ -27,6 +36,11 @@ onMounted(() => {
|
|||||||
const changeThemeColorEvent = (value: string) => {
|
const changeThemeColorEvent = (value: string) => {
|
||||||
appStore.setThemeColor(value)
|
appStore.setThemeColor(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewProfileClick = () => {
|
||||||
|
router.push('/profile/index')
|
||||||
|
appStore.isShowSetting = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -12,9 +12,8 @@
|
|||||||
<el-card class="record-card">
|
<el-card class="record-card">
|
||||||
<span class="record-content">完成人:{{ item.person }}</span>
|
<span class="record-content">完成人:{{ item.person }}</span>
|
||||||
<el-image v-if="item.imageUrl"
|
<el-image v-if="item.imageUrl"
|
||||||
:src="serviceFileRootPath + item.imageUrl"
|
:src="`${item.imageUrl}${ThumbImageQuery}`" fit="cover" alt="暂无图片"
|
||||||
:preview-src-list="[serviceFileRootPath + item.imageUrl]"
|
@click="viewImageClick(item)" class="record-image"/>
|
||||||
fit="contain" alt="暂无图片" class="record-image"/>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-timeline-item>
|
</el-timeline-item>
|
||||||
</el-timeline>
|
</el-timeline>
|
||||||
@@ -25,8 +24,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
import { ThumbImageQuery } from '@/utils'
|
||||||
import { SvgIcon } from 'vue3-common'
|
import { SvgIcon } from 'vue3-common'
|
||||||
|
import { IFoodRecord } from '@/types/food.ts'
|
||||||
|
import { showImagePreview } from 'vant'
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|
||||||
|
const viewImageClick = (item: IFoodRecord) => {
|
||||||
|
showImagePreview({ images: [item.imageUrl], closeable: true })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<div class="step-body">
|
<div class="step-body">
|
||||||
<span class="step-content">{{ item.content }}</span>
|
<span class="step-content">{{ item.content }}</span>
|
||||||
<img v-if="item.imageUrl"
|
<img v-if="item.imageUrl"
|
||||||
:src="serviceFileRootPath + item.imageUrl" alt="暂无图片" class="step-image"/>
|
:src="item.imageUrl" alt="暂无图片" class="step-image"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
|
||||||
import { SvgIcon } from 'vue3-common'
|
import { SvgIcon } from 'vue3-common'
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|||||||
@@ -1,79 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="life-record-card card-item">
|
<div class="life-record-card card-item">
|
||||||
<el-carousel v-if="props.foodItem?.recordList.length > 0"
|
<el-image v-if="props.foodItem?.recordList.length > 0"
|
||||||
arrow="always" @change="changeFoodEvent">
|
:src="`${props.foodItem?.recordList[0].imageUrl}${ThumbImageQuery}`"
|
||||||
<el-carousel-item v-for="(item, index) in getFoodRecord(props.foodItem?.recordList)"
|
fit="contain" alt="暂无成果" @click="viewImageClick">
|
||||||
:key="index">
|
<template #error>
|
||||||
<el-image :src="serviceFileRootPath + item.imageUrl"
|
<div class="image-slot">
|
||||||
fit="contain" alt="暂无成果">
|
<span>暂无图片</span>
|
||||||
<template #error>
|
</div>
|
||||||
<div class="image-slot">
|
</template>
|
||||||
<span>暂无图片</span>
|
</el-image>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-image>
|
|
||||||
</el-carousel-item>
|
|
||||||
</el-carousel>
|
|
||||||
|
|
||||||
<el-empty v-else description="暂无成果" />
|
<el-empty v-else description="暂无成果" />
|
||||||
|
|
||||||
<div class="record-info">
|
<van-swipe-cell>
|
||||||
<div class="top-container">
|
<div class="record-info" @click="viewClick()">
|
||||||
<div class="info-list">
|
<div class="top-container">
|
||||||
<div class="info-item">
|
<div class="info-list">
|
||||||
<span class="title">{{ props.foodItem.name }}</span>
|
<div class="info-item">
|
||||||
|
<span class="title">{{ props.foodItem.name }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-info">
|
<div class="bottom-container">
|
||||||
<el-button v-if="!isMobile()"
|
<div class="info-list">
|
||||||
type="primary" :icon="Edit" circle :size="'small'"
|
<div class="info-item category-item">
|
||||||
@click="editFoodRecipeClick"/>
|
<i class="fa-solid fa-utensils"></i>
|
||||||
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
|
{{ props.foodItem.category }}
|
||||||
@click="viewFoodRecipeClick()" />
|
</div>
|
||||||
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
</div>
|
||||||
@click="deleteFoodRecipeClick()" />
|
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-container">
|
<template #right>
|
||||||
<div class="info-list">
|
<van-button square type="primary" text="编辑" style="height: 100%;"
|
||||||
<div class="info-item category-item">
|
@click="editClick"/>
|
||||||
<i class="fa-solid fa-utensils"></i>
|
<van-button square type="danger" text="删除" style="height: 100%;"
|
||||||
{{ props.foodItem.category }}
|
@click="deleteClick()"/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</van-swipe-cell>
|
||||||
|
|
||||||
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
|
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { defineProps, onMounted, PropType, reactive } from 'vue'
|
import { defineProps, onMounted, PropType, reactive } from 'vue'
|
||||||
import { IFoodRecipe, IFoodRecord } from '@/types/food.ts'
|
import { IFoodRecipe } from '@/types/food.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
import { showImagePreview } from 'vant'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
import { RawImageQuery, ThumbImageQuery } from '@/utils'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
@@ -94,35 +86,28 @@ onMounted(() => {
|
|||||||
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
|
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
|
||||||
})
|
})
|
||||||
|
|
||||||
const getFoodRecord = (recordList: IFoodRecord[]) => {
|
const viewImageClick = () => {
|
||||||
return recordList.filter((item) => item.imageUrl)
|
const imageList = props.foodItem?.recordList.map((item) => `${item.imageUrl}${RawImageQuery}`)
|
||||||
|
showImagePreview({ images: imageList, closeable: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
const editFoodRecipeClick = async () => {
|
const editClick = async () => {
|
||||||
const id = props.foodItem?.id
|
const id = props.foodItem?.id
|
||||||
foodStore.foodRecipeApiType = 'UPDATE'
|
foodStore.foodRecipeApiType = 'UPDATE'
|
||||||
await foodStore.queryFoodRecipe(id as number)
|
await foodStore.queryFoodRecipe(id as number)
|
||||||
foodStore.foodRecipeDialog = {
|
await router.push({ name: 'FoodRecipeForm' })
|
||||||
title: '编辑菜谱',
|
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(foodStore.currentFoodRecipe)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewFoodRecipeClick = async () => {
|
const viewClick = async () => {
|
||||||
await router.push({ name: 'FoodDetailId', params: { id: props.foodItem?.id } })
|
await router.push({ name: 'FoodDetailId', params: { id: props.foodItem?.id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteFoodRecipeClick = () => {
|
const deleteClick = () => {
|
||||||
foodStore.foodRecipeApiType = 'DELETE'
|
foodStore.foodRecipeApiType = 'DELETE'
|
||||||
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
|
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
|
||||||
foodStore.handleFoodRecipeApi(props.foodItem?.id as number)
|
foodStore.handleFoodRecipeApi(props.foodItem)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeFoodEvent = (current: number) => {
|
|
||||||
foodCardInfo.currentFoodIndex = current
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import { useRouter } from 'vue-router'
|
|||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { IFoodRecord } from '@/types/food.ts'
|
import { IFoodRecord } from '@/types/food.ts'
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
@@ -85,18 +83,10 @@ const getFoodDailyByDate = (date: string) => {
|
|||||||
*/
|
*/
|
||||||
const selectFoodRecordClick = async (foodRecord: IFoodRecord) => {
|
const selectFoodRecordClick = async (foodRecord: IFoodRecord) => {
|
||||||
foodStore.foodRecordApiType = 'UPDATE'
|
foodStore.foodRecordApiType = 'UPDATE'
|
||||||
|
foodStore.currentFoodRecord = foodRecord
|
||||||
|
|
||||||
if (isMobile()) {
|
appStore.isShowMobileBack = true
|
||||||
appStore.isShowMobileBack = true
|
await router.push({ name: 'FoodRecordForm' })
|
||||||
foodStore.currentFoodRecord = foodRecord
|
|
||||||
await router.push({ name: 'FoodRecordForm' })
|
|
||||||
} else {
|
|
||||||
foodStore.foodRecordDialog = {
|
|
||||||
title: '编辑记录',
|
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(foodRecord)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -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/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>
|
|
||||||
@@ -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/Food/Form/FoodMaterialForm.vue'
|
|
||||||
import FoodStepForm from '@/components/Food/Form/FoodStepForm.vue'
|
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
|
||||||
import { FormInstance, FormRules } from 'element-plus'
|
|
||||||
import { computed, nextTick, ref, reactive } from 'vue'
|
|
||||||
import { foodRules } from '@/utils/element/elRules.ts'
|
|
||||||
|
|
||||||
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>
|
|
||||||
@@ -1,68 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isMobile()" class="mobile-common-query card-item">
|
<div class="common-query">
|
||||||
<div class="query-item">
|
<el-select
|
||||||
<span>分类:</span>
|
v-model="foodStore.queryRecipe.name"
|
||||||
<el-segmented v-model="foodStore.queryRecipe.category"
|
filterable clearable placeholder="请选择菜谱名称"
|
||||||
:options="queryCategoryList"
|
style="width: 150px;"
|
||||||
@change="queryFoodSegmented"/>
|
@change="changeFoodNameEvent">
|
||||||
</div>
|
<el-option
|
||||||
</div>
|
v-for="(item, index) in foodStore.foodNameList"
|
||||||
|
:key="index" :label="item" :value="item"
|
||||||
<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>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
import { onMounted } from 'vue'
|
||||||
import { onMounted, ref } from 'vue'
|
import { searchFoodRecipeApi } from '@/apis/food.ts'
|
||||||
import { ICategory } from '@/types'
|
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|
||||||
const queryCategoryList = ref([] as ICategory[])
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await foodStore.queryFoodCategory()
|
await foodStore.queryFoodNameList()
|
||||||
initCategoryList()
|
foodStore.queryRecipe.name = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const dateTypeOptions = [{
|
const changeFoodNameEvent = async (value: string) => {
|
||||||
label: '按月选择',
|
if (value) {
|
||||||
value: 'month'
|
const result = await searchFoodRecipeApi(value)
|
||||||
},
|
foodStore.foodRecipeList = [result]
|
||||||
{
|
} else {
|
||||||
label: '按年选择',
|
foodStore.pageInfo.currentPage = 1
|
||||||
value: 'year'
|
foodStore.pageInfo.pageSize = 3
|
||||||
}]
|
foodStore.foodRecipeList = []
|
||||||
|
await foodStore.queryMobileFoodRecipeList()
|
||||||
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>
|
</script>
|
||||||
|
|||||||
@@ -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/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>
|
|
||||||
@@ -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>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="foodStore.foodMaterialDialog.visible"
|
<el-dialog v-model="foodStore.foodMaterialDialogVisible"
|
||||||
:title="foodStore.foodMaterialDialog.title"
|
:title="foodStore.foodMaterialApiType === 'ADD' ? '新增食材' : '编辑食材'"
|
||||||
center @open="openDialogEvent"
|
center @open="openDialogEvent"
|
||||||
class="food-material-dialog">
|
class="food-material-dialog">
|
||||||
<el-form ref="formRef" :model="foodStore.foodMaterialDialog.data"
|
<el-form ref="formRef" :model="foodStore.currentFoodMaterial"
|
||||||
:rules="rules">
|
:rules="rules" label-width="auto">
|
||||||
<el-form-item label="类型" prop="type">
|
<el-form-item label="类型" prop="type">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="foodStore.foodMaterialDialog.data.type"
|
v-model="foodStore.currentFoodMaterial.type"
|
||||||
placeholder="请选择类型">
|
placeholder="请选择类型">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="(item, index) in foodMaterialTypeList"
|
v-for="(item, index) in foodMaterialTypeList"
|
||||||
@@ -17,14 +17,14 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="名称" prop="name">
|
<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
|
placeholder="请输入名称" clearable
|
||||||
show-word-limit maxlength="20">
|
show-word-limit maxlength="20">
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="分量" prop="amount">
|
<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
|
placeholder="请输入分量(1斤/2勺/适量)" clearable
|
||||||
show-word-limit maxlength="20">
|
show-word-limit maxlength="20">
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -68,22 +68,22 @@ const confirmClick = () => {
|
|||||||
formRef.value.validate().then(async () => {
|
formRef.value.validate().then(async () => {
|
||||||
if (foodStore.foodMaterialApiType === 'UPDATE') {
|
if (foodStore.foodMaterialApiType === 'UPDATE') {
|
||||||
const { editFoodMaterialIndex: index } = foodStore
|
const { editFoodMaterialIndex: index } = foodStore
|
||||||
foodStore.foodRecipeDialog.data.materialList[index] = foodStore.foodMaterialDialog.data
|
foodStore.currentFoodRecipe.materialList[index] = foodStore.currentFoodMaterial
|
||||||
} else {
|
} else {
|
||||||
foodStore.foodRecipeDialog.data.materialList.push(foodStore.foodMaterialDialog.data)
|
foodStore.currentFoodRecipe.materialList.push(foodStore.currentFoodMaterial)
|
||||||
}
|
}
|
||||||
foodStore.foodMaterialDialog.visible = false
|
foodStore.foodMaterialDialogVisible = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelClick = () => {
|
const cancelClick = () => {
|
||||||
foodStore.foodMaterialDialog.visible = false
|
foodStore.foodMaterialDialogVisible = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.food-material-dialog {
|
.food-material-dialog {
|
||||||
width: 30%;
|
width: 80%;
|
||||||
|
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
// height: 50vh;
|
// height: 50vh;
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-table :data="foodStore.foodRecipeDialog.data.materialList"
|
<div class="material-form">
|
||||||
border stripe class="material-table">
|
<div style="align-self: center;">
|
||||||
<el-table-column type="index" align="center" width="90">
|
<el-button type="primary" @click="handleAddClick">新增</el-button>
|
||||||
<template #header>
|
</div>
|
||||||
<el-button type="primary" @click="handleAddClick">新增</el-button>
|
|
||||||
</template>
|
<el-table :data="foodStore.currentFoodRecipe.materialList"
|
||||||
</el-table-column>
|
border stripe class="material-table">
|
||||||
<el-table-column prop="type" label="类型" align="center" min-width="10%"/>
|
<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="name" label="名称" align="center" min-width="20%"/>
|
||||||
<el-table-column prop="amount" 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">
|
<el-table-column label="操作" align="center" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" text
|
<el-button type="primary" text style="margin: 0 10px 0 0;padding: 0;"
|
||||||
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
|
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
|
||||||
<el-button type="danger" text
|
<el-button type="danger" text style="margin: 0;padding: 0;"
|
||||||
@click="handleDeleteClick(scope.$index, scope.row)">删除</el-button>
|
@click="handleDeleteClick(scope.$index, scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -30,27 +31,21 @@ const foodStore = useFoodStore()
|
|||||||
|
|
||||||
const handleAddClick = () => {
|
const handleAddClick = () => {
|
||||||
foodStore.foodMaterialApiType = 'ADD'
|
foodStore.foodMaterialApiType = 'ADD'
|
||||||
foodStore.foodMaterialDialog = {
|
foodStore.currentFoodMaterial = foodStore.getEmptyFoodMaterial()
|
||||||
title: '新增食材',
|
foodStore.foodMaterialDialogVisible = true
|
||||||
visible: true,
|
|
||||||
data: foodStore.getEmptyFoodMaterial()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditClick = (index: number, material: IFoodMaterial) => {
|
const handleEditClick = (index: number, material: IFoodMaterial) => {
|
||||||
foodStore.editFoodMaterialIndex = index
|
foodStore.editFoodMaterialIndex = index
|
||||||
foodStore.foodMaterialApiType = 'UPDATE'
|
foodStore.foodMaterialApiType = 'UPDATE'
|
||||||
foodStore.foodMaterialDialog = {
|
foodStore.currentFoodMaterial = deepCopyObject(material)
|
||||||
title: '编辑食材',
|
foodStore.foodMaterialDialogVisible = true
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(material)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteClick = (index: number, material: IFoodMaterial) => {
|
const handleDeleteClick = (index: number, material: IFoodMaterial) => {
|
||||||
foodStore.foodMaterialApiType = 'DELETE'
|
foodStore.foodMaterialApiType = 'DELETE'
|
||||||
commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => {
|
commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => {
|
||||||
foodStore.foodRecipeDialog.data.materialList.splice(index, 1)
|
foodStore.currentFoodRecipe.materialList.splice(index, 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
|
|
||||||
center @open="openDialogEvent" class="food-record-dialog">
|
|
||||||
<el-form ref="formRef" :model="dialogData.data"
|
|
||||||
:rules="rules" label-width="80">
|
|
||||||
<el-form-item label="菜谱名称" prop="name">
|
|
||||||
<el-select
|
|
||||||
v-model="dialogData.data.name"
|
|
||||||
@focus="foodStore.queryFoodNameList()"
|
|
||||||
:disabled="foodStore.foodRecordApiType === 'UPDATE'"
|
|
||||||
placeholder="请选择菜谱名称">
|
|
||||||
<el-option
|
|
||||||
v-for="(item, index) in foodStore.foodNameList"
|
|
||||||
:key="index" :label="item" :value="item"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="完成时间" prop="date">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="dialogData.data.date"
|
|
||||||
type="date" :editable="false" placeholder="请选择完成时间"
|
|
||||||
value-format="YYYY-MM-DD"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="完成人" prop="person">
|
|
||||||
<el-select
|
|
||||||
v-model="dialogData.data.person"
|
|
||||||
placeholder="请选择完成人">
|
|
||||||
<el-option
|
|
||||||
v-for="(item, index) in ['哥哥', '宝宝']"
|
|
||||||
:key="index" :label="item" :value="item"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="图片">
|
|
||||||
<upload-image v-model="dialogData.data.imageUrl"
|
|
||||||
:service-url="fileServiceUrl"
|
|
||||||
:image-limit="1"
|
|
||||||
:service-file-root-path="serviceFileRootPath"
|
|
||||||
:image-path="foodStore.foodImagePath"/>
|
|
||||||
</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>
|
|
||||||
<el-button v-if="foodStore.foodRecordApiType === 'UPDATE'"
|
|
||||||
type="danger" @click="deleteClick">删除</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { UploadImage } from 'vue3-common'
|
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
|
||||||
import { computed, ref, reactive, nextTick } from 'vue'
|
|
||||||
import { FormInstance, FormRules } from 'element-plus'
|
|
||||||
import { foodRecordRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
|
||||||
|
|
||||||
const rules = reactive<FormRules>(foodRecordRules)
|
|
||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
|
||||||
const foodStore = useFoodStore()
|
|
||||||
|
|
||||||
const dialogData = computed(() => foodStore.foodRecordDialog)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打开对话框事件
|
|
||||||
*/
|
|
||||||
const openDialogEvent = async () => {
|
|
||||||
await nextTick(() => {
|
|
||||||
formRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击确认按钮
|
|
||||||
*/
|
|
||||||
const confirmClick = () => {
|
|
||||||
formRef.value?.validate().then(() => {
|
|
||||||
foodStore.handleFoodRecordApi(foodStore.foodRecordDialog.data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelClick = () => {
|
|
||||||
dialogData.value.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteClick = () => {
|
|
||||||
commonElMessageBox('是否确认删除该记录?').then(() => {
|
|
||||||
foodStore.foodRecordApiType = 'DELETE'
|
|
||||||
foodStore.handleFoodRecordApi(dialogData.value.data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.food-record-dialog {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,23 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="foodStore.foodStepDialog.visible"
|
<el-dialog v-model="foodStore.foodStepDialogVisible"
|
||||||
:title="foodStore.foodStepDialog.title"
|
:title="foodStore.foodStepApiType === 'ADD' ? '新增步骤' : '编辑步骤'"
|
||||||
center @open="openDialogEvent"
|
center @open="openDialogEvent"
|
||||||
class="food-step-dialog">
|
class="food-step-dialog">
|
||||||
<el-form ref="formRef" :model="foodStore.foodStepDialog.data"
|
<el-form ref="formRef" :model="foodStore.currentFoodStep"
|
||||||
:rules="rules">
|
:rules="rules" label-width="auto">
|
||||||
<el-form-item label="内容" prop="content">
|
<el-form-item label="内容" prop="content">
|
||||||
<el-input v-model="foodStore.foodStepDialog.data.content" autocomplete="off"
|
<el-input v-model="foodStore.currentFoodStep.content" autocomplete="off"
|
||||||
placeholder="请输入内容" clearable
|
placeholder="请输入内容" clearable
|
||||||
show-word-limit maxlength="20">
|
show-word-limit maxlength="20">
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="图片">
|
<el-form-item label="图片">
|
||||||
<upload-image v-model="foodStore.foodStepDialog.data.imageUrl"
|
<el-upload
|
||||||
:service-url="fileServiceUrl"
|
v-model:file-list="imageFileList"
|
||||||
:image-limit="1"
|
list-type="picture-card"
|
||||||
:service-file-root-path="serviceFileRootPath"
|
:accept="acceptImageTypes"
|
||||||
:image-path="foodStore.foodImagePath"/>
|
: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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@@ -31,17 +45,58 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { UploadImage } from 'vue3-common'
|
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { ref, reactive, nextTick } from 'vue'
|
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||||
import { FormRules } from 'element-plus'
|
import { ElMessage, ElMessageBox, FormRules, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus'
|
||||||
import { foodStepRules } from '@/utils/element/elRules.ts'
|
import { foodStepRules } from '@/utils/element/elRules.ts'
|
||||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
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 rules = reactive<FormRules>(foodStepRules)
|
||||||
|
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const foodStore = useFoodStore()
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开对话框事件
|
* 打开对话框事件
|
||||||
@@ -60,22 +115,22 @@ const confirmClick = () => {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
if (foodStore.foodStepApiType === 'UPDATE') {
|
if (foodStore.foodStepApiType === 'UPDATE') {
|
||||||
const { editFoodStepIndex: index } = foodStore
|
const { editFoodStepIndex: index } = foodStore
|
||||||
foodStore.foodRecipeDialog.data.stepList[index] = foodStore.foodStepDialog.data
|
foodStore.currentFoodRecipe.stepList[index] = foodStore.currentFoodStep
|
||||||
} else {
|
} else {
|
||||||
foodStore.foodRecipeDialog.data.stepList.push(foodStore.foodStepDialog.data)
|
foodStore.currentFoodRecipe.stepList.push(foodStore.currentFoodStep)
|
||||||
}
|
}
|
||||||
foodStore.foodStepDialog.visible = false
|
foodStore.foodStepDialogVisible = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelClick = () => {
|
const cancelClick = () => {
|
||||||
foodStore.foodStepDialog.visible = false
|
foodStore.foodStepDialogVisible = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.food-step-dialog {
|
.food-step-dialog {
|
||||||
width: 30%;
|
width: 80%;
|
||||||
|
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
// height: 50vh;
|
// height: 50vh;
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-table :data="foodStore.foodRecipeDialog.data.stepList"
|
<div class="step-form">
|
||||||
border class="food-step-table">
|
<div style="align-self: center;">
|
||||||
<el-table-column type="index" align="center" width="90">
|
<el-button type="primary" @click="handleAddClick">新增</el-button>
|
||||||
<template #header>
|
</div>
|
||||||
<el-button type="primary" @click="handleAddClick">新增</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<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="serviceFileRootPath + scope.row.imageUrl" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column label="操作" align="center" width="100">
|
<el-table :data="foodStore.currentFoodRecipe.stepList"
|
||||||
<template #default="scope">
|
border class="food-step-table">
|
||||||
<el-button type="primary" text
|
<el-table-column prop="content" label="内容" align="center" min-width="20%" />
|
||||||
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
|
<el-table-column prop="imageUrl" label="图片" align="center" min-width="20%">
|
||||||
<el-button type="danger" text
|
<template #default="scope">
|
||||||
@click="handleDeleteClick(scope.$index)">删除</el-button>
|
<el-image v-if="scope.row.imageUrl" :src="scope.row.imageUrl" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
|
||||||
|
<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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -29,35 +30,27 @@ import { useFoodStore } from '@/store/food.ts'
|
|||||||
import { IFoodStep } from '@/types/food.ts'
|
import { IFoodStep } from '@/types/food.ts'
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|
||||||
const handleAddClick = () => {
|
const handleAddClick = () => {
|
||||||
foodStore.foodStepApiType = 'ADD'
|
foodStore.foodStepApiType = 'ADD'
|
||||||
foodStore.foodStepImageList = []
|
foodStore.currentFoodStep = foodStore.getEmptyFoodStep()
|
||||||
foodStore.foodStepDialog = {
|
foodStore.foodStepDialogVisible = true
|
||||||
title: '新增步骤',
|
|
||||||
visible: true,
|
|
||||||
data: foodStore.getEmptyFoodStep()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditClick = (index: number, step: IFoodStep) => {
|
const handleEditClick = (index: number, step: IFoodStep) => {
|
||||||
foodStore.editFoodStepIndex = index
|
foodStore.editFoodStepIndex = index
|
||||||
foodStore.foodStepApiType = 'UPDATE'
|
foodStore.foodStepApiType = 'UPDATE'
|
||||||
foodStore.foodStepDialog = {
|
foodStore.currentFoodStep = deepCopyObject(step)
|
||||||
title: '编辑步骤',
|
foodStore.foodStepDialogVisible = true
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(step)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteClick = (index: number) => {
|
const handleDeleteClick = (index: number) => {
|
||||||
commonElMessageBox('确认删除该步骤?').then(() => {
|
commonElMessageBox('确认删除该步骤?').then(() => {
|
||||||
foodStore.foodStepApiType = 'DELETE'
|
foodStore.foodStepApiType = 'DELETE'
|
||||||
foodStore.foodRecipeDialog.data.stepList.splice(index, 1)
|
foodStore.currentFoodRecipe.stepList.splice(index, 1)
|
||||||
foodStore.foodRecipeDialog.data.stepList.forEach((item) => {
|
foodStore.currentFoodRecipe.stepList.forEach((item) => {
|
||||||
if (item.sort > index) {
|
if (item.sort > index) {
|
||||||
item.sort -= 1
|
item.sort -= 1
|
||||||
}
|
}
|
||||||
|
|||||||
129
src/components/Health/HealthChart.vue
Normal file
129
src/components/Health/HealthChart.vue
Normal 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>
|
||||||
179
src/components/Health/HealthForm.vue
Normal file
179
src/components/Health/HealthForm.vue
Normal 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>
|
||||||
41
src/components/Health/HealthStats.vue
Normal file
41
src/components/Health/HealthStats.vue
Normal 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>
|
||||||
@@ -5,8 +5,11 @@
|
|||||||
:is-dark="appStore.isDark"
|
:is-dark="appStore.isDark"
|
||||||
class="journal-calendar card-item"/>
|
class="journal-calendar card-item"/>
|
||||||
|
|
||||||
<journal-card v-if="journalStore.currentJournal.id !== 0"
|
<div v-if="journalStore.dateJournalList.length > 0"
|
||||||
:item="journalStore.currentJournal"/>
|
class="journal-list">
|
||||||
|
<journal-card v-for="(item, index) in journalStore.dateJournalList"
|
||||||
|
:key="index" :item="item"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-empty v-else description="今日暂无日记" />
|
<el-empty v-else description="今日暂无日记" />
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +38,7 @@ const dailyInfo = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateInfo = () => {
|
const updateInfo = () => {
|
||||||
|
journalStore.dateJournalList = []
|
||||||
getCalendarMoveDate()
|
getCalendarMoveDate()
|
||||||
getCalendarAttrDates()
|
getCalendarAttrDates()
|
||||||
}
|
}
|
||||||
@@ -42,7 +46,7 @@ const updateInfo = () => {
|
|||||||
const getCalendarMoveDate = () => {
|
const getCalendarMoveDate = () => {
|
||||||
const recordLength = journalStore.journalList.length
|
const recordLength = journalStore.journalList.length
|
||||||
if (recordLength > 0) {
|
if (recordLength > 0) {
|
||||||
journalStore.currentJournal = getJournalDay(formatDate(new Date()))
|
journalStore.dateJournalList = getJournalDay(formatDate(new Date()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +58,7 @@ const getCalendarAttrDates = () => {
|
|||||||
dates: new Date()
|
dates: new Date()
|
||||||
})
|
})
|
||||||
|
|
||||||
const dateList = journalStore.journalList.map((item) => item.date)
|
const dateList = journalStore.journalList.map((item) => formatDate(item.date))
|
||||||
dailyInfo.attrs.push({
|
dailyInfo.attrs.push({
|
||||||
dot: 'red',
|
dot: 'red',
|
||||||
dates: [...new Set(dateList)]
|
dates: [...new Set(dateList)]
|
||||||
@@ -66,12 +70,11 @@ const getCalendarAttrDates = () => {
|
|||||||
* @param day 日期
|
* @param day 日期
|
||||||
*/
|
*/
|
||||||
const dateClick = (day: CalendarDay) => {
|
const dateClick = (day: CalendarDay) => {
|
||||||
journalStore.currentJournal = getJournalDay(formatDate(day.date))
|
journalStore.dateJournalList = getJournalDay(formatDate(day.date))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getJournalDay = (date: string): IJournal => {
|
const getJournalDay = (date: string): IJournal[] => {
|
||||||
return journalStore.journalList.find((item) => formatDate(item.date) === date)
|
return journalStore.journalList.filter((item) => formatDate(item.date) === date) || []
|
||||||
|| journalStore.getEmptyJournal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,5 +99,12 @@ const changeMonthClick = async (value) => {
|
|||||||
.journal-calendar {
|
.journal-calendar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.journal-list {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1vh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,16 +37,15 @@
|
|||||||
<!-- 单媒体内容(图片优先于视频) -->
|
<!-- 单媒体内容(图片优先于视频) -->
|
||||||
<el-image
|
<el-image
|
||||||
v-if="props.item?.imageUrl"
|
v-if="props.item?.imageUrl"
|
||||||
:src="serviceFileRootPath + props.item?.imageUrl"
|
:src="`${props.item?.imageUrl}${ThumbImageQuery}`"
|
||||||
:preview-src-list="[serviceFileRootPath + props.item?.imageUrl]"
|
fit="contain" @click="viewImageClick"
|
||||||
fit="contain"
|
|
||||||
class="media-content"/>
|
class="media-content"/>
|
||||||
|
|
||||||
<video
|
<video
|
||||||
v-if="props.item?.videoUrl"
|
v-if="props.item?.videoUrl"
|
||||||
controls
|
controls
|
||||||
class="media-content">
|
class="media-content">
|
||||||
<source :src="serviceFileRootPath + props.item?.videoUrl" type="video/mp4">
|
<source :src="props.item?.videoUrl" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<!-- 展开/收起按钮 -->
|
<!-- 展开/收起按钮 -->
|
||||||
@@ -71,12 +70,10 @@ import { SvgIcon } from 'vue3-common'
|
|||||||
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue'
|
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue'
|
||||||
import { IJournal } from '@/types/journal.ts'
|
import { IJournal } from '@/types/journal.ts'
|
||||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { useJournalStore } from '@/store/journal.ts'
|
import { useJournalStore } from '@/store/journal.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { RawImageQuery, ThumbImageQuery } from '@/utils'
|
||||||
import { useRouter } from 'vue-router'
|
import { showImagePreview } from 'vant'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
@@ -86,8 +83,6 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const journalStore = useJournalStore()
|
const journalStore = useJournalStore()
|
||||||
const appStore = useAppStore()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (overflowLines.value <= 0) expanded.value = true
|
if (overflowLines.value <= 0) expanded.value = true
|
||||||
@@ -106,23 +101,16 @@ const overflowLines = computed(() => {
|
|||||||
|
|
||||||
const showToggle = computed(() => overflowLines.value > 0)
|
const showToggle = computed(() => overflowLines.value > 0)
|
||||||
|
|
||||||
const toggleExpand = () => expanded.value = !expanded.value
|
const toggleExpand = () => { expanded.value = !expanded.value }
|
||||||
|
|
||||||
|
const viewImageClick = () => {
|
||||||
|
showImagePreview({ images: [props.item?.imageUrl + RawImageQuery], closeable: true })
|
||||||
|
}
|
||||||
|
|
||||||
const editClick = async () => {
|
const editClick = async () => {
|
||||||
const id = props.item?.id
|
|
||||||
journalStore.journalApiType = 'UPDATE'
|
journalStore.journalApiType = 'UPDATE'
|
||||||
journalStore.currentJournal = props.item
|
journalStore.currentJournal = deepCopyObject(props.item)
|
||||||
|
journalStore.journalDrawer = true
|
||||||
if (isMobile()) {
|
|
||||||
appStore.isShowMobileBack = true
|
|
||||||
await router.push({ name: 'JournalDetailId', params: { id } })
|
|
||||||
} else {
|
|
||||||
journalStore.journalDialog = {
|
|
||||||
title: '编辑日记',
|
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(journalStore.currentJournal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
248
src/components/Journal/JournalDrawer.vue
Normal file
248
src/components/Journal/JournalDrawer.vue
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="journalStore.journalDrawer" destroy-on-close
|
||||||
|
:with-header="false" direction="btt" size="95%">
|
||||||
|
<div class="mobile-journal-detail common-mobile-view">
|
||||||
|
<el-form ref="formRef" :model="journalStore.currentJournal"
|
||||||
|
:rules="rules" label-position="top" label-width="60px"
|
||||||
|
class="form-item">
|
||||||
|
<el-form-item label="日期" prop="date">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="journalStore.currentJournal.date"
|
||||||
|
type="datetime" :editable="false" placeholder="请选择日期"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="内容" prop="content">
|
||||||
|
<el-input
|
||||||
|
v-model="journalStore.currentJournal.content"
|
||||||
|
:rows="5" type="textarea"
|
||||||
|
show-word-limit :maxlength="200"
|
||||||
|
placeholder="请输入日记内容"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="地址">
|
||||||
|
<el-input
|
||||||
|
v-model="journalStore.currentJournal.location"
|
||||||
|
placeholder="请输入日记地址"
|
||||||
|
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="分类">
|
||||||
|
<el-radio-group v-model="journalStore.currentJournal.category"
|
||||||
|
placeholder="请选择分类" clearable>
|
||||||
|
<el-radio
|
||||||
|
v-for="(item, index) in journalCategoryTypeList"
|
||||||
|
:key="index" :label="item" :value="item"
|
||||||
|
/>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="天气">
|
||||||
|
<el-radio-group v-model="journalStore.currentJournal.weather"
|
||||||
|
placeholder="请选择天气" clearable>
|
||||||
|
<el-radio
|
||||||
|
v-for="(item, index) in journalWeatherTypeList"
|
||||||
|
:key="index" :value="item.value"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<svg-icon :name="`weather-${item.value}`"/>
|
||||||
|
</template>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="心情">
|
||||||
|
<el-radio-group v-model="journalStore.currentJournal.mood"
|
||||||
|
placeholder="请选择心情" clearable>
|
||||||
|
<el-radio
|
||||||
|
v-for="(item, index) in journalMoodTypeList"
|
||||||
|
:key="index" :value="item" :label="item"
|
||||||
|
/>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="图片">
|
||||||
|
<el-upload
|
||||||
|
v-model:file-list="imageFileList"
|
||||||
|
list-type="picture-card"
|
||||||
|
:accept="acceptImageTypes"
|
||||||
|
:limit="1"
|
||||||
|
:before-upload="beforeImageUpload"
|
||||||
|
:http-request="httpImageRequest"
|
||||||
|
:on-success="handleImageSuccess"
|
||||||
|
:on-exceed="handleImageExceed"
|
||||||
|
:before-remove="beforeRemove"
|
||||||
|
:on-remove="handleImageRemove"
|
||||||
|
>
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">
|
||||||
|
请上传小于10M的图片
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="视频">
|
||||||
|
<el-upload
|
||||||
|
v-model:file-list="videoFileList"
|
||||||
|
:accept="acceptVideoTypes"
|
||||||
|
:limit="1"
|
||||||
|
:before-upload="beforeVideoUpload"
|
||||||
|
:http-request="httpVideoRequest"
|
||||||
|
:on-success="handleVideoSuccess"
|
||||||
|
:on-exceed="handleVideoExceed"
|
||||||
|
:before-remove="beforeRemove"
|
||||||
|
:on-remove="handleVideoRemove"
|
||||||
|
>
|
||||||
|
<el-button type="primary">点击上传</el-button>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">
|
||||||
|
请上传小于200M的视频
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div style="flex: auto">
|
||||||
|
<el-button type="primary" @click="confirmClick">确认</el-button>
|
||||||
|
<el-button v-if="journalStore.journalApiType === '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 { Location, Plus } from '@element-plus/icons-vue'
|
||||||
|
import { useJournalStore } from '@/store/journal.ts'
|
||||||
|
import { ElMessage, ElMessageBox, FormRules, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus'
|
||||||
|
import { journalRules } from '@/utils/element/elRules.ts'
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
|
import { SvgIcon } from 'vue3-common'
|
||||||
|
import { acceptImageTypes, acceptVideoTypes } from '@/utils/file.ts'
|
||||||
|
import { beforeUploadImage, beforeUploadVideo, httpRequestFile } from '@/utils/element/elUpload.ts'
|
||||||
|
import { journalCategoryTypeList, journalMoodTypeList, journalWeatherTypeList } from '@/utils/home/journalUtil.ts'
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const rules = reactive<FormRules>(journalRules)
|
||||||
|
const journalStore = useJournalStore()
|
||||||
|
|
||||||
|
const imageFileList = ref<UploadUserFile[]>([])
|
||||||
|
const videoFileList = ref<UploadUserFile[]>([])
|
||||||
|
|
||||||
|
watch(() => journalStore.journalDrawer, (value) => {
|
||||||
|
initFileList()
|
||||||
|
})
|
||||||
|
|
||||||
|
const initFileList = () => {
|
||||||
|
imageFileList.value = []
|
||||||
|
videoFileList.value = []
|
||||||
|
|
||||||
|
const { imageUrl, videoUrl } = journalStore.currentJournal
|
||||||
|
if (imageUrl) {
|
||||||
|
imageFileList.value.push({
|
||||||
|
name: 'image',
|
||||||
|
url: imageUrl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoUrl) {
|
||||||
|
videoFileList.value.push({
|
||||||
|
name: 'video',
|
||||||
|
url: videoUrl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||||
|
return beforeUploadImage(rawFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpImageRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
|
||||||
|
return await httpRequestFile(journalStore.journalImagePath, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageSuccess = (response: string) => {
|
||||||
|
journalStore.currentJournal.imageUrl = response
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageExceed: UploadProps['onExceed'] = () => {
|
||||||
|
ElMessage.warning('最多上传1张图片')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageRemove: UploadProps['onRemove'] = () => {
|
||||||
|
journalStore.currentJournal.imageUrl = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeRemove: UploadProps['beforeRemove'] = () => {
|
||||||
|
return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||||
|
return beforeUploadVideo(rawFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpVideoRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
|
||||||
|
return await httpRequestFile(journalStore.journalImagePath, options, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVideoSuccess = (response: string) => {
|
||||||
|
journalStore.currentJournal.videoUrl = `p${response}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVideoExceed: UploadProps['onExceed'] = () => {
|
||||||
|
ElMessage.warning('最多上传1个视频')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVideoRemove: UploadProps['onRemove'] = () => {
|
||||||
|
journalStore.currentJournal.videoUrl = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击确认按钮
|
||||||
|
*/
|
||||||
|
const confirmClick = () => {
|
||||||
|
formRef.value.validate().then(async () => {
|
||||||
|
await journalStore.handleJournalApi(journalStore.currentJournal)
|
||||||
|
journalStore.journalDrawer = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelClick = () => {
|
||||||
|
journalStore.journalDrawer = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteClick = () => {
|
||||||
|
commonElMessageBox('是否确认删除该日记内容?').then(async () => {
|
||||||
|
journalStore.journalApiType = 'DELETE'
|
||||||
|
await journalStore.handleJournalApi(journalStore.currentJournal)
|
||||||
|
journalStore.journalDrawer = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.mobile-journal-detail {
|
||||||
|
.el-radio {
|
||||||
|
.el-radio__label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -38,8 +38,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, PropType } from 'vue'
|
import { defineProps, PropType } from 'vue'
|
||||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { useLedgerStore } from '@/store/ledger.ts'
|
import { useLedgerStore } from '@/store/ledger.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ILedger, ILegerItem } from '@/types/ledger.ts'
|
import { ILedger, ILegerItem } from '@/types/ledger.ts'
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ onMounted(async () => {
|
|||||||
const initCategoryList = () => {
|
const initCategoryList = () => {
|
||||||
queryCategoryList.value = []
|
queryCategoryList.value = []
|
||||||
|
|
||||||
if (ledgerStore.ledgerBookList.length > 0) {
|
|
||||||
ledgerStore.queryInfo.book = ledgerStore.ledgerBookList[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
ledgerStore.ledgerBookList.forEach((item) => {
|
ledgerStore.ledgerBookList.forEach((item) => {
|
||||||
queryCategoryList.value.push({
|
queryCategoryList.value.push({
|
||||||
label: item,
|
label: item,
|
||||||
|
|||||||
23
src/components/Ledger/LedgerRank.vue
Normal file
23
src/components/Ledger/LedgerRank.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div v-for="(item, index) in ledgerRankList.slice(0, MAX_RANK_COUNT)"
|
||||||
|
:key="index" class="rank-item">
|
||||||
|
<span class="rank-index">{{ index+1 }}.</span>
|
||||||
|
<span class="rank-content">{{ item.content }}</span>
|
||||||
|
<span class="rank-amount">¥{{ formatAmount(item.totalPrice) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLedgerStore } from '@/store/ledger.ts'
|
||||||
|
import { getLedgerSortByPrice } from '@/utils/home/ledgerUtil.ts'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||||
|
|
||||||
|
const ledgerStore = useLedgerStore()
|
||||||
|
|
||||||
|
const MAX_RANK_COUNT = 10
|
||||||
|
|
||||||
|
const ledgerRankList = computed(() => {
|
||||||
|
return getLedgerSortByPrice(ledgerStore.ledgerRecordList)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -79,7 +79,6 @@ import { onMounted, reactive, ref } from 'vue'
|
|||||||
import { loginRules } from '@/utils/element/elRules.ts'
|
import { loginRules } from '@/utils/element/elRules.ts'
|
||||||
import { getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
import { getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
||||||
import { ILoginType } from '@/types/auth.ts'
|
import { ILoginType } from '@/types/auth.ts'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
const rules = reactive<FormRules>(loginRules)
|
const rules = reactive<FormRules>(loginRules)
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -98,16 +97,6 @@ onMounted(() => {
|
|||||||
const handleLogin = async (type: ILoginType) => {
|
const handleLogin = async (type: ILoginType) => {
|
||||||
loginFormRef.value.validate(async (valid: any) => {
|
loginFormRef.value.validate(async (valid: any) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
if (type === 'common' && !isMobile()) {
|
|
||||||
ElMessage.error('目前只支持移动端')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'admin' && isMobile()) {
|
|
||||||
ElMessage.error('目前只支持PC端')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'common') {
|
if (type === 'common') {
|
||||||
authStore.loginLoading = true
|
authStore.loginLoading = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
114
src/components/NumberInput.vue
Normal file
114
src/components/NumberInput.vue
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div class="keyboard">
|
||||||
|
<div class="display">
|
||||||
|
{{ amount || '0' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="keys">
|
||||||
|
<el-button v-for="key in keys" :key="String(key)" class="key" @click="press(key)">
|
||||||
|
{{ key }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button class="key delete" @click="removeLast">
|
||||||
|
<i class="fa-solid fa-delete-left"></i>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineModel } from 'vue'
|
||||||
|
|
||||||
|
const amount = defineModel('amount', { type: String, default: '0' })
|
||||||
|
|
||||||
|
const keys = [1, 2, 3, 4, 5, 6, 7, 8, 9, '.', 0] as const
|
||||||
|
|
||||||
|
const press = (key: number | '.') => {
|
||||||
|
const pressValue = amount.value
|
||||||
|
|
||||||
|
// 小数点
|
||||||
|
if (key === '.') {
|
||||||
|
if (pressValue.includes('.')) return
|
||||||
|
if (pressValue === '') {
|
||||||
|
amount.value = '0.'
|
||||||
|
} else {
|
||||||
|
amount.value += '.'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = pressValue + key
|
||||||
|
|
||||||
|
// 限制小数点后两位
|
||||||
|
if (next.includes('.')) {
|
||||||
|
const [, decimal] = next.split('.')
|
||||||
|
if (decimal.length > 2) return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.length > 1 && next.startsWith('0') && !next.startsWith('0.')) {
|
||||||
|
amount.value = next.slice(1)
|
||||||
|
} else {
|
||||||
|
amount.value = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeLast = () => {
|
||||||
|
amount.value = amount.value.slice(0, -1)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.keyboard {
|
||||||
|
min-height: 280px;
|
||||||
|
width: 100%;
|
||||||
|
background: #f7f8fa;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.display {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
padding: 0 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 22px;
|
||||||
|
text-align: right;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keys {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.key {
|
||||||
|
margin-left: 0;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: none;
|
||||||
|
box-shadow:
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.08),
|
||||||
|
inset 0 -1px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
color: #ff3b30;
|
||||||
|
|
||||||
|
.fa-delete-left {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="password-card card-item">
|
<van-swipe-cell>
|
||||||
<el-avatar class="avatar-info">{{ passwordItem.title.charAt(0) }}</el-avatar>
|
<div class="password-card card-item" @click="editClick">
|
||||||
|
<el-avatar class="avatar-info">{{ passwordItem.title.charAt(0) }}</el-avatar>
|
||||||
|
|
||||||
<div class="title-info">
|
<div class="title-info">
|
||||||
<span>{{ passwordItem.title }}</span>
|
<span>{{ passwordItem.title }}</span>
|
||||||
<span>{{ passwordItem.username }}</span>
|
<span>{{ passwordItem.username }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-info">
|
<template #right>
|
||||||
<el-button type="primary" :icon="Edit" circle
|
<van-button square type="primary" text="编辑" style="height: 100%;"
|
||||||
@click="editClick"/>
|
@click="editClick"/>
|
||||||
<el-button type="danger" :icon="DeleteFilled" circle
|
<van-button square type="danger" text="删除" style="height: 100%;"
|
||||||
@click="deleteClick()" />
|
@click="deleteClick()"/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</van-swipe-cell>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Edit, DeleteFilled } from '@element-plus/icons-vue'
|
|
||||||
import { usePasswordStore } from '@/store/password.ts'
|
import { usePasswordStore } from '@/store/password.ts'
|
||||||
import { defineProps, onMounted, PropType } from 'vue'
|
import { defineProps, PropType } from 'vue'
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { IPassword } from '@/types/password.ts'
|
import { IPassword } from '@/types/password.ts'
|
||||||
@@ -34,10 +35,6 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
const editClick = async () => {
|
const editClick = async () => {
|
||||||
const id = props.passwordItem?.id
|
const id = props.passwordItem?.id
|
||||||
passwordStore.passwordApiType = 'UPDATE'
|
passwordStore.passwordApiType = 'UPDATE'
|
||||||
@@ -59,7 +56,7 @@ const deleteClick = () => {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 40px 1fr 80px;
|
grid-template-columns: 40px 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch } from 'vue'
|
import { onMounted, watch } from 'vue'
|
||||||
import { Calendar, DateSelectArg, EventClickArg } from '@fullcalendar/core'
|
import { Calendar, DateSelectArg, EventClickArg } from '@fullcalendar/core'
|
||||||
import { initCommonCalendar, initMobileCalendar } from '@/utils/home/planUtil.ts'
|
import { initMobileCalendar } from '@/utils/home/planUtil.ts'
|
||||||
import { usePlanStore } from '@/store/plan.ts'
|
import { usePlanStore } from '@/store/plan.ts'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
let calendar: Calendar | undefined
|
let calendar: Calendar | undefined
|
||||||
|
|
||||||
@@ -27,11 +26,7 @@ watch(() => planStore.isRefresh, () => {
|
|||||||
const initCalendar = async () => {
|
const initCalendar = async () => {
|
||||||
const calendarEl = document.getElementById('planCalendar') as HTMLElement
|
const calendarEl = document.getElementById('planCalendar') as HTMLElement
|
||||||
|
|
||||||
if (isMobile()) {
|
calendar = initMobileCalendar(calendarEl, selectDateEvent, eventClickEvent, updateDateEvent)
|
||||||
calendar = initMobileCalendar(calendarEl, selectDateEvent, eventClickEvent, updateDateEvent)
|
|
||||||
} else {
|
|
||||||
calendar = initCommonCalendar(calendarEl, selectDateEvent, eventClickEvent, updateDateEvent)
|
|
||||||
}
|
|
||||||
calendar.render()
|
calendar.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +78,7 @@ const eventClickEvent = (eventClick: EventClickArg) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "@/styles/home.variable.module";
|
@use "@/styles/home.variable.module" as variable;
|
||||||
|
|
||||||
$cell-height: 3.8vh;
|
$cell-height: 3.8vh;
|
||||||
$time-slot-width: 70px;
|
$time-slot-width: 70px;
|
||||||
@@ -109,7 +104,7 @@ $month-cell-height: 120px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fc-button-active {
|
.fc-button-active {
|
||||||
background-color: $theme-color;
|
background-color: variable.$theme-color;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +137,7 @@ $month-cell-height: 120px;
|
|||||||
|
|
||||||
// 表头区域
|
// 表头区域
|
||||||
.fc-col-header {
|
.fc-col-header {
|
||||||
color: $theme-color;
|
color: variable.$theme-color;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
background-color: var(--color-record-card-bg);
|
background-color: var(--color-record-card-bg);
|
||||||
|
|||||||
@@ -67,11 +67,9 @@ import { defineProps, PropType } from 'vue'
|
|||||||
import { AlarmClock, Clock } from '@element-plus/icons-vue'
|
import { AlarmClock, Clock } from '@element-plus/icons-vue'
|
||||||
import { IPlanInfo } from '@/types/plan.ts'
|
import { IPlanInfo } from '@/types/plan.ts'
|
||||||
import { getRemainDays, isBeforeToday } from '@/utils/home/planUtil.ts'
|
import { getRemainDays, isBeforeToday } from '@/utils/home/planUtil.ts'
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { usePlanStore } from '@/store/plan.ts'
|
import { usePlanStore } from '@/store/plan.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
const planStore = usePlanStore()
|
const planStore = usePlanStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -85,17 +83,8 @@ const props = defineProps({
|
|||||||
|
|
||||||
const editPlanClick = async () => {
|
const editPlanClick = async () => {
|
||||||
planStore.planApiType = 'UPDATE'
|
planStore.planApiType = 'UPDATE'
|
||||||
|
planStore.currentPlan = props.plan as IPlanInfo
|
||||||
if (isMobile()) {
|
await router.push({ name: 'PlanDetailId', params: { id: planStore.currentPlan.id } })
|
||||||
planStore.currentPlan = props.plan as IPlanInfo
|
|
||||||
await router.push({ name: 'MobileHomePlanDetailId', params: { id: planStore.currentPlan.id } })
|
|
||||||
} else {
|
|
||||||
planStore.planDialog = {
|
|
||||||
title: '编辑计划',
|
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(props.plan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeCompleteEvent = (value: boolean) => {
|
const changeCompleteEvent = (value: boolean) => {
|
||||||
|
|||||||
@@ -1,211 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
|
|
||||||
center @open="openDialogEvent"
|
|
||||||
class="plan-dialog">
|
|
||||||
<el-form ref="formRef" :model="dialogData.data"
|
|
||||||
:rules="rules" label-width="80px" class="plan-form">
|
|
||||||
<el-form-item label="计划标题" prop="title">
|
|
||||||
<el-input v-model="dialogData.data.title" autocomplete="off"
|
|
||||||
placeholder="请输入标题" clearable
|
|
||||||
show-word-limit maxlength="10">
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="计划内容">
|
|
||||||
<el-input v-model="dialogData.data.content" autocomplete="off"
|
|
||||||
placeholder="请输入标题" :rows="2" type="textarea" clearable
|
|
||||||
show-word-limit maxlength="20">
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<div style="display: flex;justify-content: space-between;">
|
|
||||||
<el-form-item label="开始日期">
|
|
||||||
<el-date-picker v-model="dialogData.data.date"
|
|
||||||
type="date" placeholder="请选择日期" :editable="false"
|
|
||||||
value-format="YYYY-MM-DD" style="width: 150px;"
|
|
||||||
:disabled-date="disabledDate" @change="changeDateEvent"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="开始时间">
|
|
||||||
<el-time-picker v-model="dialogData.data.startTime"
|
|
||||||
placeholder="请选择开始时间" :editable="false"
|
|
||||||
format="HH:mm" value-format="HH:mm" style="width: 150px;"
|
|
||||||
:disabled="dialogData.data.date === null"
|
|
||||||
@change="changeStartTimeEvent"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="结束时间">
|
|
||||||
<el-time-picker v-model="dialogData.data.endTime"
|
|
||||||
placeholder="请选择结束时间" :editable="false"
|
|
||||||
:disabled="dialogData.data.startTime === null"
|
|
||||||
format="HH:mm" value-format="HH:mm" style="width: 150px;"/>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: flex;gap: 40px;">
|
|
||||||
<el-form-item label="提醒时间">
|
|
||||||
<el-date-picker v-model="dialogData.data.alterTime"
|
|
||||||
type="datetime" placeholder="请选择提醒时间" :editable="false"
|
|
||||||
format="YYYY-MM-DD HH:mm" value-format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
@change="changeAlterTimeEvent"
|
|
||||||
:disabled-date="disabledDate" style="width: 200px;"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="邮箱地址" prop="email">
|
|
||||||
<el-input v-model="dialogData.data.email"
|
|
||||||
placeholder="请输入邮箱地址" clearable
|
|
||||||
:disabled="dialogData.data.alterTime === null"
|
|
||||||
style="width: 200px;">
|
|
||||||
<template #prefix>
|
|
||||||
<i class="fa-solid fa-envelope"></i>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: flex;justify-content: space-between;">
|
|
||||||
<el-form-item label="类别">
|
|
||||||
<el-input v-model="dialogData.data.tag" autocomplete="off"
|
|
||||||
placeholder="请输入类别" clearable
|
|
||||||
show-word-limit maxlength="10" style="width: 150px;">
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="置顶">
|
|
||||||
<el-switch v-model="dialogData.data.isTop"
|
|
||||||
:active-value="1" :inactive-value="0"
|
|
||||||
@change="changeTopEvent"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="优先级">
|
|
||||||
<el-select v-model="dialogData.data.priority"
|
|
||||||
placeholder="请选择优先级" style="width: 150px;"
|
|
||||||
:disabled="!!dialogData.data.isTop">
|
|
||||||
<el-option v-for="(item, index) in priorityOptionList"
|
|
||||||
:key="index" :label="item.label" :value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="cancelClick">取消</el-button>
|
|
||||||
<el-button type="primary" @click="confirmClick">确认</el-button>
|
|
||||||
<el-button v-if="planStore.planApiType === 'UPDATE'"
|
|
||||||
type="danger" @click="deleteClick">删除</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ElMessage, FormInstance, FormRules } from 'element-plus'
|
|
||||||
import { planRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { computed, nextTick, reactive, ref } from 'vue'
|
|
||||||
import { isBeforeTime } from '@/utils/home/planUtil.ts'
|
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
|
||||||
import { usePlanStore } from '@/store/plan.ts'
|
|
||||||
import { useUserStore } from '@/store/user.ts'
|
|
||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
|
||||||
const rules = reactive<FormRules>(planRules)
|
|
||||||
const planStore = usePlanStore()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const dialogData = computed(() => planStore.planDialog)
|
|
||||||
|
|
||||||
const priorityOptionList = [{
|
|
||||||
value: 0,
|
|
||||||
label: '高'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 1,
|
|
||||||
label: '中'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 2,
|
|
||||||
label: '低'
|
|
||||||
}]
|
|
||||||
|
|
||||||
const disabledDate = (time: Date) => {
|
|
||||||
return time.getTime() < Date.now() - 8.64e7
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打开对话框事件
|
|
||||||
*/
|
|
||||||
const openDialogEvent = async () => {
|
|
||||||
await nextTick(() => {
|
|
||||||
formRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeDateEvent = (value: string) => {
|
|
||||||
if (!value) {
|
|
||||||
dialogData.value.data.startTime = null
|
|
||||||
dialogData.value.data.endTime = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeStartTimeEvent = (value: string) => {
|
|
||||||
if (!value) {
|
|
||||||
dialogData.value.data.endTime = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeAlterTimeEvent = (value: string) => {
|
|
||||||
if (value) {
|
|
||||||
dialogData.value.data.email = userStore.userInfo.email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeTopEvent = (value: number) => {
|
|
||||||
if (value) {
|
|
||||||
dialogData.value.data.priority = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatePlan = (): boolean => {
|
|
||||||
const { startTime, endTime } = dialogData.value.data
|
|
||||||
if (startTime && endTime) {
|
|
||||||
if (!isBeforeTime(startTime, endTime)) {
|
|
||||||
ElMessage.error('请确认结束时间大于开始时间')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击确认按钮
|
|
||||||
*/
|
|
||||||
const confirmClick = () => {
|
|
||||||
formRef.value?.validate().then(async () => {
|
|
||||||
if (validatePlan()) {
|
|
||||||
await planStore.handlePlanApi(dialogData.value.data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteClick = () => {
|
|
||||||
commonElMessageBox('是否确认删除该计划内容?').then(() => {
|
|
||||||
planStore.planApiType = 'DELETE'
|
|
||||||
planStore.handlePlanApi(dialogData.value.data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelClick = () => {
|
|
||||||
planStore.planDialog.visible = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.plan-dialog {
|
|
||||||
width:40%;
|
|
||||||
min-width: 800px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="plan-list">
|
|
||||||
<el-empty v-if="planStore.planList.length === 0" />
|
|
||||||
|
|
||||||
<el-scrollbar v-else>
|
|
||||||
<plan-card v-for="(item, index) in planStore.planList"
|
|
||||||
:key="index" :plan="item">
|
|
||||||
</plan-card>
|
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import PlanCard from '@/components/Plan/PlanCard.vue'
|
|
||||||
import { usePlanStore } from '@/store/plan.ts'
|
|
||||||
|
|
||||||
const planStore = usePlanStore()
|
|
||||||
</script>
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="common-query">
|
<div class="mobile-common-query card-item">
|
||||||
<el-select v-model="planStore.queryInfo.planType"
|
<div class="query-item">
|
||||||
@change="planStore.refreshInfo()"
|
<span>分类:</span>
|
||||||
style="width: 120px;align-self: center;">
|
<el-segmented v-model="planStore.queryInfo.planType"
|
||||||
<el-option v-for="(item, index) in planTypeOptions" :key="index"
|
:options="planTypeOptions"
|
||||||
:label="item.label" :value="item.value"/>
|
@change="planStore.refreshInfo()"/>
|
||||||
</el-select>
|
</div>
|
||||||
|
|
||||||
<el-select v-model="planStore.queryInfo.orderType"
|
<div class="query-item">
|
||||||
@change="planStore.refreshInfo()"
|
<span>排序:</span>
|
||||||
style="width: 120px;align-self: center;">
|
<el-segmented v-model="planStore.queryInfo.orderType"
|
||||||
<el-option v-for="(item, index) in orderTypeOptions" :key="index"
|
:options="orderTypeOptions"
|
||||||
:label="item.label" :value="item.value"/>
|
@change="planStore.refreshInfo()"/>
|
||||||
</el-select>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -26,20 +26,20 @@ const planTypeOptions = [{
|
|||||||
value: 'all'
|
value: 'all'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '已完成计划',
|
label: '已完成',
|
||||||
value: 'completed'
|
value: 'completed'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '未完成计划',
|
label: '未完成',
|
||||||
value: 'notCompleted'
|
value: 'notCompleted'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const orderTypeOptions = [{
|
const orderTypeOptions = [{
|
||||||
label: '按时间排序',
|
label: '时间',
|
||||||
value: 'date'
|
value: 'date'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '按重要性排序',
|
label: '重要性',
|
||||||
value: 'priority'
|
value: 'priority'
|
||||||
}]
|
}]
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="common-statistics">
|
|
||||||
<stats-card
|
|
||||||
title="计划总数" color="#CA6C65" unit="个" icon="iconfont icon-richeng"
|
|
||||||
:value="planStore.planStats.totalCount" />
|
|
||||||
|
|
||||||
<stats-card
|
|
||||||
title="完成总数" color="#6FBB69" unit="个" icon="iconfont icon-wancheng"
|
|
||||||
:value="planStore.planStats.completedCount" />
|
|
||||||
|
|
||||||
<stats-card
|
|
||||||
title="当前计划" color="#E4BF62" unit="个" icon="iconfont icon-dangqianjihua"
|
|
||||||
:value="planStore.planStats.currentCount" />
|
|
||||||
|
|
||||||
<stats-card
|
|
||||||
title="过期计划" color="#5470C6" unit="个" icon="iconfont icon-yuqi"
|
|
||||||
:value="planStore.planStats.remainCount" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import StatsCard from '@/components/StatsCard.vue'
|
|
||||||
import { usePlanStore } from '@/store/plan.ts'
|
|
||||||
|
|
||||||
const planStore = usePlanStore()
|
|
||||||
</script>
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="life-record-card card-item">
|
<div class="life-record-card card-item">
|
||||||
<el-image :src="serviceFileRootPath + props.productItem?.imageUrl"
|
<el-image :src="`${props.productItem?.imageUrl}${ThumbImageQuery}`"
|
||||||
:preview-src-list="[serviceFileRootPath + props.productItem?.imageUrl]"
|
fit="contain" alt="暂无图片" @click="viewImageClick">
|
||||||
fit="contain" alt="暂无成果">
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<span>暂无图片</span>
|
<span>暂无图片</span>
|
||||||
@@ -10,70 +9,71 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-image>
|
</el-image>
|
||||||
|
|
||||||
<div class="record-info">
|
<van-swipe-cell>
|
||||||
<div class="top-container">
|
<div class="record-info" @click="editClick()">
|
||||||
<div class="info-list">
|
<div class="top-container">
|
||||||
<div class="info-item">
|
<div class="info-list">
|
||||||
<span class="title">{{ props.productItem.name }}</span>
|
<div class="info-item">
|
||||||
|
<span class="title">{{ props.productItem.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-item amount-item">
|
||||||
|
<i class="fa-solid fa-coins"></i>
|
||||||
|
{{ props.productItem.price }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-item amount-item">
|
<el-tag v-if="props.productItem.expiryDate" type="danger" effect="dark">
|
||||||
<i class="fa-solid fa-coins"></i>
|
{{ props.productItem.expiryDate }}
|
||||||
{{ props.productItem.price }}
|
</el-tag>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-info">
|
<div class="bottom-container">
|
||||||
<el-button type="primary" :icon="Edit" circle :size="'small'"
|
<div class="info-list">
|
||||||
@click="editClick"/>
|
<div class="info-item">
|
||||||
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
<i class="fa-solid fa-clock" style="color: #6A5ACD"></i>
|
||||||
@click="deleteClick()" />
|
{{ getTotalDay(props.productItem) }}天
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fa-solid fa-chart-line" style="color: #FFA500"></i>
|
||||||
|
{{ (props.productItem.price / getTotalDay(props.productItem)).toFixed(2) }}元/天
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-list">
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fa-solid fa-flag"></i>
|
||||||
|
{{ props.productItem.purchaser }}
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fa-regular fa-calendar-check"></i>
|
||||||
|
{{ props.productItem.purchaseDate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-container">
|
<template #right>
|
||||||
<div class="info-list">
|
<van-button square type="primary" text="编辑" style="height: 100%;"
|
||||||
<div class="info-item">
|
@click="editClick"/>
|
||||||
<i class="fa-solid fa-clock" style="color: #6A5ACD"></i>
|
<van-button square type="danger" text="删除" style="height: 100%;"
|
||||||
{{ props.productItem.totalDay }}天
|
@click="deleteClick()"/>
|
||||||
</div>
|
</template>
|
||||||
|
</van-swipe-cell>
|
||||||
<div class="info-item">
|
|
||||||
<i class="fa-solid fa-chart-line" style="color: #FFA500"></i>
|
|
||||||
{{ props.productItem.averagePrice }}元/天
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-item">
|
|
||||||
<i class="fa-solid fa-location-dot"></i>
|
|
||||||
{{ props.productItem.channel }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-list">
|
|
||||||
<div class="info-item">
|
|
||||||
<i class="fa-solid fa-flag"></i>
|
|
||||||
{{ props.productItem.purchaser }}
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<i class="fa-regular fa-calendar-check"></i>
|
|
||||||
{{ props.productItem.purchaseDate }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Edit, DeleteFilled } from '@element-plus/icons-vue'
|
|
||||||
import { useProductStore } from '@/store/product.ts'
|
import { useProductStore } from '@/store/product.ts'
|
||||||
import { defineProps, onMounted, PropType } from 'vue'
|
import { defineProps, PropType } from 'vue'
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
import { IProduct } from '@/types/product.ts'
|
import { IProduct } from '@/types/product.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { ThumbImageQuery } from '@/utils'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
import { showImagePreview } from 'vant'
|
||||||
|
import { formatDate, getDateDifference } from 'vue3-common/utils/dateUtil'
|
||||||
|
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||||
|
|
||||||
const productStore = useProductStore()
|
const productStore = useProductStore()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
productItem: {
|
productItem: {
|
||||||
@@ -82,16 +82,14 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
const viewImageClick = () => {
|
||||||
|
showImagePreview({ images: [props.productItem?.imageUrl], closeable: true })
|
||||||
})
|
}
|
||||||
|
|
||||||
const editClick = async () => {
|
const editClick = async () => {
|
||||||
const id = props.productItem?.id
|
|
||||||
productStore.productApiType = 'UPDATE'
|
productStore.productApiType = 'UPDATE'
|
||||||
|
productStore.currentProduct = deepCopyObject(props.productItem) as IProduct
|
||||||
productStore.currentProduct = props.productItem as IProduct
|
productStore.productDrawer = true
|
||||||
await router.push({ name: 'ProductDetailId', params: { id } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteClick = () => {
|
const deleteClick = () => {
|
||||||
@@ -100,6 +98,14 @@ const deleteClick = () => {
|
|||||||
productStore.handleProductApi(props.productItem as IProduct)
|
productStore.handleProductApi(props.productItem as IProduct)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTotalDay = (product: IProduct) => {
|
||||||
|
if (product.expiryDate) {
|
||||||
|
return getDateDifference(product.purchaseDate, product.expiryDate, 'days')
|
||||||
|
} else {
|
||||||
|
return getDateDifference(product.purchaseDate, formatDate(new Date()), 'days')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
168
src/components/Product/ProductDrawer.vue
Normal file
168
src/components/Product/ProductDrawer.vue
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="productStore.productDrawer" destroy-on-close
|
||||||
|
:with-header="false" direction="btt" size="95%">
|
||||||
|
<div class="common-mobile-view">
|
||||||
|
<el-form ref="formRef" :model="productStore.currentProduct"
|
||||||
|
:rules="rules" label-position="top" label-width="80px"
|
||||||
|
class="form-item">
|
||||||
|
<el-form-item label="物品名称" prop="name">
|
||||||
|
<el-input v-model="productStore.currentProduct.name" autocomplete="off"
|
||||||
|
placeholder="请输入物品名称" clearable
|
||||||
|
show-word-limit maxlength="20">
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="物品价格" prop="price">
|
||||||
|
<el-input
|
||||||
|
v-model="productStore.currentProduct.price"
|
||||||
|
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="purchaseDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="productStore.currentProduct.purchaseDate"
|
||||||
|
type="date" :editable="false" placeholder="请选择购买日期"
|
||||||
|
value-format="YYYY-MM-DD"/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="购买人" prop="purchaser">
|
||||||
|
<el-select v-model="productStore.currentProduct.purchaser"
|
||||||
|
placeholder="请选择购买人" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in ['哥哥', '宝宝']"
|
||||||
|
:key="index" :label="item" :value="item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="过期日期" prop="expiryDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="productStore.currentProduct.expiryDate"
|
||||||
|
type="date" :editable="false" placeholder="请选择过期日期"
|
||||||
|
value-format="YYYY-MM-DD"/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="物品分类" prop="category">
|
||||||
|
<el-select v-model="productStore.currentProduct.category"
|
||||||
|
placeholder="请选择物品分类" clearable
|
||||||
|
:allow-create="true" :filterable="true">
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in productStore.categoryList"
|
||||||
|
:key="index" :label="item" :value="item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="购买渠道" prop="channel">
|
||||||
|
<el-input v-model="productStore.currentProduct.channel" autocomplete="off"
|
||||||
|
placeholder="请输入购买渠道" clearable
|
||||||
|
show-word-limit maxlength="10">
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div style="flex: auto">
|
||||||
|
<el-button type="primary" @click="confirmClick">确认</el-button>
|
||||||
|
<el-button type="info" @click="cancelClick">取消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useProductStore } from '@/store/product.ts'
|
||||||
|
import { ElMessage, ElMessageBox, FormRules, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus'
|
||||||
|
import { productRules } from '@/utils/element/elRules.ts'
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { acceptImageTypes } from '@/utils/file.ts'
|
||||||
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
|
import { beforeUploadImage, httpRequestFile } from '@/utils/element/elUpload.ts'
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const rules = reactive<FormRules>(productRules)
|
||||||
|
const productStore = useProductStore()
|
||||||
|
|
||||||
|
const imageFileList = ref<UploadUserFile[]>([])
|
||||||
|
|
||||||
|
watch(() => productStore.productDrawer, (value) => {
|
||||||
|
if (value) {
|
||||||
|
initImageFileList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const initImageFileList = () => {
|
||||||
|
imageFileList.value = []
|
||||||
|
const { imageUrl } = productStore.currentProduct
|
||||||
|
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(productStore.productImagePath, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSuccess = (response: string) => {
|
||||||
|
productStore.currentProduct.imageUrl = response
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleExceed: UploadProps['onExceed'] = () => {
|
||||||
|
ElMessage.warning('最多上传1张图片')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemove: UploadProps['onRemove'] = () => {
|
||||||
|
productStore.currentProduct.imageUrl = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeRemove: UploadProps['beforeRemove'] = () => {
|
||||||
|
return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击确认按钮
|
||||||
|
*/
|
||||||
|
const confirmClick = () => {
|
||||||
|
formRef.value.validate().then(async () => {
|
||||||
|
await productStore.handleProductApi(productStore.currentProduct)
|
||||||
|
productStore.productDrawer = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelClick = () => {
|
||||||
|
productStore.productDrawer = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -35,7 +35,7 @@ const queryOrderList = [
|
|||||||
label: '购买日期',
|
label: '购买日期',
|
||||||
value: 'date'
|
value: 'date'
|
||||||
}, {
|
}, {
|
||||||
label: '日均价格',
|
label: '总价格',
|
||||||
value: 'price'
|
value: 'price'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,24 +12,15 @@
|
|||||||
<el-card class="record-card">
|
<el-card class="record-card">
|
||||||
<span class="record-content">旅行人:{{ item.persons.join('-') }}</span>
|
<span class="record-content">旅行人:{{ item.persons.join('-') }}</span>
|
||||||
<el-image v-for="(image, index) in item.imageList" :key="index"
|
<el-image v-for="(image, index) in item.imageList" :key="index"
|
||||||
:src="serviceFileRootPath + image"
|
:src="`${image}${ThumbImageQuery}`"
|
||||||
:preview-src-list="[serviceFileRootPath + image]"
|
fit="cover" alt="暂无图片" class="record-image"
|
||||||
fit="contain" alt="暂无图片" class="record-image"/>
|
@click="viewImageClick(item, index)"/>
|
||||||
|
|
||||||
<video v-for="(video, index) in item.videoList"
|
<video v-for="(video, index) in item.videoList"
|
||||||
:key="index" controls style="width: 60%;min-height: 200px;">
|
:key="index" controls style="width: 100%;">
|
||||||
<source :src="serviceFileRootPath + video" type="video/mp4">
|
<source :src="video" type="video/mp4">
|
||||||
您的浏览器不支持 HTML5 视频
|
您的浏览器不支持 HTML5 视频
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<!-- <el-carousel v-if="item.imageList.length > 0"-->
|
|
||||||
<!-- arrow="always">-->
|
|
||||||
<!-- <el-carousel-item v-for="(item, index) in item.imageList" :key="index">-->
|
|
||||||
<!-- -->
|
|
||||||
<!-- </el-carousel-item>-->
|
|
||||||
<!-- </el-carousel>-->
|
|
||||||
|
|
||||||
<!-- <el-empty v-else description="暂无记录"/>-->
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-timeline-item>
|
</el-timeline-item>
|
||||||
</el-timeline>
|
</el-timeline>
|
||||||
@@ -41,7 +32,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SvgIcon } from 'vue3-common'
|
import { SvgIcon } from 'vue3-common'
|
||||||
import { useTravelStore } from '@/store/travel.ts'
|
import { useTravelStore } from '@/store/travel.ts'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
import { showImagePreview } from 'vant'
|
||||||
|
import { ITravelRecord } from '@/types/travel.ts'
|
||||||
|
import { ThumbImageQuery } from '@/utils'
|
||||||
|
|
||||||
const travelStore = useTravelStore()
|
const travelStore = useTravelStore()
|
||||||
|
|
||||||
|
const viewImageClick = (record: ITravelRecord, index: number) => {
|
||||||
|
showImagePreview({ images: record.imageList, closeable: true, startPosition: index })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
|
|
||||||
center @open="openDialogEvent" class="travel-record-dialog">
|
|
||||||
<el-form ref="formRef" :model="dialogData.data"
|
|
||||||
:rules="rules" label-width="80">
|
|
||||||
<el-form-item label="景点名称" prop="name">
|
|
||||||
<el-select
|
|
||||||
v-model="travelStore.travelRecordDialog.data.name"
|
|
||||||
@focus="travelStore.queryTravelSpotNameList()"
|
|
||||||
:disabled="travelStore.travelRecordApiType === 'UPDATE'"
|
|
||||||
placeholder="请选择景点名称">
|
|
||||||
<el-option
|
|
||||||
v-for="(item, index) in travelStore.travelSpotNameList"
|
|
||||||
:key="index" :label="item" :value="item"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="旅行日期" prop="date">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="dialogData.data.date"
|
|
||||||
type="date" placeholder="请选择日期"
|
|
||||||
value-format="YYYY-MM-DD"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="旅行人员" prop="persons">
|
|
||||||
<multi-input :data="dialogData.data.persons"
|
|
||||||
:size="5"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="图片">
|
|
||||||
<upload-image v-model="dialogData.data.imageList"
|
|
||||||
:service-url="fileServiceUrl"
|
|
||||||
:image-limit="3"
|
|
||||||
:service-file-root-path="serviceFileRootPath"
|
|
||||||
:image-path="travelStore.travelImagePath"/>
|
|
||||||
</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>
|
|
||||||
<el-button v-if="travelStore.travelRecordApiType === 'UPDATE'"
|
|
||||||
type="danger" @click="deleteClick">删除</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { UploadImage } from 'vue3-common'
|
|
||||||
import { useTravelStore } from '@/store/travel.ts'
|
|
||||||
import { ref, reactive, computed, nextTick } from 'vue'
|
|
||||||
import { FormRules } from 'element-plus'
|
|
||||||
import { travelRecordRules } from '@/utils/element/elRules.ts'
|
|
||||||
import { MultiInput } from 'vue3-common'
|
|
||||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
|
||||||
|
|
||||||
const rules = reactive<FormRules>(travelRecordRules)
|
|
||||||
|
|
||||||
const formRef = ref()
|
|
||||||
const travelStore = useTravelStore()
|
|
||||||
|
|
||||||
const dialogData = computed(() => travelStore.travelRecordDialog)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打开对话框事件
|
|
||||||
*/
|
|
||||||
const openDialogEvent = async () => {
|
|
||||||
await nextTick(() => {
|
|
||||||
formRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击确认按钮
|
|
||||||
*/
|
|
||||||
const confirmClick = () => {
|
|
||||||
formRef.value.validate().then(() => {
|
|
||||||
travelStore.handleTravelRecordApi(dialogData.value.data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelClick = () => {
|
|
||||||
dialogData.value.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteClick = () => {
|
|
||||||
commonElMessageBox('是否确认删除该记录?').then(() => {
|
|
||||||
travelStore.travelRecordApiType = 'DELETE'
|
|
||||||
travelStore.handleTravelRecordApi(dialogData.value.data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.travel-record-dialog {
|
|
||||||
width: 40%;
|
|
||||||
|
|
||||||
.el-dialog__body {
|
|
||||||
height: 45vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,68 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="life-record-card card-item">
|
<div class="life-record-card card-item">
|
||||||
<el-carousel v-if="props.travelItem?.recordList.length > 0"
|
<el-image v-if="props.travelItem?.recordList.length > 0"
|
||||||
arrow="always" @change="changeTravelEvent">
|
:src="`${props.travelItem?.recordList[0].imageList[0]}${ThumbImageQuery}`"
|
||||||
<el-carousel-item v-for="(item, index) in props.travelItem?.recordList" :key="index">
|
fit="contain" alt="暂无成果" @click="viewImageClick">
|
||||||
<el-image :src="serviceFileRootPath + item.imageList[0]"
|
<template #error>
|
||||||
fit="contain" alt="暂无图片">
|
<div class="image-slot">
|
||||||
<template #error>
|
<span>暂无图片</span>
|
||||||
<div class="image-slot">
|
</div>
|
||||||
<span>暂无图片</span>
|
</template>
|
||||||
</div>
|
</el-image>
|
||||||
</template>
|
|
||||||
</el-image>
|
|
||||||
</el-carousel-item>
|
|
||||||
</el-carousel>
|
|
||||||
|
|
||||||
<el-empty v-else description="暂无记录"/>
|
<el-empty v-else description="暂无记录"/>
|
||||||
|
|
||||||
<div class="record-info">
|
<van-swipe-cell>
|
||||||
<div class="top-container">
|
<div class="record-info" @click="viewClick">
|
||||||
<div class="info-list">
|
<div class="top-container">
|
||||||
<div class="info-item">
|
<div class="info-list">
|
||||||
<span class="title">{{ props.travelItem.name }}</span>
|
<div class="info-item">
|
||||||
|
<span class="title">{{ props.travelItem.name }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-info">
|
<div class="bottom-container">
|
||||||
<el-button type="primary" :icon="Edit" circle :size="'small'"
|
<div class="info-list">
|
||||||
@click="editClick"/>
|
<div class="info-item location-item">
|
||||||
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
|
<i class="fa-solid fa-location-dot"></i>
|
||||||
@click="viewClick()"/>
|
{{ props.travelItem.area.join('-') }}
|
||||||
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
</div>
|
||||||
@click="deleteClick()"/>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="props.travelItem?.recordList.length > 0" class="info-list">
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fa-solid fa-flag"></i>
|
||||||
|
{{ parsePerson(props.travelItem.recordList[cardInfo.currentIndex].persons) }}
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fa-regular fa-calendar-check"></i>
|
||||||
|
{{ formatDate(props.travelItem.recordList[cardInfo.currentIndex].date) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<span>暂无记录</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-container">
|
<template #right>
|
||||||
<div class="info-list">
|
<van-button square type="primary" text="编辑" style="height: 100%;"
|
||||||
<div class="info-item location-item">
|
@click="editClick"/>
|
||||||
<i class="fa-solid fa-location-dot"></i>
|
<van-button square type="danger" text="删除" style="height: 100%;"
|
||||||
{{ props.travelItem.area.join('-') }}
|
@click="deleteClick()"/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</van-swipe-cell>
|
||||||
|
|
||||||
<div v-if="props.travelItem?.recordList.length > 0" class="info-list">
|
|
||||||
<div class="info-item">
|
|
||||||
<i class="fa-solid fa-flag"></i>
|
|
||||||
{{ parsePerson(props.travelItem.recordList[cardInfo.currentIndex].persons) }}
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<i class="fa-regular fa-calendar-check"></i>
|
|
||||||
{{ formatDate(props.travelItem.recordList[cardInfo.currentIndex].date) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<span>暂无记录</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
|
|
||||||
import { useTravelStore } from '@/store/travel.ts'
|
import { useTravelStore } from '@/store/travel.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { defineProps, PropType, reactive } from 'vue'
|
import { defineProps, PropType, reactive } from 'vue'
|
||||||
@@ -70,8 +65,9 @@ import { useRouter } from 'vue-router'
|
|||||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { ITravelSpot } from '@/types/travel.ts'
|
import { ITravelSpot } from '@/types/travel.ts'
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||||
import { serviceFileRootPath } from '@/utils'
|
|
||||||
import { parsePerson } from '@/utils/home/travelUtil.ts'
|
import { parsePerson } from '@/utils/home/travelUtil.ts'
|
||||||
|
import { showImagePreview } from 'vant'
|
||||||
|
import { RawImageQuery, ThumbImageQuery } from '@/utils'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const travelStore = useTravelStore()
|
const travelStore = useTravelStore()
|
||||||
@@ -88,6 +84,13 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const viewImageClick = () => {
|
||||||
|
const imageList = props.travelItem.recordList.flatMap((record) => {
|
||||||
|
return record.imageList.map((item) => `${item}${RawImageQuery}`)
|
||||||
|
})
|
||||||
|
showImagePreview({ images: imageList, closeable: true })
|
||||||
|
}
|
||||||
|
|
||||||
const editClick = async () => {
|
const editClick = async () => {
|
||||||
const id = props.travelItem?.id
|
const id = props.travelItem?.id
|
||||||
travelStore.travelSpotApiType = 'UPDATE'
|
travelStore.travelSpotApiType = 'UPDATE'
|
||||||
@@ -108,10 +111,6 @@ const deleteClick = () => {
|
|||||||
travelStore.handleTravelSpotApi(props.travelItem)
|
travelStore.handleTravelSpotApi(props.travelItem)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeTravelEvent = (current: number) => {
|
|
||||||
cardInfo.currentIndex = current
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isMobile()" class="mobile-common-query card-item">
|
<div class="mobile-common-query card-item">
|
||||||
<div class="query-item">
|
<div class="query-item">
|
||||||
<span>城市:</span>
|
<span>城市:</span>
|
||||||
<el-segmented v-model="travelStore.querySpot.category"
|
<el-segmented v-model="travelStore.querySpot.category"
|
||||||
@@ -7,20 +7,10 @@
|
|||||||
@change="queryTravelSegmented"/>
|
@change="queryTravelSegmented"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="common-query">
|
|
||||||
<el-select v-model="travelStore.queryDateType"
|
|
||||||
@change="travelStore.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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTravelStore } from '@/store/travel.ts'
|
import { useTravelStore } from '@/store/travel.ts'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ICategory } from '@/types'
|
import { ICategory } from '@/types'
|
||||||
|
|
||||||
@@ -28,15 +18,6 @@ const travelStore = useTravelStore()
|
|||||||
|
|
||||||
const queryCategoryList = ref([] as ICategory[])
|
const queryCategoryList = ref([] as ICategory[])
|
||||||
|
|
||||||
const dateTypeOptions = [{
|
|
||||||
label: '按月选择',
|
|
||||||
value: 'month'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '按年选择',
|
|
||||||
value: 'year'
|
|
||||||
}]
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await travelStore.queryTravelCategory()
|
await travelStore.queryTravelCategory()
|
||||||
initCategoryList()
|
initCategoryList()
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PropType, defineProps, computed } from 'vue'
|
import { PropType, defineProps, computed } from 'vue'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
|
||||||
import { useKpiStore } from '@/store/kpi.ts'
|
import { useKpiStore } from '@/store/kpi.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -42,16 +40,8 @@ const editClick = async (kpi: IKpi) => {
|
|||||||
kpiStore.kpiApiType = 'UPDATE'
|
kpiStore.kpiApiType = 'UPDATE'
|
||||||
kpiStore.currentKpi = kpi
|
kpiStore.currentKpi = kpi
|
||||||
|
|
||||||
if (isMobile()) {
|
appStore.isShowMobileBack = true
|
||||||
appStore.isShowMobileBack = true
|
await router.push({ name: 'KpiDetailId', params: { id } })
|
||||||
await router.push({ name: 'KpiDetailId', params: { id } })
|
|
||||||
} else {
|
|
||||||
kpiStore.kpiDialog = {
|
|
||||||
title: '编辑绩效',
|
|
||||||
visible: true,
|
|
||||||
data: deepCopyObject(kpiStore.currentKpi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tab-bar">
|
<div class="tab-bar">
|
||||||
<router-link to="/home" @click="routerClick()" class="item">
|
|
||||||
<i class="fa-solid fa-house"></i>
|
|
||||||
<span>主页</span>
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link v-if="path.includes('bill')" to="/bill/data"
|
<router-link v-if="path.includes('bill')" to="/bill/data"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-calendar-days"></i>
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
@@ -15,10 +10,10 @@
|
|||||||
<i class="fa-solid fa-chart-pie"></i>
|
<i class="fa-solid fa-chart-pie"></i>
|
||||||
<span>统计</span>
|
<span>统计</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-if="path.includes('bill')" to="/bill/setting"
|
<router-link v-if="path.includes('bill')" to="/bill/agent"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
<span>设置</span>
|
<span>分析</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link v-if="path.includes('food')" to="/food/recipe"
|
<router-link v-if="path.includes('food')" to="/food/recipe"
|
||||||
@@ -26,6 +21,11 @@
|
|||||||
<i class="fa-solid fa-utensils"></i>
|
<i class="fa-solid fa-utensils"></i>
|
||||||
<span>菜谱</span>
|
<span>菜谱</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link v-if="path.includes('food')" to="/food/timeline"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-timeline"></i>
|
||||||
|
<span>时间轴</span>
|
||||||
|
</router-link>
|
||||||
<router-link v-if="path.includes('food')" to="/food/record"
|
<router-link v-if="path.includes('food')" to="/food/record"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-calendar-days"></i>
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
@@ -43,6 +43,17 @@
|
|||||||
<span>记录</span>
|
<span>记录</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<router-link v-if="path.includes('health')" to="/health/calendar"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
|
<span>记录</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link v-if="path.includes('health')" to="/health/chart"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-chart-pie"></i>
|
||||||
|
<span>统计</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<router-link v-if="path.includes('plan')" to="/plan/calendar"
|
<router-link v-if="path.includes('plan')" to="/plan/calendar"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-calendar-days"></i>
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
@@ -70,6 +81,11 @@
|
|||||||
<i class="fa-solid fa-calendar-days"></i>
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
<span>记录</span>
|
<span>记录</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link v-if="path.includes('period')" to="/period/status"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-chart-pie"></i>
|
||||||
|
<span>统计</span>
|
||||||
|
</router-link>
|
||||||
<router-link v-if="path.includes('period')" to="/period/setting"
|
<router-link v-if="path.includes('period')" to="/period/setting"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
@@ -82,6 +98,12 @@
|
|||||||
<span>清单</span>
|
<span>清单</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<router-link v-if="path.includes('car')" to="/car/list"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-list-ul"></i>
|
||||||
|
<span>清单</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<router-link v-if="path.includes('journal')" to="/journal/record"
|
<router-link v-if="path.includes('journal')" to="/journal/record"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-calendar-days"></i>
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
@@ -110,6 +132,11 @@
|
|||||||
<i class="fa-solid fa-list-ul"></i>
|
<i class="fa-solid fa-list-ul"></i>
|
||||||
<span>清单</span>
|
<span>清单</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link v-if="path.includes('ledger')" to="/ledger/stats"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-chart-pie"></i>
|
||||||
|
<span>统计</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<router-link v-if="path.includes('password')" to="/password/list"
|
<router-link v-if="path.includes('password')" to="/password/list"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
@@ -117,6 +144,12 @@
|
|||||||
<span>清单</span>
|
<span>清单</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<router-link v-if="path.includes('award')" to="/award/list"
|
||||||
|
@click="routerClick()" class="item">
|
||||||
|
<i class="fa-solid fa-list-ul"></i>
|
||||||
|
<span>清单</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<router-link v-if="path.includes('server')" to="/server/monitor"
|
<router-link v-if="path.includes('server')" to="/server/monitor"
|
||||||
@click="routerClick()" class="item">
|
@click="routerClick()" class="item">
|
||||||
<i class="fa-solid fa-desktop"></i>
|
<i class="fa-solid fa-desktop"></i>
|
||||||
@@ -143,12 +176,6 @@
|
|||||||
<i class="fa-solid fa-box-archive"></i>
|
<i class="fa-solid fa-box-archive"></i>
|
||||||
<span>归档</span>
|
<span>归档</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link to="/profile/index"
|
|
||||||
@click="routerClick()" class="item">
|
|
||||||
<i class="fa-solid fa-user"></i>
|
|
||||||
<span>我的</span>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
type="info" :icon="Back" size="small"
|
type="info" :icon="Back" size="small"
|
||||||
circle color="#FFFFFF"
|
circle color="#FFFFFF"
|
||||||
@click="goBackClick" />
|
@click="goBackClick" />
|
||||||
<div v-else class="logo-container">
|
<div v-else class="logo-container" @click="goHomeClick">
|
||||||
<img :src="getAssetsImageFile('logo/logo.png')" alt="暂无图片" class="logo"/>
|
<img :src="getAssetsImageFile('logo/logo.png')" alt="暂无图片" class="logo"/>
|
||||||
<span>{{ appStore.appName }}</span>
|
<span>{{ appStore.appName }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,6 +44,10 @@ const router = useRouter()
|
|||||||
|
|
||||||
const title = computed(() => router.currentRoute.value.meta.title)
|
const title = computed(() => router.currentRoute.value.meta.title)
|
||||||
|
|
||||||
|
const goHomeClick = () => {
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
|
||||||
const goBackClick = () => {
|
const goBackClick = () => {
|
||||||
appStore.isShowMobileBack = false
|
appStore.isShowMobileBack = false
|
||||||
history.back()
|
history.back()
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="layout-mobile-home">
|
<van-config-provider :theme="appStore.isDark ? 'dark' : 'light'">
|
||||||
<title-bar />
|
<section class="layout-mobile-home"
|
||||||
|
:class="{ 'hide-tab-bar': appStore.isShowMobileBack }">
|
||||||
|
<title-bar />
|
||||||
|
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|
||||||
<tab-bar />
|
<tab-bar v-if="!appStore.isShowMobileBack"/>
|
||||||
</section>
|
</section>
|
||||||
|
</van-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import TitleBar from '@/layout/TitleBar.vue'
|
import TitleBar from '@/layout/TitleBar.vue'
|
||||||
import TabBar from '@/layout/TabBar.vue'
|
import TabBar from '@/layout/TabBar.vue'
|
||||||
|
import { useAppStore } from '@/store/app.ts'
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import 'animate.css'
|
|||||||
// 注册svg-icon
|
// 注册svg-icon
|
||||||
import 'virtual:svg-icons-register'
|
import 'virtual:svg-icons-register'
|
||||||
|
|
||||||
|
// vant图片预览样式
|
||||||
|
import 'vant/es/image-preview/style'
|
||||||
|
|
||||||
// 创建Vue3实例
|
// 创建Vue3实例
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
// 使用路由
|
// 使用路由
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
230
src/mock/bill.ts
230
src/mock/bill.ts
@@ -1,230 +0,0 @@
|
|||||||
import { MockMethod } from 'vite-plugin-mock'
|
|
||||||
|
|
||||||
const billMock: MockMethod[] = [
|
|
||||||
{
|
|
||||||
url: '/home-service/bill/pay',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
id: 602111394902085,
|
|
||||||
name: '支付宝',
|
|
||||||
icon: 'fa-brands fa-alipay',
|
|
||||||
color: '#2220B2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 602130528899141,
|
|
||||||
name: '招商银行',
|
|
||||||
icon: 'fa-solid fa-money-check',
|
|
||||||
color: '#3E5BFF'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/bill/book',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
id: 601082142339141,
|
|
||||||
name: '日常账本',
|
|
||||||
icon: 'fa-solid fa-book',
|
|
||||||
color: '#1E29FF'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 601083198201925,
|
|
||||||
name: '汽车账本',
|
|
||||||
icon: 'fa-solid fa-car',
|
|
||||||
color: '#2E8B57'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/bill/category',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
bookName: '日常账本',
|
|
||||||
color: '#FF1ED6',
|
|
||||||
icon: 'fa-brands fa-shopify',
|
|
||||||
id: 601082333016133,
|
|
||||||
name: '购物',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bookName: '日常账本',
|
|
||||||
color: '#3C1EFF',
|
|
||||||
icon: 'fa-solid fa-utensils',
|
|
||||||
id: 601082911907909,
|
|
||||||
name: '餐饮',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bookName: '日常账本',
|
|
||||||
color: '#FF1ED6',
|
|
||||||
icon: 'fa-brands fa-shopify',
|
|
||||||
id: 601082333016133,
|
|
||||||
name: '购物',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bookName: '日常账本',
|
|
||||||
color: '#FF3C1E',
|
|
||||||
icon: 'fa-solid fa-sack-dollar',
|
|
||||||
id: 601422191939653,
|
|
||||||
name: '工资',
|
|
||||||
type: 'income'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bookName: '汽车账本',
|
|
||||||
color: '#1ED2FF',
|
|
||||||
icon: 'fa-solid fa-car',
|
|
||||||
id: 601083391709253,
|
|
||||||
name: '汽车保养',
|
|
||||||
type: 'expensive'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/bill/summary',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
amount: 213.32,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '购物',
|
|
||||||
content: '买东西',
|
|
||||||
date: '2024-11-11',
|
|
||||||
id: 611071966380101,
|
|
||||||
location: '埃斯顿',
|
|
||||||
payAccount: '支付宝',
|
|
||||||
remark: '',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 32.22,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '餐饮',
|
|
||||||
content: '肯德基套餐',
|
|
||||||
date: '2024-10-17',
|
|
||||||
id: 601083046211653,
|
|
||||||
location: '肯德基',
|
|
||||||
payAccount: '招商银行',
|
|
||||||
remark: '',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 213.32,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '购物',
|
|
||||||
content: '买了一件衣服',
|
|
||||||
date: '2024-10-17',
|
|
||||||
id: 601082570399813,
|
|
||||||
location: '江宁奥特莱斯',
|
|
||||||
payAccount: '招商银行',
|
|
||||||
remark: '',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 55555,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '工资',
|
|
||||||
content: '工资收入',
|
|
||||||
date: '2024-10-17',
|
|
||||||
id: 601422418636869,
|
|
||||||
location: '埃斯顿',
|
|
||||||
payAccount: '招商银行',
|
|
||||||
remark: '',
|
|
||||||
type: 'income'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 2125.23,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '餐饮',
|
|
||||||
content: '聚餐',
|
|
||||||
date: '2024-10-16',
|
|
||||||
id: 601884663570501,
|
|
||||||
location: '江宁',
|
|
||||||
payAccount: '支付宝',
|
|
||||||
remark: '',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 532.22,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '汽车保养',
|
|
||||||
content: '汽车保养',
|
|
||||||
date: '2024-10-16',
|
|
||||||
id: 601083666997317,
|
|
||||||
location: '溧水4S店',
|
|
||||||
payAccount: '支付宝',
|
|
||||||
remark: '',
|
|
||||||
type: 'expensive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 122.32,
|
|
||||||
bookName: '日常账本',
|
|
||||||
category: '餐饮',
|
|
||||||
content: '老表聚餐',
|
|
||||||
date: '2024-09-12',
|
|
||||||
id: 601876599046213,
|
|
||||||
location: '江宁',
|
|
||||||
payAccount: '招商银行',
|
|
||||||
remark: '',
|
|
||||||
type: 'expensive'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/bill/stats/year',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '2024-10',
|
|
||||||
value: 2902.9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '2024-09',
|
|
||||||
value: 122.32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '2024-11',
|
|
||||||
value: 213.32
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/bill/stats/month',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/bill/stats/book',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '日常账本',
|
|
||||||
value: 3238.54
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/bill/stats/category',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '购物',
|
|
||||||
value: 426.55
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '餐饮',
|
|
||||||
value: 2279.77
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '汽车保养',
|
|
||||||
value: 532.22
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
export default billMock
|
|
||||||
159
src/mock/blog.ts
159
src/mock/blog.ts
@@ -1,159 +0,0 @@
|
|||||||
import { MockMethod } from 'vite-plugin-mock'
|
|
||||||
|
|
||||||
const blogMock: MockMethod[] = [
|
|
||||||
{
|
|
||||||
url: '/blog-service/blog/stats',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return {
|
|
||||||
blogCount: 10,
|
|
||||||
categoryCount: 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/blog-service/blog/latest',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
id: 591259203186757,
|
|
||||||
title: '一篇标题很长的博客嘿嘿嘿哦哦哦哦哦哦哦'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 591837462601797,
|
|
||||||
title: '锵锵锵'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 591137032622149,
|
|
||||||
title: '台风'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 591317392101445,
|
|
||||||
title: '一篇新的2336'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 591317151170629,
|
|
||||||
title: '新的一篇文章233500'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/blog-service/blog/category',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
count: 7,
|
|
||||||
name: '学习'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
count: 3,
|
|
||||||
name: '工作'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/blog-service/blog/page',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return {
|
|
||||||
current: 1,
|
|
||||||
pages: 2,
|
|
||||||
records: [
|
|
||||||
{
|
|
||||||
category: '学习',
|
|
||||||
content: null,
|
|
||||||
createTime: '2024-09-18 10:52:50',
|
|
||||||
id: 591837462601797,
|
|
||||||
isGreat: true,
|
|
||||||
readDuration: 1.27,
|
|
||||||
summary: '那半弯新月悬挂于空,洒落一地的晶莹。\n\n夜已被月洗白,月下,匆匆的步履停顿,凝望,清点心中如帛的情丝。\n月透着初秋的韵致,想念已悄然坠临,无语凝噎的痛楚在心间扩散,丰盈的思念在秋夜沉迷,抒这夜的情愁,',
|
|
||||||
title: '锵锵锵',
|
|
||||||
topValue: 3,
|
|
||||||
updateTime: '2024-09-18 10:57:20',
|
|
||||||
visitCount: 19,
|
|
||||||
wordCount: 761
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: '学习',
|
|
||||||
content: null,
|
|
||||||
createTime: '2024-09-16 11:22:47',
|
|
||||||
id: 591837462601797,
|
|
||||||
isGreat: true,
|
|
||||||
readDuration: 1.27,
|
|
||||||
summary: '# 台风12312321312312',
|
|
||||||
title: '锵锵锵',
|
|
||||||
topValue: 3,
|
|
||||||
updateTime: '2024-09-17 00:16:31',
|
|
||||||
visitCount: 19,
|
|
||||||
wordCount: 761
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: '学习',
|
|
||||||
content: null,
|
|
||||||
createTime: '2024-09-18 10:52:50',
|
|
||||||
id: 591837462601797,
|
|
||||||
isGreat: true,
|
|
||||||
readDuration: 1.27,
|
|
||||||
summary: '那半弯新月悬挂于空,洒落一地的晶莹。\n\n夜已被月洗白,月下,匆匆的步履停顿,凝望,清点心中如帛的情丝。\n月透着初秋的韵致,想念已悄然坠临,无语凝噎的痛楚在心间扩散,丰盈的思念在秋夜沉迷,抒这夜的情愁,',
|
|
||||||
title: '锵锵锵',
|
|
||||||
topValue: 3,
|
|
||||||
updateTime: '2024-09-18 10:57:20',
|
|
||||||
visitCount: 19,
|
|
||||||
wordCount: 761
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: '学习',
|
|
||||||
content: null,
|
|
||||||
createTime: '2024-09-18 10:52:50',
|
|
||||||
id: 591837462601797,
|
|
||||||
isGreat: true,
|
|
||||||
readDuration: 1.27,
|
|
||||||
summary: '那半弯新月悬挂于空,洒落一地的晶莹。\n\n夜已被月洗白,月下,匆匆的步履停顿,凝望,清点心中如帛的情丝。\n月透着初秋的韵致,想念已悄然坠临,无语凝噎的痛楚在心间扩散,丰盈的思念在秋夜沉迷,抒这夜的情愁,',
|
|
||||||
title: '锵锵锵',
|
|
||||||
topValue: 3,
|
|
||||||
updateTime: '2024-09-18 10:57:20',
|
|
||||||
visitCount: 19,
|
|
||||||
wordCount: 761
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: '学习',
|
|
||||||
content: null,
|
|
||||||
createTime: '2024-09-18 10:52:50',
|
|
||||||
id: 591837462601797,
|
|
||||||
isGreat: true,
|
|
||||||
readDuration: 1.27,
|
|
||||||
summary: '那半弯新月悬挂于空,洒落一地的晶莹。\n\n夜已被月洗白,月下,匆匆的步履停顿,凝望,清点心中如帛的情丝。\n月透着初秋的韵致,想念已悄然坠临,无语凝噎的痛楚在心间扩散,丰盈的思念在秋夜沉迷,抒这夜的情愁,',
|
|
||||||
title: '锵锵锵',
|
|
||||||
topValue: 3,
|
|
||||||
updateTime: '2024-09-18 10:57:20',
|
|
||||||
visitCount: 19,
|
|
||||||
wordCount: 761
|
|
||||||
}
|
|
||||||
],
|
|
||||||
size: 5,
|
|
||||||
total: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/blog-service/blog/content/591837462601797',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return {
|
|
||||||
category: '学习',
|
|
||||||
content: '# 台风12312321312312',
|
|
||||||
createTime: '2024-09-16 11:22:47',
|
|
||||||
id: 591137032622149,
|
|
||||||
isGreat: true,
|
|
||||||
readDuration: 0.03,
|
|
||||||
summary: '# 台风12312321312312',
|
|
||||||
title: '台风',
|
|
||||||
topValue: 3,
|
|
||||||
updateTime: '2024-09-17 00:16:31',
|
|
||||||
visitCount: 1,
|
|
||||||
wordCount: 17
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
export default blogMock
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import { MockMethod } from 'vite-plugin-mock'
|
|
||||||
|
|
||||||
const budgetMock: MockMethod[] = [
|
|
||||||
{
|
|
||||||
url: '/home-service/budget/category',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return ['结婚预算']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/budget',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
actual: 26000,
|
|
||||||
budget: 23123,
|
|
||||||
category: '结婚预算',
|
|
||||||
content: '10.2午宴',
|
|
||||||
id: 611115630714949,
|
|
||||||
payTime: null,
|
|
||||||
project: '婚宴',
|
|
||||||
remake: '5桌',
|
|
||||||
vendor: '世纪缘酒店'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
actual: 680,
|
|
||||||
budget: 700,
|
|
||||||
category: '结婚预算',
|
|
||||||
content: '看结婚日子',
|
|
||||||
id: 611116171505733,
|
|
||||||
payTime: null,
|
|
||||||
project: '吉日',
|
|
||||||
remake: '',
|
|
||||||
vendor: '算命先生'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
actual: 0,
|
|
||||||
budget: 0,
|
|
||||||
category: '结婚预算',
|
|
||||||
content: '化妆师',
|
|
||||||
id: 611404639944773,
|
|
||||||
payTime: null,
|
|
||||||
project: '婚庆',
|
|
||||||
remake: '',
|
|
||||||
vendor: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
actual: 0,
|
|
||||||
budget: 0,
|
|
||||||
category: '结婚预算',
|
|
||||||
content: '主持人',
|
|
||||||
id: 611404716798021,
|
|
||||||
payTime: null,
|
|
||||||
project: '婚庆',
|
|
||||||
remake: '',
|
|
||||||
vendor: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
actual: 10000,
|
|
||||||
budget: 10000,
|
|
||||||
category: '结婚预算',
|
|
||||||
content: '婚车',
|
|
||||||
id: 611404772474949,
|
|
||||||
payTime: null,
|
|
||||||
project: '婚庆',
|
|
||||||
remake: '',
|
|
||||||
vendor: ''
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/budget/stats/rank',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '10.2午宴',
|
|
||||||
value: 26000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '婚车',
|
|
||||||
value: 10000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '看结婚日子',
|
|
||||||
value: 680
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '化妆师',
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '主持人',
|
|
||||||
value: 0
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/budget/stats/project',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '婚宴',
|
|
||||||
value: 26000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '吉日',
|
|
||||||
value: 680
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '婚庆',
|
|
||||||
value: 10000
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
export default budgetMock
|
|
||||||
166
src/mock/food.ts
166
src/mock/food.ts
@@ -1,166 +0,0 @@
|
|||||||
import { MockMethod } from 'vite-plugin-mock'
|
|
||||||
|
|
||||||
const foodMock: MockMethod[] = [
|
|
||||||
{
|
|
||||||
url: '/home-service/food/origin',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return ['中餐']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/food/summary/page',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return {
|
|
||||||
current: 1,
|
|
||||||
pages: 1,
|
|
||||||
records: [
|
|
||||||
{
|
|
||||||
id: 614660173221957,
|
|
||||||
name: '鱼头汤',
|
|
||||||
recommendRate: 5,
|
|
||||||
workList: [
|
|
||||||
{
|
|
||||||
finishBy: '哥哥',
|
|
||||||
finishTime: '2024-11-13',
|
|
||||||
id: 614673962934342,
|
|
||||||
imageUrl: 'files\\food\\image2.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
finishBy: '哥哥',
|
|
||||||
finishTime: '2024-11-20',
|
|
||||||
id: 614673962934341,
|
|
||||||
imageUrl: 'files\\food\\image1.png'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 614665900060741,
|
|
||||||
name: '红烧肉',
|
|
||||||
recommendRate: 3,
|
|
||||||
workList: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 614668663291973,
|
|
||||||
name: '红烧排骨',
|
|
||||||
recommendRate: 5,
|
|
||||||
workList: []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
size: 6,
|
|
||||||
total: 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/food/614660173221957',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return {
|
|
||||||
attention: '',
|
|
||||||
category: '淮扬菜',
|
|
||||||
cuisine: '清炖',
|
|
||||||
id: 614660173221957,
|
|
||||||
materialList: [
|
|
||||||
{
|
|
||||||
amount: '一个鱼头',
|
|
||||||
id: 614673962901573,
|
|
||||||
name: '胖头鱼',
|
|
||||||
type: '主料'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: '一块',
|
|
||||||
id: 614673962901574,
|
|
||||||
name: '豆腐',
|
|
||||||
type: '辅料'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: '1小块',
|
|
||||||
id: 614673962901575,
|
|
||||||
name: '生姜',
|
|
||||||
type: '调料'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: '2勺',
|
|
||||||
id: 614673962901576,
|
|
||||||
name: '生抽',
|
|
||||||
type: '调料'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: '鱼头汤',
|
|
||||||
nutritiveValue: '',
|
|
||||||
origin: '中餐',
|
|
||||||
recommendRate: 5,
|
|
||||||
remark: null,
|
|
||||||
stepList: [{
|
|
||||||
content: '先把鱼头剁开',
|
|
||||||
id: 614673962917957,
|
|
||||||
imageUrl: '',
|
|
||||||
sort: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '洗鱼',
|
|
||||||
id: 614673962917958,
|
|
||||||
imageUrl: '',
|
|
||||||
sort: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '倒水放砂锅里煮',
|
|
||||||
id: 614673962917959,
|
|
||||||
imageUrl: '',
|
|
||||||
sort: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '放豆腐',
|
|
||||||
id: 614673962917960,
|
|
||||||
imageUrl: '',
|
|
||||||
sort: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '放盐',
|
|
||||||
id: 614673962917961,
|
|
||||||
imageUrl: '',
|
|
||||||
sort: 4
|
|
||||||
}],
|
|
||||||
workList: [
|
|
||||||
{
|
|
||||||
finishBy: '哥哥',
|
|
||||||
finishTime: '2024-11-20',
|
|
||||||
id: 614673962934341,
|
|
||||||
imageUrl: 'files\\food\\image1.png'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/food/stats/category',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '淮扬菜',
|
|
||||||
value: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '鲁菜',
|
|
||||||
value: 2
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/home-service/food/stats/cuisine',
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return [{
|
|
||||||
name: '清炖',
|
|
||||||
value: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '红烧',
|
|
||||||
value: 2
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
export default foodMock
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import BillMock from './bill'
|
|
||||||
import BudgetMock from './budget'
|
|
||||||
import FoodMock from './food'
|
|
||||||
|
|
||||||
export default [
|
|
||||||
...BillMock,
|
|
||||||
...BudgetMock,
|
|
||||||
...FoodMock
|
|
||||||
]
|
|
||||||
@@ -32,7 +32,8 @@ export const useAnniversaryStore = defineStore('anniversary', {
|
|||||||
title: '',
|
title: '',
|
||||||
data: {}
|
data: {}
|
||||||
} as IDialog<IAnniversary>,
|
} as IDialog<IAnniversary>,
|
||||||
isRefresh: false
|
isRefresh: false,
|
||||||
|
isLoading: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async queryAnniversaryList() {
|
async queryAnniversaryList() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { setCookie, getCookie, getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
import { setCookie, getCookie, getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
||||||
import { useDark, useToggle } from '@vueuse/core'
|
import { useDark, useToggle } from '@vueuse/core'
|
||||||
|
import { calcDarken, calcLighten } from '@/utils/element/elColor.ts'
|
||||||
|
|
||||||
export const useAppStore = defineStore('ipp', {
|
export const useAppStore = defineStore('ipp', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -89,7 +90,17 @@ export const useAppStore = defineStore('ipp', {
|
|||||||
return getLocalStorage('theme-color') as string || '#009FA8'
|
return getLocalStorage('theme-color') as string || '#009FA8'
|
||||||
},
|
},
|
||||||
setThemeColor(value: string) {
|
setThemeColor(value: string) {
|
||||||
document.documentElement.style.setProperty('--el-color-primary', value)
|
const root = document.documentElement
|
||||||
|
|
||||||
|
root.style.setProperty('--el-color-primary', value)
|
||||||
|
root.style.setProperty('--el-color-primary-light-3', calcLighten(value, 30))
|
||||||
|
root.style.setProperty('--el-color-primary-light-5', calcLighten(value, 50))
|
||||||
|
root.style.setProperty('--el-color-primary-light-7', calcLighten(value, 70))
|
||||||
|
root.style.setProperty('--el-color-primary-light-8', calcLighten(value, 80))
|
||||||
|
root.style.setProperty('--el-color-primary-light-9', calcLighten(value, 90))
|
||||||
|
root.style.setProperty('--el-color-primary-dark-2', calcDarken(value, 20))
|
||||||
|
|
||||||
|
root.style.setProperty('--van-primary-color', value)
|
||||||
setLocalStorage('theme-color', value)
|
setLocalStorage('theme-color', value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
src/store/award.ts
Normal file
70
src/store/award.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import type { IHandleApi } from 'vue3-common/types'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { ImagePathEnum } from '@/types'
|
||||||
|
import { IAward, IAwardQuery } from '@/types/award.ts'
|
||||||
|
import { addAwardApi, deleteAwardApi, queryAwardListApi, updateAwardApi } from '@/apis/award.ts'
|
||||||
|
|
||||||
|
export const useAwardStore = defineStore('award', {
|
||||||
|
state: () => ({
|
||||||
|
awardList: [] as IAward[],
|
||||||
|
queryInfo: {
|
||||||
|
type: ''
|
||||||
|
} as IAwardQuery,
|
||||||
|
awardApiType: 'ADD' as IHandleApi,
|
||||||
|
currentAward: {
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
owner: '',
|
||||||
|
date: '',
|
||||||
|
imageList: [] as string[]
|
||||||
|
} as IAward,
|
||||||
|
awardImagePath: ImagePathEnum.AWARD,
|
||||||
|
pageInfo: {
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 3,
|
||||||
|
totalSize: 1
|
||||||
|
},
|
||||||
|
isScrollEnd: false,
|
||||||
|
isRefresh: false
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async queryAwardList() {
|
||||||
|
this.awardList = await queryAwardListApi(this.queryInfo)
|
||||||
|
},
|
||||||
|
async handleAwardApi(award: IAward) {
|
||||||
|
switch (this.awardApiType) {
|
||||||
|
case 'ADD':
|
||||||
|
await addAwardApi(award)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
break
|
||||||
|
case 'UPDATE':
|
||||||
|
await updateAwardApi(award.id as number, award)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
break
|
||||||
|
case 'DELETE':
|
||||||
|
await deleteAwardApi(award.id as number)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async refreshInfo() {
|
||||||
|
await this.queryAwardList()
|
||||||
|
this.isRefresh = !this.isRefresh
|
||||||
|
},
|
||||||
|
getEmptyAward(): IAward {
|
||||||
|
return {
|
||||||
|
id: 0,
|
||||||
|
date: '',
|
||||||
|
name: '',
|
||||||
|
owner: '',
|
||||||
|
type: '',
|
||||||
|
location: '',
|
||||||
|
imageList: [],
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -8,13 +8,12 @@ import {
|
|||||||
queryBillBookApi,
|
queryBillBookApi,
|
||||||
queryBillCategoryApi, queryBillPayApi,
|
queryBillCategoryApi, queryBillPayApi,
|
||||||
queryBillRecordApi,
|
queryBillRecordApi,
|
||||||
queryBillStatsApi, queryBillSummaryApi, updateBillRecordApi
|
queryBillStatsApi, queryBillSummaryApi, searchBillSummaryApi, updateBillRecordApi
|
||||||
} from '@/apis/bill'
|
} from '@/apis/bill'
|
||||||
import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
import { formatDate, getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { IDateType, ImagePathEnum } from '@/types'
|
import { IDateType, ImagePathEnum } from '@/types'
|
||||||
import { IDialog, IStatsChart, IHandleApi } from 'vue3-common/types'
|
import { IStatsChart, IHandleApi } from 'vue3-common/types'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
|
|
||||||
export const useBillStore = defineStore('bill', {
|
export const useBillStore = defineStore('bill', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -35,7 +34,7 @@ export const useBillStore = defineStore('bill', {
|
|||||||
billPayList: [] as IBillPay[],
|
billPayList: [] as IBillPay[],
|
||||||
billBookList: [] as IBillBook[],
|
billBookList: [] as IBillBook[],
|
||||||
billCategoryList: [] as IBillCategory[],
|
billCategoryList: [] as IBillCategory[],
|
||||||
billType: 'expensive' as IBillType,
|
billType: 'expense' as IBillType,
|
||||||
billDateType: 'month' as IDateType,
|
billDateType: 'month' as IDateType,
|
||||||
billDateList: [] as string[],
|
billDateList: [] as string[],
|
||||||
billImagePath: ImagePathEnum.BILL,
|
billImagePath: ImagePathEnum.BILL,
|
||||||
@@ -44,15 +43,16 @@ export const useBillStore = defineStore('bill', {
|
|||||||
billYear: getCurrentYear(),
|
billYear: getCurrentYear(),
|
||||||
billMonth: getCurrentMonth()
|
billMonth: getCurrentMonth()
|
||||||
},
|
},
|
||||||
|
billSearchForm: {
|
||||||
|
content: '',
|
||||||
|
category: '',
|
||||||
|
location: ''
|
||||||
|
},
|
||||||
billDateStatsList: [] as IStatsChart[],
|
billDateStatsList: [] as IStatsChart[],
|
||||||
billBookStatsList: [] as IStatsChart[],
|
billBookStatsList: [] as IStatsChart[],
|
||||||
billCategoryStatsList: [] as IStatsChart[],
|
billCategoryStatsList: [] as IStatsChart[],
|
||||||
billApiType: 'ADD' as IHandleApi,
|
billApiType: 'ADD' as IHandleApi,
|
||||||
billDialog: {
|
billDrawer: false,
|
||||||
title: '',
|
|
||||||
visible: false,
|
|
||||||
data: {}
|
|
||||||
} as IDialog<IBillRecord>,
|
|
||||||
isRefresh: false,
|
isRefresh: false,
|
||||||
isLoading: false
|
isLoading: false
|
||||||
}),
|
}),
|
||||||
@@ -119,31 +119,39 @@ export const useBillStore = defineStore('bill', {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.billDialog.visible = false
|
await this.refreshInfo()
|
||||||
|
|
||||||
if (!isMobile()) {
|
|
||||||
await this.refreshInfo()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async refreshInfo() {
|
getBillDateList() {
|
||||||
this.isLoading = true
|
|
||||||
const bookName = this.billQueryForm.billBook === 'all' ? '' : this.billQueryForm.billBook
|
|
||||||
const { billYear, billMonth } = this.billQueryForm
|
const { billYear, billMonth } = this.billQueryForm
|
||||||
const date = this.billDateType === 'year' ? billYear : billMonth
|
const date = this.billDateType === 'year' ? billYear : billMonth
|
||||||
this.billDateList = getStartEndDate(date, this.billDateType)
|
this.billDateList = getStartEndDate(date, this.billDateType)
|
||||||
|
},
|
||||||
|
async refreshInfo(isStats = false) {
|
||||||
|
this.isLoading = true
|
||||||
|
const bookName = this.billQueryForm.billBook === 'all' ? '' : this.billQueryForm.billBook
|
||||||
|
this.getBillDateList()
|
||||||
|
|
||||||
await this.queryBillSummary(bookName)
|
await this.queryBillSummary(bookName)
|
||||||
await this.queryBillStats(bookName)
|
|
||||||
|
if (isStats) {
|
||||||
|
await this.queryBillStats(bookName)
|
||||||
|
}
|
||||||
|
|
||||||
this.isRefresh = !this.isRefresh
|
this.isRefresh = !this.isRefresh
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
},
|
},
|
||||||
|
async searchInfo() {
|
||||||
|
this.isLoading = true
|
||||||
|
this.billRecordList = await searchBillSummaryApi(this.billSearchForm)
|
||||||
|
this.isLoading = false
|
||||||
|
},
|
||||||
getEmptyBillRecord(): IBillRecord {
|
getEmptyBillRecord(): IBillRecord {
|
||||||
return {
|
return {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
bookName: '',
|
bookName: '',
|
||||||
category: '',
|
category: '',
|
||||||
content: '',
|
content: '',
|
||||||
date: '',
|
date: formatDate(new Date()),
|
||||||
location: '',
|
location: '',
|
||||||
payAccount: '',
|
payAccount: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import {
|
|||||||
IBlogLatest, IBlogNestedComment, IBlogStats, INewBlog, IQueryBlog
|
IBlogLatest, IBlogNestedComment, IBlogStats, INewBlog, IQueryBlog
|
||||||
} from '@/types/blog'
|
} from '@/types/blog'
|
||||||
import {
|
import {
|
||||||
addBlogApi, addBlogCommentApi, deleteBlogApi,
|
|
||||||
queryBlogApi, queryBlogByConditionApi, queryBlogByIdApi,
|
queryBlogApi, queryBlogByConditionApi, queryBlogByIdApi,
|
||||||
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi, updateBlogApi
|
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi
|
||||||
} from '@/apis/blog.ts'
|
} from '@/apis/blog.ts'
|
||||||
import { IHandleApi } from 'vue3-common/types'
|
import { IHandleApi } from 'vue3-common/types'
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { ImagePathEnum } from '@/types'
|
import { ImagePathEnum } from '@/types'
|
||||||
import { flattenToTwoLevel } from '@/utils/blog/blogUtil.ts'
|
import { flattenToTwoLevel } from '@/utils/blog/blogUtil.ts'
|
||||||
|
|
||||||
@@ -90,10 +88,6 @@ export const useBlogStore = defineStore('blog', {
|
|||||||
async queryBlogComment(blogId: number) {
|
async queryBlogComment(blogId: number) {
|
||||||
this.blogCommentList = await queryBlogCommentApi(blogId)
|
this.blogCommentList = await queryBlogCommentApi(blogId)
|
||||||
},
|
},
|
||||||
async addBlogComment(blogComment: IBlogComment) {
|
|
||||||
await addBlogCommentApi(blogComment)
|
|
||||||
await this.queryBlogById(blogComment.blogId)
|
|
||||||
},
|
|
||||||
async queryBlogCategory() {
|
async queryBlogCategory() {
|
||||||
this.blogCategoryList = await queryBlogCategoryApi()
|
this.blogCategoryList = await queryBlogCategoryApi()
|
||||||
},
|
},
|
||||||
@@ -103,25 +97,6 @@ export const useBlogStore = defineStore('blog', {
|
|||||||
async queryLatestBlog() {
|
async queryLatestBlog() {
|
||||||
this.latestBlogList = await queryLatestBlogApi()
|
this.latestBlogList = await queryLatestBlogApi()
|
||||||
},
|
},
|
||||||
async handleBlogApi(id?: number) {
|
|
||||||
switch (this.blogApiType) {
|
|
||||||
case 'ADD':
|
|
||||||
await addBlogApi(this.newBlog)
|
|
||||||
ElMessage.success('新增博客成功')
|
|
||||||
break
|
|
||||||
case 'UPDATE':
|
|
||||||
await updateBlogApi(this.newBlog.id as number, this.newBlog)
|
|
||||||
ElMessage.success('更新博客成功')
|
|
||||||
break
|
|
||||||
case 'DELETE':
|
|
||||||
await deleteBlogApi(id as number)
|
|
||||||
ElMessage.success('删除博客成功')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await this.refreshInfo()
|
|
||||||
},
|
|
||||||
async refreshInfo() {
|
async refreshInfo() {
|
||||||
this.pageInfo.currentPage = 1
|
this.pageInfo.currentPage = 1
|
||||||
await this.queryBlog()
|
await this.queryBlog()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user