Compare commits
10 Commits
0346586548
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8537e6d956 | |||
| 0fa8cfb79f | |||
| 80139c54d1 | |||
| b4ebc0e35b | |||
| 45a129ea94 | |||
| 1d0cb061d0 | |||
| 1037edeed4 | |||
| b6f7869fe8 | |||
| be8e105a55 | |||
| 50991bb49a |
6
Dockerfile
Normal file
6
Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM nginx:1.27.0
|
||||||
|
COPY dist/ /usr/share/nginx/html/
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||||
|
RUN echo 'Asia/Shanghai' > /etc/timezone
|
||||||
|
RUN chmod -R 755 /usr/share/nginx/html/
|
||||||
23
nginx.conf
Normal file
23
nginx.conf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location /food-hub/ {
|
||||||
|
alias /usr/share/nginx/html/;
|
||||||
|
try_files $uri $uri/ /food-hub/index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /food-api/ {
|
||||||
|
proxy_pass http://host.docker.internal:8083/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -22,7 +22,7 @@ importers:
|
|||||||
version: 3.5.17(typescript@5.8.3)
|
version: 3.5.17(typescript@5.8.3)
|
||||||
vue3-common:
|
vue3-common:
|
||||||
specifier: git+https://gitee.com/Cxx0822/vue3-common
|
specifier: git+https://gitee.com/Cxx0822/vue3-common
|
||||||
version: git+https://gitee.com/Cxx0822/vue3-common#4cabccc268f404bef9fd13078ff88fc2830b7461(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint-config-standard@16.0.3(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(pinia@2.3.1(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)))(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
|
version: git+https://gitee.com/Cxx0822/vue3-common#0d4a3beb7b1bfb068fe6672c25839997cd8d2129(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint-config-standard@16.0.3(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(pinia@2.3.1(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)))(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
|
||||||
vue3-emoji-picker:
|
vue3-emoji-picker:
|
||||||
specifier: ^1.1.8
|
specifier: ^1.1.8
|
||||||
version: 1.1.8(typescript@5.8.3)
|
version: 1.1.8(typescript@5.8.3)
|
||||||
@@ -1220,8 +1220,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
form-data@4.0.3:
|
form-data@4.0.4:
|
||||||
resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
|
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
@@ -2022,8 +2022,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=5.0.0'
|
typescript: '>=5.0.0'
|
||||||
|
|
||||||
vue3-common@git+https://gitee.com/Cxx0822/vue3-common#4cabccc268f404bef9fd13078ff88fc2830b7461:
|
vue3-common@git+https://gitee.com/Cxx0822/vue3-common#0d4a3beb7b1bfb068fe6672c25839997cd8d2129:
|
||||||
resolution: {commit: 4cabccc268f404bef9fd13078ff88fc2830b7461, repo: https://gitee.com/Cxx0822/vue3-common, type: git}
|
resolution: {commit: 0d4a3beb7b1bfb068fe6672c25839997cd8d2129, repo: https://gitee.com/Cxx0822/vue3-common, type: git}
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
pinia: ^2.3.0
|
pinia: ^2.3.0
|
||||||
@@ -2727,7 +2727,7 @@ snapshots:
|
|||||||
axios@1.10.0:
|
axios@1.10.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.9
|
follow-redirects: 1.15.9
|
||||||
form-data: 4.0.3
|
form-data: 4.0.4
|
||||||
proxy-from-env: 1.1.0
|
proxy-from-env: 1.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
@@ -3284,7 +3284,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
|
|
||||||
form-data@4.0.3:
|
form-data@4.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
asynckit: 0.4.0
|
asynckit: 0.4.0
|
||||||
combined-stream: 1.0.8
|
combined-stream: 1.0.8
|
||||||
@@ -4119,7 +4119,7 @@ snapshots:
|
|||||||
'@vue/language-core': 2.2.10(typescript@5.8.3)
|
'@vue/language-core': 2.2.10(typescript@5.8.3)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
vue3-common@git+https://gitee.com/Cxx0822/vue3-common#4cabccc268f404bef9fd13078ff88fc2830b7461(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint-config-standard@16.0.3(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(pinia@2.3.1(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)))(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)):
|
vue3-common@git+https://gitee.com/Cxx0822/vue3-common#0d4a3beb7b1bfb068fe6672c25839997cd8d2129(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@5.8.3))(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint-config-standard@16.0.3(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.3))(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(pinia@2.3.1(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)))(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fortawesome/fontawesome-free': 6.7.2
|
'@fortawesome/fontawesome-free': 6.7.2
|
||||||
'@popperjs/core': 2.11.8
|
'@popperjs/core': 2.11.8
|
||||||
|
|||||||
@@ -4,33 +4,45 @@ import type { IStatsChart } from 'vue3-common/types'
|
|||||||
|
|
||||||
export const addRecipeApi = (recipe: IRecipe): Promise<boolean> =>
|
export const addRecipeApi = (recipe: IRecipe): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/recipe',
|
url: '/food-api/food/recipe',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: recipe
|
data: recipe
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateRecipeApi = (id: number, recipe: IRecipe): Promise<boolean> =>
|
export const updateRecipeApi = (id: number, recipe: IRecipe): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/food/recipe/${id}`,
|
url: `/food-api/food/recipe/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: recipe
|
data: recipe
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteRecipeApi = (id: number): Promise<boolean> =>
|
export const deleteRecipeApi = (id: number): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/food/recipe/${id}`,
|
url: `/food-api/food/recipe/${id}`,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryRecipeByIdApi = (id: number): Promise<IRecipe> =>
|
export const queryRecipeByIdApi = (id: number): Promise<IRecipe> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/food/recipe/${id}`,
|
url: `/food-api/food/recipe/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const queryRecipeByUserApi = (id: number): Promise<IRecipe[]> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/food/recipe/user/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const queryRecipeUserFavouriteApi = (): Promise<IRecipe[]> =>
|
||||||
|
cloudService({
|
||||||
|
url: '/food-api/food/recipe/user/favourite',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryRecipeApi = (query: IFoodQuery): Promise<IRecipe[]> =>
|
export const queryRecipeApi = (query: IFoodQuery): Promise<IRecipe[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/recipe',
|
url: '/food-api/food/recipe',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { ...query }
|
params: { ...query }
|
||||||
})
|
})
|
||||||
@@ -38,70 +50,101 @@ export const queryRecipeApi = (query: IFoodQuery): Promise<IRecipe[]> =>
|
|||||||
export const queryRecipeByPageApi = (currentPage: number, pageSize: number,
|
export const queryRecipeByPageApi = (currentPage: number, pageSize: number,
|
||||||
query: IFoodQuery): Promise<any> =>
|
query: IFoodQuery): Promise<any> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/recipe/page',
|
url: '/food-api/food/recipe/page',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { currentPage, pageSize, ...query }
|
params: { currentPage, pageSize, ...query }
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryFoodNameListApi = (): Promise<string[]> =>
|
export const queryFoodNameListApi = (): Promise<string[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/recipe/name',
|
url: '/food-api/food/recipe/name',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addRecordApi = (record: IRecord): Promise<boolean> =>
|
export const addRecordApi = (record: IRecord): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/record',
|
url: '/food-api/food/record',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: record
|
data: record
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateRecordApi = (id: number, record: IRecord): Promise<boolean> =>
|
export const updateRecordApi = (id: number, record: IRecord): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/food/record/${id}`,
|
url: `/food-api/food/record/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: record
|
data: record
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteRecordApi = (id: number): Promise<boolean> =>
|
export const deleteRecordApi = (id: number): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/food/record/${id}`,
|
url: `/food-api/food/record/${id}`,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const addRecipeCommentApi = (id: number, content: string): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/food/recipe/${id}/comment`,
|
||||||
|
method: 'post',
|
||||||
|
params: { content }
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addRecipeLikeApi = (id: number): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/food/recipe/${id}/like`,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const deleteRecipeLikeApi = (id: number): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/food/recipe/${id}/like`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addRecipeFavouriteApi = (id: number): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/food/recipe/${id}/favourite`,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const deleteRecipeFavouriteApi = (id: number): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/food/recipe/${id}/favourite`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
|
||||||
export const queryRecordApi = (startDate: string, endDate: string): Promise<IRecord[]> =>
|
export const queryRecordApi = (startDate: string, endDate: string): Promise<IRecord[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/record',
|
url: '/food-api/food/record',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { startDate, endDate }
|
params: { startDate, endDate }
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryCategoryApi = (): Promise<string[]> =>
|
export const queryCategoryApi = (): Promise<string[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/category',
|
url: '/food-api/food/category',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryStatsApi = (): Promise<IStats> =>
|
export const queryStatsApi = (): Promise<IStats> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/stats',
|
url: '/food-api/food/stats',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryRecordStatsApi = (): Promise<IStatsChart[]> =>
|
export const queryRecordStatsApi = (): Promise<IStatsChart[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/stats/record',
|
url: '/food-api/food/stats/record',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryCategoryStatsApi = (): Promise<IStatsChart[]> =>
|
export const queryCategoryStatsApi = (): Promise<IStatsChart[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/stats/category',
|
url: '/food-api/food/stats/category',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryRankStatsApi = (): Promise<IStatsChart[]> =>
|
export const queryRankStatsApi = (): Promise<IStatsChart[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/food/stats/rank',
|
url: '/food-api/food/stats/rank',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,45 +3,51 @@ import type { IMoment } from '@/types/food'
|
|||||||
|
|
||||||
export const addMomentApi = (moment: IMoment): Promise<boolean> =>
|
export const addMomentApi = (moment: IMoment): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/moment',
|
url: '/food-api/moment',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: moment
|
data: moment
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateMomentApi = (id: number, moment: IMoment): Promise<boolean> =>
|
export const updateMomentApi = (id: number, moment: IMoment): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/moment/${id}`,
|
url: `/food-api/moment/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: moment
|
data: moment
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteMomentApi = (id: number): Promise<boolean> =>
|
export const deleteMomentApi = (id: number): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/moment/${id}`,
|
url: `/food-api/moment/${id}`,
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryMomentApi = (): Promise<IMoment[]> =>
|
export const queryMomentListApi = (): Promise<IMoment[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/moment',
|
url: '/food-api/moment',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addCommentApi = (id: number, content: string): Promise<boolean> =>
|
export const queryMomentByIdApi = (id: number): Promise<IMoment[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/moment/${id}/comment`,
|
url: `/food-api/moment/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addMomentCommentApi = (id: number, content: string): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/food-api/moment/${id}/comment`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { content }
|
params: { content }
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addLikeApi = (id: number): Promise<boolean> =>
|
export const addMomentLikeApi = (id: number): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/moment/${id}/like`,
|
url: `/food-api/moment/${id}/like`,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteLikeApi = (id: number): Promise<boolean> =>
|
export const deleteMomentLikeApi = (id: number): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/moment/${id}/like`,
|
url: `/food-api/moment/${id}/like`,
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import type { ISession } from '@/types/auth'
|
|||||||
|
|
||||||
export const loginApi = (username: string, password: string): Promise<ISession> =>
|
export const loginApi = (username: string, password: string): Promise<ISession> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/session',
|
url: '/food-api/session',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { username, password }
|
params: { username, password }
|
||||||
})
|
})
|
||||||
|
|
||||||
export const logoutApi = (username: string): Promise<ISession> =>
|
export const logoutApi = (username: string): Promise<ISession> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/session',
|
url: '/food-api/session',
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
params: { username }
|
params: { username }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import type { ISession, IUser } from '@/types/auth'
|
|||||||
|
|
||||||
export const registerApi = (username: string, password: string): Promise<ISession> =>
|
export const registerApi = (username: string, password: string): Promise<ISession> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/user',
|
url: '/food-api/user',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { username, password }
|
params: { username, password }
|
||||||
})
|
})
|
||||||
|
|
||||||
export const queryUserApi = (id: number): Promise<IUser> =>
|
export const queryUserApi = (id: number): Promise<IUser> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/user/${id}`,
|
url: `/food-api/user/${id}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateUserApi = (id: number, user: IUser): Promise<boolean> =>
|
export const updateUserApi = (id: number, user: IUser): Promise<boolean> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: `/food-service/user/${id}`,
|
url: `/food-api/user/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: user
|
data: user
|
||||||
})
|
})
|
||||||
|
|||||||
BIN
src/assets/font/custom.woff2
Normal file
BIN
src/assets/font/custom.woff2
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,8 @@
|
|||||||
<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-carousel v-if="props.item?.recordList.length > 0"
|
||||||
arrow="always" @change="changeFoodEvent">
|
arrow="always" @change="changeFoodEvent">
|
||||||
<el-carousel-item v-for="(item, index) in getRecord(props.foodItem?.recordList)"
|
<el-carousel-item v-for="(item, index) in getRecord(props.item?.recordList)"
|
||||||
:key="index">
|
:key="index">
|
||||||
<el-image :src="serviceFileRootPath + item.imageUrl"
|
<el-image :src="serviceFileRootPath + item.imageUrl"
|
||||||
fit="contain" alt="暂无成果">
|
fit="contain" alt="暂无成果">
|
||||||
@@ -21,32 +21,50 @@
|
|||||||
<div class="top-container">
|
<div class="top-container">
|
||||||
<div class="info-list">
|
<div class="info-list">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="title">{{ props.foodItem.name }}</span>
|
<span class="title">{{ props.item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-info">
|
<div class="button-info">
|
||||||
<el-button type="primary" :icon="Edit" circle :size="'small'"
|
<el-button v-if="isCurrentUser"
|
||||||
|
type="primary" :icon="Edit" circle :size="'small'"
|
||||||
@click="editClick"/>
|
@click="editClick"/>
|
||||||
<el-button type="primary" :icon="Collection" circle :size="'small'"
|
<el-button type="primary" :icon="Collection" circle :size="'small'"
|
||||||
@click="viewClick()" />
|
@click="viewClick()" />
|
||||||
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
<el-button v-if="isCurrentUser"
|
||||||
|
type="danger" :icon="DeleteFilled" circle :size="'small'"
|
||||||
@click="deleteClick()" />
|
@click="deleteClick()" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-container">
|
<div class="bottom-container">
|
||||||
<div class="info-list">
|
<div class="info-list">
|
||||||
<div class="info-item category-item">
|
<el-avatar v-if="!isCurrentUser" :src="avatarUrl" :size="20" fit="contain"/>
|
||||||
|
<span v-if="!isCurrentUser" class="user">{{ props.item.username }}</span>
|
||||||
|
|
||||||
|
<div v-if="isCurrentUser" class="info-item category-item">
|
||||||
<i class="fa-solid fa-utensils"></i>
|
<i class="fa-solid fa-utensils"></i>
|
||||||
{{ props.foodItem.category }}
|
{{ props.item.category }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isCurrentUser" class="info-item like-item">
|
||||||
|
<i class="fa-regular fa-thumbs-up"></i>
|
||||||
|
{{ props.item.likeCount }}
|
||||||
|
</div>
|
||||||
|
<div v-if="isCurrentUser" class="info-item comment-item">
|
||||||
|
<i class="fa-regular fa-comment"></i>
|
||||||
|
{{ props.item.commentCount }}
|
||||||
|
</div>
|
||||||
|
<div v-if="isCurrentUser" class="info-item start-item">
|
||||||
|
<i class="fa-solid fa-star"></i>
|
||||||
|
{{ props.item.favouriteCount }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.foodItem?.recordList.length > 0" class="info-list">
|
<div v-if="props.item?.recordList.length > 0" class="info-list">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<i class="fa-regular fa-calendar-check"></i>
|
<i class="fa-regular fa-calendar-check"></i>
|
||||||
{{ formatDate(props.foodItem.recordList[foodCardInfo.currentFoodIndex].date) }}
|
{{ formatDate(props.item.recordList[foodCardInfo.currentFoodIndex].date) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -61,7 +79,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Edit, Collection, DeleteFilled } from '@element-plus/icons-vue'
|
import { Edit, Collection, DeleteFilled } from '@element-plus/icons-vue'
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
import { defineProps, onMounted, reactive } from 'vue'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { computed, defineProps, onMounted, reactive } from 'vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import type { IRecipe, IRecord } from '@/types/food.ts'
|
import type { IRecipe, IRecord } from '@/types/food.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -71,6 +90,7 @@ import { serviceFileRootPath } from '@/utils'
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const foodCardInfo = reactive({
|
const foodCardInfo = reactive({
|
||||||
recommendRate: 0,
|
recommendRate: 0,
|
||||||
@@ -78,34 +98,44 @@ const foodCardInfo = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
foodItem: {
|
item: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Object as PropType<IRecipe>
|
type: Object as PropType<IRecipe>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
|
foodCardInfo.recommendRate = props.item?.recommendRate as number
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const avatarUrl = computed(() => {
|
||||||
|
if (props.item?.avatar) {
|
||||||
|
return serviceFileRootPath + props.item.avatar
|
||||||
|
} else {
|
||||||
|
return 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCurrentUser = computed(() => props.item?.userId === userStore.getUserId())
|
||||||
|
|
||||||
const getRecord = (recordList: IRecord[]) => {
|
const getRecord = (recordList: IRecord[]) => {
|
||||||
return recordList.filter((item) => item.imageUrl)
|
return recordList.filter((item) => item.imageUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
const editClick = async () => {
|
const editClick = async () => {
|
||||||
foodStore.recipeApiType = 'UPDATE'
|
foodStore.recipeApiType = 'UPDATE'
|
||||||
await foodStore.queryRecipe(props.foodItem.id as number)
|
await foodStore.queryRecipe(props.item.id as number)
|
||||||
await router.push('/record/recipeForm')
|
await router.push('/record/recipeForm')
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewClick = async () => {
|
const viewClick = async () => {
|
||||||
await router.push({ name: 'RecordDetailId', params: { id: props.foodItem?.id } })
|
await router.push({ name: 'RecordDetailId', params: { id: props.item?.id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteClick = () => {
|
const deleteClick = () => {
|
||||||
foodStore.recipeApiType = 'DELETE'
|
foodStore.recipeApiType = 'DELETE'
|
||||||
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
|
commonElMessageBox(`请确认是否删除该菜谱: ${props.item?.name}?`).then(() => {
|
||||||
foodStore.handleRecipeApi(props.foodItem as IRecipe)
|
foodStore.handleRecipeApi(props.item as IRecipe)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ const foodStore = useFoodStore()
|
|||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
foodStore.queryDateType = 'month'
|
|
||||||
await foodStore.refreshInfo()
|
|
||||||
|
|
||||||
const { month } = foodStore.queryRecord
|
const { month } = foodStore.queryRecord
|
||||||
calendarRef.value.move(new Date(month))
|
calendarRef.value.move(new Date(month))
|
||||||
})
|
})
|
||||||
@@ -86,7 +83,7 @@ const dateClick = (day) => {
|
|||||||
*/
|
*/
|
||||||
const changeMonthClick = async (value) => {
|
const changeMonthClick = async (value) => {
|
||||||
foodStore.queryRecord.month = value[0].id
|
foodStore.queryRecord.month = value[0].id
|
||||||
await foodStore.refreshInfo()
|
await foodStore.queryRecordList()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
49
src/components/Food/FoodQuery.vue
Normal file
49
src/components/Food/FoodQuery.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-container">
|
||||||
|
<el-input v-if="expanded" ref="inputRef"
|
||||||
|
v-model="searchText" placeholder="搜索菜友菜谱"
|
||||||
|
style="width: 200px;"
|
||||||
|
show-word-limit :maxlength="10" clearable
|
||||||
|
@keyup.enter="handleSearch" @blur="handleBlur"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-button v-else :icon="Search"
|
||||||
|
circle size="small" @click="expandSearch"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const expanded = ref(false)
|
||||||
|
const searchText = ref('')
|
||||||
|
const inputRef = ref(null)
|
||||||
|
|
||||||
|
// 展开搜索框
|
||||||
|
const expandSearch = () => {
|
||||||
|
expanded.value = true
|
||||||
|
// 等待DOM更新后自动聚焦输入框
|
||||||
|
nextTick(() => {
|
||||||
|
inputRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (searchText.value.trim()) {
|
||||||
|
console.log('搜索内容:', searchText.value)
|
||||||
|
// 这里可以触发搜索逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框失去焦点时的处理
|
||||||
|
const handleBlur = () => {
|
||||||
|
if (!searchText.value.trim()) {
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
@@ -7,24 +7,17 @@
|
|||||||
@change="queryFoodSegmented"/>
|
@change="queryFoodSegmented"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="food-list">
|
<recipe-list />
|
||||||
<el-empty v-if="foodStore.recipeList.length === 0" description="暂无记录"/>
|
|
||||||
|
|
||||||
<div v-else class="list">
|
|
||||||
<food-card v-for="(item, index) in foodStore.recipeList"
|
|
||||||
:key="index" :food-item="item"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<arrive-bottom v-if="foodStore.isScrollEnd"/>
|
<arrive-bottom v-if="foodStore.isScrollEnd"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import FoodCard from '@/components/Food/FoodCard.vue'
|
import RecipeList from '@/components/Food/RecipeList.vue'
|
||||||
import ArriveBottom from '@/components/ArriveButton.vue'
|
import ArriveBottom from '@/components/ArriveButton.vue'
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import type { ICategory } from '@/types'
|
import type { ICategory } from '@/types'
|
||||||
|
|
||||||
const foodRecipeRef = ref()
|
const foodRecipeRef = ref()
|
||||||
@@ -33,10 +26,7 @@ const foodStore = useFoodStore()
|
|||||||
const queryCategoryList = ref([] as ICategory[])
|
const queryCategoryList = ref([] as ICategory[])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await foodStore.queryCategory()
|
|
||||||
initCategoryList()
|
|
||||||
// foodStore.foodRecipeList = []
|
// foodStore.foodRecipeList = []
|
||||||
await foodStore.refreshInfo()
|
|
||||||
// foodRecipeRef.value.addEventListener('scroll', handleScroll)
|
// foodRecipeRef.value.addEventListener('scroll', handleScroll)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -44,6 +34,10 @@ onBeforeUnmount(() => {
|
|||||||
// foodRecipeRef.value.removeEventListener('scroll', handleScroll)
|
// foodRecipeRef.value.removeEventListener('scroll', handleScroll)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => foodStore.isRefresh, () => {
|
||||||
|
initCategoryList()
|
||||||
|
})
|
||||||
|
|
||||||
const initCategoryList = () => {
|
const initCategoryList = () => {
|
||||||
queryCategoryList.value = []
|
queryCategoryList.value = []
|
||||||
queryCategoryList.value.push({
|
queryCategoryList.value.push({
|
||||||
@@ -87,13 +81,5 @@ const handleScroll = () => {
|
|||||||
.query-item {
|
.query-item {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.food-list {
|
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="food-timeline">
|
<div class="food-timeline">
|
||||||
|
<div class="button-container">
|
||||||
|
<el-button :icon="CaretLeft" circle
|
||||||
|
type="primary" size="small"
|
||||||
|
@click="previousYearClick"/>
|
||||||
|
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="currentYear"
|
v-model="foodStore.queryRecord.year"
|
||||||
type="year" placeholder="请选择年份"
|
type="year" placeholder="请选择年份"
|
||||||
:editable="false"
|
:editable="false"
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
@change="changeDateEvent"
|
@change="changeDateEvent"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<el-button :icon="CaretRight" circle
|
||||||
|
type="primary" size="small"
|
||||||
|
@click="nextYearClick"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-timeline v-if="foodStore.recordList.length > 0">
|
<el-timeline v-if="foodStore.recordList.length > 0">
|
||||||
<el-timeline-item v-for="(item, index) in foodStore.recordList"
|
<el-timeline-item v-for="(item, index) in foodStore.recordList"
|
||||||
:key="index" :timestamp="formatDate(item.date)" placement="top">
|
:key="index" :timestamp="formatDate(item.date)"
|
||||||
|
placement="top" color='var(--el-color-primary)'>
|
||||||
<div class="timeline-info card-item">
|
<div class="timeline-info card-item">
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
<el-image :src="serviceFileRootPath + item.imageUrl"
|
<el-image :src="serviceFileRootPath + item.imageUrl"
|
||||||
@@ -30,22 +41,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { CaretLeft, CaretRight } from '@element-plus/icons-vue'
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { onMounted, ref } from 'vue'
|
import { formatDate, getLastDate, getNextDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
|
||||||
import { serviceFileRootPath } from '@/utils'
|
import { serviceFileRootPath } from '@/utils'
|
||||||
|
|
||||||
const currentYear = ref(new Date())
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
foodStore.queryDateType = 'year'
|
|
||||||
await foodStore.refreshInfo()
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeDateEvent = async (value: Date) => {
|
const changeDateEvent = async (value: Date) => {
|
||||||
foodStore.queryRecord.year = String(value.getFullYear())
|
foodStore.queryRecord.year = String(value.getFullYear())
|
||||||
await foodStore.refreshInfo()
|
await foodStore.queryRecordList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousYearClick = async () => {
|
||||||
|
const lastYear = getLastDate(foodStore.queryRecord.year, 1, 'years')
|
||||||
|
foodStore.queryRecord.year = String(new Date(lastYear).getFullYear())
|
||||||
|
await foodStore.queryRecordList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextYearClick = async () => {
|
||||||
|
const lastYear = getNextDate(foodStore.queryRecord.year, 1, 'years')
|
||||||
|
foodStore.queryRecord.year = String(new Date(lastYear).getFullYear())
|
||||||
|
await foodStore.queryRecordList()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -54,13 +71,21 @@ const changeDateEvent = async (value: Date) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.el-date-editor {
|
.button-container {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-timeline {
|
.el-timeline {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
|
.el-timeline-item__timestamp {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
.timeline-info {
|
.timeline-info {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
28
src/components/Food/RecipeList.vue
Normal file
28
src/components/Food/RecipeList.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="food-list">
|
||||||
|
<el-empty v-if="foodStore.recipeList.length === 0" description="暂无记录"/>
|
||||||
|
|
||||||
|
<div v-else class="list">
|
||||||
|
<food-card v-for="(item, index) in foodStore.recipeList"
|
||||||
|
:key="index" :item="item"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import FoodCard from '@/components/Food/FoodCard.vue'
|
||||||
|
import { useFoodStore } from '@/store/food'
|
||||||
|
|
||||||
|
const foodStore = useFoodStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.food-list {
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<div />
|
<div />
|
||||||
|
|
||||||
<div class="login-type">
|
<div class="login-type">
|
||||||
<el-button @click="authStore.changeLoginType('phone')">手机号登录</el-button>
|
<el-button>手机号登录</el-button>
|
||||||
<el-button>扫码登录</el-button>
|
<el-button>扫码登录</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -58,8 +58,7 @@
|
|||||||
<div class="type-list">
|
<div class="type-list">
|
||||||
<i class="fa-brands fa-weixin" style="color: #3CB034"></i>
|
<i class="fa-brands fa-weixin" style="color: #3CB034"></i>
|
||||||
<i class="fa-brands fa-qq" style="color: #29A4D9"></i>
|
<i class="fa-brands fa-qq" style="color: #29A4D9"></i>
|
||||||
<i class="fa-solid fa-envelope" style="color: #4971B8"
|
<i class="fa-solid fa-envelope" style="color: #4971B8"></i>
|
||||||
@click="authStore.changeLoginType('email')"></i>
|
|
||||||
<i class="fa-brands fa-github" style="color: #555555"></i>
|
<i class="fa-brands fa-github" style="color: #555555"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moment-card card-item">
|
<div class="moment-card card-item">
|
||||||
<el-avatar :src="avatarUrl" fit="contain" @click="viewProfileClick"/>
|
<el-avatar :src="avatarUrl" :size="35" fit="contain" @click="viewProfileClick"/>
|
||||||
|
|
||||||
<div class="moment-content">
|
<div class="moment-content">
|
||||||
<span class="username">{{ props.item.username }}</span>
|
<span class="username">{{ props.item.username }}</span>
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<div class="moment-image-list">
|
<div class="moment-image-list">
|
||||||
<el-image v-for="(item, index) in props.item.imageList" :key="index"
|
<el-image v-for="(item, index) in props.item.imageList" :key="index"
|
||||||
:src="serviceFileRootPath + item" fit="cover"
|
:src="serviceFileRootPath + item" fit="cover"
|
||||||
|
:initial-index="index"
|
||||||
:preview-src-list=previewList />
|
:preview-src-list=previewList />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -124,9 +125,9 @@ const editClick = () => {
|
|||||||
|
|
||||||
const handleLikeClick = () => {
|
const handleLikeClick = () => {
|
||||||
if (isLike.value) {
|
if (isLike.value) {
|
||||||
foodStore.deleteLike(props.item?.id as number)
|
foodStore.deleteMomentLike(props.item?.id as number)
|
||||||
} else {
|
} else {
|
||||||
foodStore.addLike(props.item?.id as number)
|
foodStore.addMomentLike(props.item?.id as number)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ const onSelectEmoji = (emoji) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sendCommentClick = async () => {
|
const sendCommentClick = async () => {
|
||||||
await foodStore.addComment(props.item?.id as number, momentInfo.comment)
|
await foodStore.addMomentComment(props.item?.id as number, momentInfo.comment)
|
||||||
|
|
||||||
momentInfo.comment = ''
|
momentInfo.comment = ''
|
||||||
momentInfo.isShowComment = false
|
momentInfo.isShowComment = false
|
||||||
@@ -150,61 +151,3 @@ const viewProfileClick = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.moment-card {
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 50px 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.moment-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
|
|
||||||
.moment-image-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.moment-comment {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 40px 60px;
|
|
||||||
justify-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.moment-comment-list {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--color-comment-bg);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.moment-comment-item {
|
|
||||||
.name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
23
src/components/Moment/MomentList.vue
Normal file
23
src/components/Moment/MomentList.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div class="food-moment"
|
||||||
|
v-loading="foodStore.isLoading" element-loading-text="加载中">
|
||||||
|
<el-empty v-if="foodStore.momentList.length === 0" description="暂无记录"/>
|
||||||
|
|
||||||
|
<div v-else class="moment-list">
|
||||||
|
<moment-card v-for="(item, index) in foodStore.momentList"
|
||||||
|
:key="index" :item="item"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MomentCard from '@/components/Moment/MomentCard.vue'
|
||||||
|
import { useFoodStore } from '@/store/food'
|
||||||
|
|
||||||
|
const foodStore = useFoodStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "@/styles/moment.module.scss";
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="profile-card">
|
<div class="profile-card card-item">
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<el-avatar :src="avatarUrl" alt="用户头像" :size="80"
|
<el-avatar :src="avatarUrl" alt="用户头像" :size="80"
|
||||||
fit="contain" @click="previewVisible = true"
|
fit="contain" @click="previewVisible = true"
|
||||||
@@ -75,16 +75,16 @@ const avatarUrl = computed(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.profile-card {
|
.profile-card {
|
||||||
background: linear-gradient(135deg, #6e8efb, #a777e3);
|
// background: linear-gradient(135deg, #6e8efb, #a777e3);
|
||||||
border-radius: 10px;
|
// border-radius: 10px;
|
||||||
padding: 20px;
|
padding: 10px;
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
// box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
// color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
gap: 10px;
|
||||||
|
|
||||||
.avatar-wrapper {
|
.avatar-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
<span class="title">📊排行榜</span>
|
<span class="title">📊排行榜</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart rank-chart">
|
<div class="chart rank-chart">
|
||||||
<food-rank />
|
<food-rank v-if="foodStore.rankStats.length > 0"/>
|
||||||
|
<el-empty v-else description="暂无数据"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
|
|
||||||
<span class="title-container">{{ title }}</span>
|
<span class="title-container">{{ title }}</span>
|
||||||
|
|
||||||
<div class="right-container">
|
<div v-if="!appStore.isShowMobileBack" class="right-container">
|
||||||
<svg-icon v-if="appStore.isWebSocket"
|
<!-- <svg-icon v-if="appStore.isWebSocket"-->
|
||||||
name="home-websocket" style="color: #FFFFFF;"></svg-icon>
|
<!-- name="home-websocket" style="color: #FFFFFF;"></svg-icon>-->
|
||||||
<el-button type="info" :icon="Setting" size="small"
|
<food-query />
|
||||||
circle color="#FFFFFF" @click="openSettingClick"/>
|
|
||||||
|
<el-button :icon="Setting" size="small"
|
||||||
|
circle @click="openSettingClick"/>
|
||||||
|
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="appStore.isShowSetting"
|
v-model="appStore.isShowSetting"
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import FoodQuery from '@/components/Food/FoodQuery.vue'
|
||||||
import { SvgIcon } from 'vue3-common'
|
import { SvgIcon } from 'vue3-common'
|
||||||
import MobileSetting from '@/components/MobileSetting.vue'
|
import MobileSetting from '@/components/MobileSetting.vue'
|
||||||
import { Back } from '@element-plus/icons-vue'
|
import { Back } from '@element-plus/icons-vue'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
import { getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
||||||
import { useDark, useToggle } from '@vueuse/core'
|
import { useDark, useToggle } from '@vueuse/core'
|
||||||
|
import { darken, lighten } from '@/utils/colorUtils'
|
||||||
|
|
||||||
export const useAppStore = defineStore('ipp', {
|
export const useAppStore = defineStore('ipp', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -14,7 +15,7 @@ export const useAppStore = defineStore('ipp', {
|
|||||||
isDark: false,
|
isDark: false,
|
||||||
isWebSocket: false,
|
isWebSocket: false,
|
||||||
serverIp: '14.103.235.151',
|
serverIp: '14.103.235.151',
|
||||||
version: 'v1.0.0.2025062802'
|
version: 'v1.0.0.2025071801'
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
initApp() {
|
initApp() {
|
||||||
@@ -71,9 +72,18 @@ export const useAppStore = defineStore('ipp', {
|
|||||||
getThemeColor(): string {
|
getThemeColor(): string {
|
||||||
return getLocalStorage('theme-color') as string || '#009FA8'
|
return getLocalStorage('theme-color') as string || '#009FA8'
|
||||||
},
|
},
|
||||||
setThemeColor(value: string) {
|
setThemeColor(color: string) {
|
||||||
document.documentElement.style.setProperty('--el-color-primary', value)
|
const { style } = document.documentElement
|
||||||
setLocalStorage('theme-color', value)
|
|
||||||
|
// 1. 设置主色
|
||||||
|
style.setProperty('--el-color-primary', color)
|
||||||
|
|
||||||
|
for (let i = 1; i <= 9; i++) {
|
||||||
|
style.setProperty(`--el-color-primary-light-${i}`, lighten(color, i / 10))
|
||||||
|
style.setProperty(`--el-color-primary-dark-${i}`, darken(color, i / 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalStorage('theme-color', color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,30 +4,22 @@ import type {
|
|||||||
IStats, IRecord, IMoment, IFoodSegment
|
IStats, IRecord, IMoment, IFoodSegment
|
||||||
} from '@/types/food'
|
} from '@/types/food'
|
||||||
import {
|
import {
|
||||||
addRecipeApi,
|
addRecipeApi, addRecordApi, deleteRecipeApi, deleteRecordApi,
|
||||||
addRecordApi,
|
queryFoodNameListApi, queryRecipeByIdApi, queryRecordApi, queryStatsApi,
|
||||||
deleteRecipeApi,
|
updateRecipeApi, updateRecordApi, queryCategoryApi, queryRecordStatsApi,
|
||||||
deleteRecordApi,
|
queryCategoryStatsApi, queryRankStatsApi,
|
||||||
queryFoodNameListApi,
|
queryRecipeApi, queryRecipeByUserApi, deleteRecipeLikeApi,
|
||||||
queryRecipeByIdApi,
|
addRecipeLikeApi, addRecipeCommentApi, addRecipeFavouriteApi, deleteRecipeFavouriteApi, queryRecipeUserFavouriteApi
|
||||||
queryRecordApi,
|
|
||||||
queryStatsApi,
|
|
||||||
updateRecipeApi,
|
|
||||||
updateRecordApi,
|
|
||||||
queryCategoryApi,
|
|
||||||
queryRecordStatsApi,
|
|
||||||
queryCategoryStatsApi, queryRankStatsApi, queryRecipeApi
|
|
||||||
} from '@/apis/food.ts'
|
} from '@/apis/food.ts'
|
||||||
import type { IDialog, IHandleApi, IStatsChart } from 'vue3-common/types'
|
import type { IDialog, IHandleApi, IStatsChart } from 'vue3-common/types'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
||||||
import {
|
import {
|
||||||
addCommentApi,
|
addMomentLikeApi,
|
||||||
addLikeApi,
|
addMomentApi, addMomentCommentApi,
|
||||||
addMomentApi,
|
deleteMomentLikeApi,
|
||||||
deleteLikeApi,
|
deleteMomentApi, queryMomentByIdApi,
|
||||||
deleteMomentApi,
|
queryMomentListApi,
|
||||||
queryMomentApi,
|
|
||||||
updateMomentApi
|
updateMomentApi
|
||||||
} from '@/apis/moment'
|
} from '@/apis/moment'
|
||||||
|
|
||||||
@@ -40,9 +32,19 @@ export const useFoodStore = defineStore('food', {
|
|||||||
category: '',
|
category: '',
|
||||||
recommendRate: 0,
|
recommendRate: 0,
|
||||||
remark: '',
|
remark: '',
|
||||||
|
isShare: false,
|
||||||
|
username: '',
|
||||||
|
userId: 0,
|
||||||
|
avatar: '',
|
||||||
materialList: [],
|
materialList: [],
|
||||||
stepList: [],
|
stepList: [],
|
||||||
recordList: []
|
recordList: [],
|
||||||
|
likeCount: 0,
|
||||||
|
likeList: [],
|
||||||
|
favouriteCount: 0,
|
||||||
|
favouriteList: [],
|
||||||
|
commentCount: 0,
|
||||||
|
commentList: []
|
||||||
} as IRecipe,
|
} as IRecipe,
|
||||||
recordList: [] as IRecord[],
|
recordList: [] as IRecord[],
|
||||||
currentRecord: {
|
currentRecord: {
|
||||||
@@ -105,7 +107,8 @@ export const useFoodStore = defineStore('food', {
|
|||||||
recordStats: [] as IStatsChart[],
|
recordStats: [] as IStatsChart[],
|
||||||
categoryStats: [] as IStatsChart[],
|
categoryStats: [] as IStatsChart[],
|
||||||
rankStats: [] as IStatsChart[],
|
rankStats: [] as IStatsChart[],
|
||||||
isRefresh: false
|
isRefresh: false,
|
||||||
|
isLoading: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async queryRecipeList() {
|
async queryRecipeList() {
|
||||||
@@ -113,16 +116,34 @@ export const useFoodStore = defineStore('food', {
|
|||||||
// const result = await queryFoodRecipeByPageApi(currentPage, pageSize, this.queryRecipe)
|
// const result = await queryFoodRecipeByPageApi(currentPage, pageSize, this.queryRecipe)
|
||||||
// this.foodRecipeList.push(...result.records)
|
// this.foodRecipeList.push(...result.records)
|
||||||
// this.pageInfo.totalSize = result.total
|
// this.pageInfo.totalSize = result.total
|
||||||
|
this.isLoading = true
|
||||||
this.recipeList = await queryRecipeApi(this.queryRecipeSegment)
|
this.recipeList = await queryRecipeApi(this.queryRecipeSegment)
|
||||||
|
this.isRefresh = !this.isRefresh
|
||||||
|
this.isLoading = false
|
||||||
|
},
|
||||||
|
async queryRecipeByUser(id: number) {
|
||||||
|
this.isLoading = true
|
||||||
|
this.recipeList = await queryRecipeByUserApi(id)
|
||||||
|
this.isLoading = false
|
||||||
|
},
|
||||||
|
async queryRecipeUserFavourite() {
|
||||||
|
this.isLoading = true
|
||||||
|
this.recipeList = await queryRecipeUserFavouriteApi()
|
||||||
|
this.isLoading = false
|
||||||
},
|
},
|
||||||
async queryRecordList() {
|
async queryRecordList() {
|
||||||
const { year, month } = this.queryRecord
|
const { year, month } = this.queryRecord
|
||||||
const date = this.queryDateType === 'year' ? year : month
|
const date = this.queryDateType === 'year' ? year : month
|
||||||
this.queryDateList = getStartEndDate(date, this.queryDateType)
|
this.queryDateList = getStartEndDate(date, this.queryDateType)
|
||||||
|
this.isLoading = true
|
||||||
this.recordList = await queryRecordApi(this.queryDateList[0], this.queryDateList[1])
|
this.recordList = await queryRecordApi(this.queryDateList[0], this.queryDateList[1])
|
||||||
|
this.isRefresh = !this.isRefresh
|
||||||
|
this.isLoading = false
|
||||||
},
|
},
|
||||||
async queryMomentList() {
|
async queryMomentList() {
|
||||||
this.momentList = await queryMomentApi()
|
this.isLoading = true
|
||||||
|
this.momentList = await queryMomentListApi()
|
||||||
|
this.isLoading = false
|
||||||
},
|
},
|
||||||
async queryFoodNameList() {
|
async queryFoodNameList() {
|
||||||
this.foodNameList = await queryFoodNameListApi()
|
this.foodNameList = await queryFoodNameListApi()
|
||||||
@@ -130,6 +151,11 @@ export const useFoodStore = defineStore('food', {
|
|||||||
async queryRecipe(id: number) {
|
async queryRecipe(id: number) {
|
||||||
this.currentRecipe = await queryRecipeByIdApi(id)
|
this.currentRecipe = await queryRecipeByIdApi(id)
|
||||||
},
|
},
|
||||||
|
async queryMoment(id: number) {
|
||||||
|
this.isLoading = true
|
||||||
|
this.momentList = await queryMomentByIdApi(id)
|
||||||
|
this.isLoading = false
|
||||||
|
},
|
||||||
async queryStats() {
|
async queryStats() {
|
||||||
this.stats = await queryStatsApi()
|
this.stats = await queryStatsApi()
|
||||||
this.recordStats = await queryRecordStatsApi()
|
this.recordStats = await queryRecordStatsApi()
|
||||||
@@ -157,7 +183,7 @@ export const useFoodStore = defineStore('food', {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
await this.refreshInfo()
|
await this.queryRecipeList()
|
||||||
},
|
},
|
||||||
async handleRecordApi(record: IRecord) {
|
async handleRecordApi(record: IRecord) {
|
||||||
switch (this.recordApiType) {
|
switch (this.recordApiType) {
|
||||||
@@ -176,7 +202,7 @@ export const useFoodStore = defineStore('food', {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
await this.refreshInfo()
|
await this.queryRecordList()
|
||||||
},
|
},
|
||||||
async handleMomentApi(moment: IMoment) {
|
async handleMomentApi(moment: IMoment) {
|
||||||
switch (this.momentApiType) {
|
switch (this.momentApiType) {
|
||||||
@@ -195,31 +221,51 @@ export const useFoodStore = defineStore('food', {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
await queryMomentApi()
|
|
||||||
},
|
|
||||||
async addComment(id: number, content: string) {
|
|
||||||
await addCommentApi(id, content)
|
|
||||||
await this.queryMomentList()
|
await this.queryMomentList()
|
||||||
},
|
},
|
||||||
async addLike(id: number) {
|
async addMomentComment(id: number, content: string) {
|
||||||
await addLikeApi(id)
|
await addMomentCommentApi(id, content)
|
||||||
await this.queryMomentList()
|
await this.queryMomentList()
|
||||||
},
|
},
|
||||||
async deleteLike(id: number) {
|
async addMomentLike(id: number) {
|
||||||
await deleteLikeApi(id)
|
await addMomentLikeApi(id)
|
||||||
await this.queryMomentList()
|
await this.queryMomentList()
|
||||||
},
|
},
|
||||||
async refreshInfo() {
|
async deleteMomentLike(id: number) {
|
||||||
await this.queryRecipeList()
|
await deleteMomentLikeApi(id)
|
||||||
await this.queryRecordList()
|
await this.queryMomentList()
|
||||||
this.isRefresh = !this.isRefresh
|
},
|
||||||
|
async addRecipeComment(id: number, content: string) {
|
||||||
|
await addRecipeCommentApi(id, content)
|
||||||
|
await this.queryRecipe(this.currentRecipe.id as number)
|
||||||
|
},
|
||||||
|
async addRecipeLike(id: number) {
|
||||||
|
await addRecipeLikeApi(id)
|
||||||
|
await this.queryRecipe(this.currentRecipe.id as number)
|
||||||
|
},
|
||||||
|
async deleteRecipeLike(id: number) {
|
||||||
|
await deleteRecipeLikeApi(id)
|
||||||
|
await this.queryRecipe(this.currentRecipe.id as number)
|
||||||
|
},
|
||||||
|
async addRecipeFavourite(id: number) {
|
||||||
|
await addRecipeFavouriteApi(id)
|
||||||
|
await this.queryRecipe(this.currentRecipe.id as number)
|
||||||
|
},
|
||||||
|
async deleteRecipeFavourite(id: number) {
|
||||||
|
await deleteRecipeFavouriteApi(id)
|
||||||
|
await this.queryRecipe(this.currentRecipe.id as number)
|
||||||
},
|
},
|
||||||
getEmptyRecipe(): IRecipe {
|
getEmptyRecipe(): IRecipe {
|
||||||
return {
|
return {
|
||||||
|
commentCount: 0,
|
||||||
|
commentList: [],
|
||||||
|
likeCount: 0,
|
||||||
|
likeList: [],
|
||||||
category: '',
|
category: '',
|
||||||
materialList: [],
|
materialList: [],
|
||||||
name: '',
|
name: '',
|
||||||
recommendRate: 0,
|
recommendRate: 0,
|
||||||
|
isShare: false,
|
||||||
remark: '',
|
remark: '',
|
||||||
stepList: [],
|
stepList: [],
|
||||||
recordList: []
|
recordList: []
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ span, input {
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'CustomFont';
|
font-family: 'CustomFont';
|
||||||
src: url('@/assets/font/霞鹜臻楷.ttf') format('truetype');
|
src: url('@/assets/font/custom.woff2') format('truetype');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,6 +201,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.food-comment-list {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.food-comment-item {
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button {
|
.back-button {
|
||||||
|
|||||||
@@ -47,18 +47,35 @@ $card-image-height: 200px;
|
|||||||
.info-item {
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 3px;
|
gap: 5px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
// font-size: 0.9rem;
|
||||||
|
color: #9C9C9C;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-item {
|
.category-item {
|
||||||
color: #0d9488;
|
color: #0d9488;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.like-item {
|
||||||
|
color: #FF4D4F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
color: #03A9F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-item {
|
||||||
|
color: #FFAB40;
|
||||||
|
}
|
||||||
|
|
||||||
.location-item {
|
.location-item {
|
||||||
color: #0d9488;
|
color: #0d9488;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ $tabBar-height: 70px;
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
67
src/styles/moment.module.scss
Normal file
67
src/styles/moment.module.scss
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.food-moment {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.moment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.moment-card {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 35px 1fr;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.moment-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moment-image-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moment-comment {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 40px 60px;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moment-comment-list {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--color-comment-bg);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.moment-comment-item {
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,14 @@ export interface IStep {
|
|||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IComment {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
avatar: string;
|
||||||
|
content: string;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 成果信息
|
* 成果信息
|
||||||
*/
|
*/
|
||||||
@@ -51,6 +59,10 @@ export interface IRecipe {
|
|||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
remark: string;
|
remark: string;
|
||||||
|
isShare: boolean;
|
||||||
|
userId?: number;
|
||||||
|
username?: string;
|
||||||
|
avatar?: string;
|
||||||
/**
|
/**
|
||||||
* 食材
|
* 食材
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +75,12 @@ export interface IRecipe {
|
|||||||
* 成果
|
* 成果
|
||||||
*/
|
*/
|
||||||
recordList: IRecord[];
|
recordList: IRecord[];
|
||||||
|
likeList?: number[];
|
||||||
|
likeCount?: number;
|
||||||
|
favouriteList?: number[];
|
||||||
|
favouriteCount?: number;
|
||||||
|
commentList?: IComment[];
|
||||||
|
commentCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,14 +96,6 @@ export interface IFoodQuery {
|
|||||||
category: string;
|
category: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IComment {
|
|
||||||
id: number;
|
|
||||||
username: string;
|
|
||||||
avatar: string;
|
|
||||||
content: string;
|
|
||||||
date: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMoment {
|
export interface IMoment {
|
||||||
id?: number;
|
id?: number;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
|||||||
69
src/utils/colorUtils.ts
Normal file
69
src/utils/colorUtils.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 将十六进制颜色转换为RGB数组
|
||||||
|
* @param color 十六进制颜色字符串(如:#FF0000 或 FFFFFF)
|
||||||
|
* @returns RGB数值数组 [r, g, b]
|
||||||
|
*/
|
||||||
|
export const hex2Rgb = (color: string): [number, number, number] => {
|
||||||
|
const cleanColor = color.replace('#', '')
|
||||||
|
const result = cleanColor.match(/../g)?.map((val) => parseInt(val, 16)) ?? [0, 0, 0]
|
||||||
|
|
||||||
|
// 确保结果长度为3,并处理无效输入
|
||||||
|
if (result.length !== 3) {
|
||||||
|
throw new Error('Invalid hex color format')
|
||||||
|
}
|
||||||
|
|
||||||
|
return [result[0], result[1], result[2]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将RGB颜色转换为十六进制字符串
|
||||||
|
* @param r 红色值 (0-255)
|
||||||
|
* @param g 绿色值 (0-255)
|
||||||
|
* @param b 蓝色值 (0-255)
|
||||||
|
* @returns 十六进制颜色字符串(如:#FF0000)
|
||||||
|
*/
|
||||||
|
export const rgb2Hex = (r: number, g: number, b: number): string => {
|
||||||
|
const validateComponent = (val: number) => Math.max(0, Math.min(255, Math.round(val)))
|
||||||
|
|
||||||
|
const hexs = [
|
||||||
|
validateComponent(r).toString(16).padStart(2, '0'),
|
||||||
|
validateComponent(g).toString(16).padStart(2, '0'),
|
||||||
|
validateComponent(b).toString(16).padStart(2, '0')
|
||||||
|
]
|
||||||
|
|
||||||
|
return `#${hexs.join('')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调亮颜色
|
||||||
|
* @param color 十六进制颜色字符串
|
||||||
|
* @param level 亮度调整级别 (0-1)
|
||||||
|
* @returns 调亮后的十六进制颜色
|
||||||
|
*/
|
||||||
|
export const lighten = (color: string, level: number): string => {
|
||||||
|
const [r, g, b] = hex2Rgb(color)
|
||||||
|
const validateLevel = Math.max(0, Math.min(1, level))
|
||||||
|
|
||||||
|
const newR = Math.floor((255 - r) * validateLevel + r)
|
||||||
|
const newG = Math.floor((255 - g) * validateLevel + g)
|
||||||
|
const newB = Math.floor((255 - b) * validateLevel + b)
|
||||||
|
|
||||||
|
return rgb2Hex(newR, newG, newB)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调暗颜色
|
||||||
|
* @param color 十六进制颜色字符串
|
||||||
|
* @param level 暗度调整级别 (0-1)
|
||||||
|
* @returns 调暗后的十六进制颜色
|
||||||
|
*/
|
||||||
|
export const darken = (color: string, level: number): string => {
|
||||||
|
const [r, g, b] = hex2Rgb(color)
|
||||||
|
const validateLevel = Math.max(0, Math.min(1, level))
|
||||||
|
|
||||||
|
const newR = Math.floor(r * (1 - validateLevel))
|
||||||
|
const newG = Math.floor(g * (1 - validateLevel))
|
||||||
|
const newB = Math.floor(b * (1 - validateLevel))
|
||||||
|
|
||||||
|
return rgb2Hex(newR, newG, newB)
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* 文件服务器地址
|
* 文件服务器地址
|
||||||
*/
|
*/
|
||||||
export const fileServiceUrl = '/food-service/file'
|
export const fileServiceUrl = '/food-api/file'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器文件路径根地址
|
* 服务器文件路径根地址
|
||||||
*/
|
*/
|
||||||
export const serviceFileRootPath = '/food-service/'
|
export const serviceFileRootPath = '/food-api/'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取assets静态资源
|
* 获取assets静态资源
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moment-form common-mobile-view">
|
<div class="moment-form common-mobile-view">
|
||||||
<el-form ref="formRef" :model="foodStore.currentMoment"
|
<el-form ref="formRef" :model="foodStore.currentMoment"
|
||||||
:rules="rules" label-width="60">
|
:rules="rules" label-width="55">
|
||||||
<el-form-item label="内容" prop="content">
|
<el-form-item label="内容" prop="content">
|
||||||
<el-input v-model="foodStore.currentMoment.content"
|
<el-input v-model="foodStore.currentMoment.content"
|
||||||
type="textarea" :rows="3"
|
type="textarea" :rows="3"
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
<el-form-item label="图片">
|
<el-form-item label="图片">
|
||||||
<upload-image v-model="foodStore.currentMoment.imageList"
|
<upload-image v-model="foodStore.currentMoment.imageList"
|
||||||
:service-url="fileServiceUrl"
|
:service-url="fileServiceUrl"
|
||||||
:image-limit="4"
|
:image-limit="9"
|
||||||
|
:multiple="true"
|
||||||
:service-file-root-path="serviceFileRootPath"
|
:service-file-root-path="serviceFileRootPath"
|
||||||
:image-path="foodStore.imagePath"/>
|
:image-path="foodStore.imagePath"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="common-mobile-view food-moment">
|
<div class="common-mobile-view">
|
||||||
<el-empty v-if="foodStore.momentList.length === 0" description="暂无朋友圈记录"/>
|
<moment-list />
|
||||||
|
|
||||||
<div v-else class="moment-list">
|
|
||||||
<moment-card v-for="(item, index) in foodStore.momentList"
|
|
||||||
:key="index" :item="item"/>
|
|
||||||
|
|
||||||
<el-button type="primary" circle :icon="Refresh"
|
<el-button type="primary" circle :icon="Refresh"
|
||||||
class="refresh-button" @click="foodStore.queryMomentList()"/>
|
class="refresh-button" @click="foodStore.queryMomentList()"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import MomentList from '@/components/Moment/MomentList.vue'
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
import { Refresh } from '@element-plus/icons-vue'
|
||||||
import MomentCard from '@/components/Moment/MomentCard.vue'
|
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
@@ -26,21 +21,9 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.food-moment {
|
.refresh-button {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.moment-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.refresh-button {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,22 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moment-profile-view common-mobile-view">
|
<div class="moment-profile-view common-mobile-view">
|
||||||
<profile-card />
|
<profile-card />
|
||||||
|
|
||||||
|
<el-tabs v-model="activeName"
|
||||||
|
stretch class="profile-tabs"
|
||||||
|
@tab-click="handleClick">
|
||||||
|
<el-tab-pane label="朋友圈" name="moment">
|
||||||
|
<moment-list />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="菜谱记录" name="recipe">
|
||||||
|
<recipe-list />
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import RecipeList from '@/components/Food/RecipeList.vue'
|
||||||
import ProfileCard from '@/components/Profile/ProfileCard.vue'
|
import ProfileCard from '@/components/Profile/ProfileCard.vue'
|
||||||
import { useUserStore } from '@/store/user.ts'
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import MomentList from '@/components/Moment/MomentList.vue'
|
||||||
import { onMounted } from 'vue'
|
import type { TabsPaneContext } from 'element-plus'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const foodStore = useFoodStore()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
onMounted(() => {
|
const activeName = ref('moment')
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await foodStore.queryMoment(foodStore.currentMomentUserId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleClick = async (tab: TabsPaneContext) => {
|
||||||
|
switch (tab.paneName) {
|
||||||
|
case 'moment':
|
||||||
|
await foodStore.queryMoment(foodStore.currentMomentUserId)
|
||||||
|
break
|
||||||
|
case 'recipe':
|
||||||
|
await foodStore.queryRecipeByUser(foodStore.currentMomentUserId)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
<div class="profile-view common-mobile-view">
|
<div class="profile-view common-mobile-view">
|
||||||
<profile-card />
|
<profile-card />
|
||||||
|
|
||||||
|
<el-tabs v-model="activeName"
|
||||||
|
stretch class="profile-tabs"
|
||||||
|
@tab-click="handleClick">
|
||||||
|
<el-tab-pane label="基本资料" name="basic">
|
||||||
<div class="menu-card card-item">
|
<div class="menu-card card-item">
|
||||||
<div class="menu-item" @click="infoClick">
|
<div class="menu-item" @click="infoClick">
|
||||||
<i class="fas fa-user"></i>
|
<i class="fas fa-user"></i>
|
||||||
@@ -19,19 +23,26 @@
|
|||||||
|
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
|
|
||||||
<div class="menu-item">
|
|
||||||
<i class="fa-solid fa-comment-dots"></i>
|
|
||||||
<span>反馈意见</span>
|
|
||||||
<i class="fas fa-chevron-right"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-divider"></div>
|
|
||||||
|
|
||||||
<div class="menu-item" @click="passwordClick">
|
<div class="menu-item" @click="passwordClick">
|
||||||
<i class="fas fa-key"></i>
|
<i class="fas fa-key"></i>
|
||||||
<span>更改密码</span>
|
<span>更改密码</span>
|
||||||
<i class="fas fa-chevron-right"></i>
|
<i class="fas fa-chevron-right"></i>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="朋友圈" name="moment">
|
||||||
|
<moment-list />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="菜谱收藏" name="recipe">
|
||||||
|
<recipe-list />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="系统设置" name="system">
|
||||||
|
<div class="menu-card card-item">
|
||||||
|
<div class="menu-item">
|
||||||
|
<i class="fa-solid fa-comment-dots"></i>
|
||||||
|
<span>反馈意见</span>
|
||||||
|
<i class="fas fa-chevron-right"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
|
|
||||||
@@ -57,6 +68,8 @@
|
|||||||
<i class="fas fa-chevron-right"></i>
|
<i class="fas fa-chevron-right"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
<div class="version">版本号 {{ appStore.version }}</div>
|
<div class="version">版本号 {{ appStore.version }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,18 +77,25 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ProfileCard from '@/components/Profile/ProfileCard.vue'
|
import ProfileCard from '@/components/Profile/ProfileCard.vue'
|
||||||
|
import RecipeList from '@/components/Food/RecipeList.vue'
|
||||||
|
import MomentList from '@/components/Moment/MomentList.vue'
|
||||||
import { useUserStore } from '@/store/user.ts'
|
import { useUserStore } from '@/store/user.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { useAuthStore } from '@/store/auth'
|
import { useAuthStore } from '@/store/auth'
|
||||||
|
import { useFoodStore } from '@/store/food.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 { onMounted } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
import type { TabsPaneContext } from 'element-plus'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const foodStore = useFoodStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const activeName = ref('basic')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
userStore.queryUser(userStore.getUserId() as number)
|
userStore.queryUser(userStore.getUserId() as number)
|
||||||
})
|
})
|
||||||
@@ -89,6 +109,19 @@ const passwordClick = () => {
|
|||||||
// router.push('/mobile-home/profile/password')
|
// router.push('/mobile-home/profile/password')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClick = async (tab: TabsPaneContext) => {
|
||||||
|
switch (tab.paneName) {
|
||||||
|
case 'moment':
|
||||||
|
await foodStore.queryMoment(userStore.getUserId())
|
||||||
|
break
|
||||||
|
case 'recipe':
|
||||||
|
await foodStore.queryRecipeUserFavourite()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refreshClick = async () => {
|
const refreshClick = async () => {
|
||||||
await router.push('/')
|
await router.push('/')
|
||||||
location.reload()
|
location.reload()
|
||||||
|
|||||||
@@ -12,28 +12,67 @@
|
|||||||
|
|
||||||
<food-others-data/>
|
<food-others-data/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="button-container">
|
||||||
|
<el-badge :value="foodStore.currentRecipe.likeList.length" :max="9">
|
||||||
|
<el-button :type="isLike ? 'danger' : 'default'"
|
||||||
|
circle @click="handleLikeClick()">
|
||||||
|
<i class="fa-regular fa-thumbs-up"></i>
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-badge v-if="!isCurrentUser"
|
||||||
|
:value="foodStore.currentRecipe.favouriteList.length" :max="9">
|
||||||
|
<el-button v-if="!isCurrentUser"
|
||||||
|
:type="isFavourite ? 'danger' : 'default'"
|
||||||
|
circle @click="handleFavouriteClick()">
|
||||||
|
<i class="fa-regular fa-star"></i>
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFoodStore } from '@/store/food.ts'
|
|
||||||
import { useAppStore } from '@/store/app.ts'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import FoodBasicData from '@/components/Food/FoodBasicData.vue'
|
import FoodBasicData from '@/components/Food/FoodBasicData.vue'
|
||||||
import FoodMaterialData from '@/components/Food/FoodMaterialData.vue'
|
import FoodMaterialData from '@/components/Food/FoodMaterialData.vue'
|
||||||
import FoodStepData from '@/components/Food/FoodStepData.vue'
|
import FoodStepData from '@/components/Food/FoodStepData.vue'
|
||||||
import FoodOthersData from '@/components/Food/FoodOthersData.vue'
|
import FoodOthersData from '@/components/Food/FoodOthersData.vue'
|
||||||
import { onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/store/user.ts'
|
||||||
|
import { useFoodStore } from '@/store/food.ts'
|
||||||
|
import { useAppStore } from '@/store/app.ts'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
appStore.isShowMobileBack = true
|
appStore.isShowMobileBack = true
|
||||||
foodStore.queryRecipe(route.params.id as number)
|
foodStore.queryRecipe(route.params.id as number)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isLike = computed(() => foodStore.currentRecipe.likeList.includes(userStore.getUserId()))
|
||||||
|
const isFavourite = computed(() => foodStore.currentRecipe.favouriteList.includes(userStore.getUserId()))
|
||||||
|
const isCurrentUser = computed(() => foodStore.currentRecipe.userId === userStore.getUserId())
|
||||||
|
|
||||||
|
const handleLikeClick = () => {
|
||||||
|
if (isLike.value) {
|
||||||
|
foodStore.deleteRecipeLike(foodStore.currentRecipe.id as number)
|
||||||
|
} else {
|
||||||
|
foodStore.addRecipeLike(foodStore.currentRecipe.id as number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFavouriteClick = () => {
|
||||||
|
if (isFavourite.value) {
|
||||||
|
foodStore.deleteRecipeFavourite(foodStore.currentRecipe.id as number)
|
||||||
|
} else {
|
||||||
|
foodStore.addRecipeFavourite(foodStore.currentRecipe.id as number)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,32 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="common-mobile-view food-record">
|
<div class="common-mobile-view food-record">
|
||||||
<div class="food-segment card-item">
|
<el-tabs v-model="foodStore.recordSegment"
|
||||||
<el-input
|
stretch class="record-tabs"
|
||||||
v-model="foodInfo.query"
|
@tab-click="handleClick">
|
||||||
placeholder="请输入菜谱名称" :prefix-icon="Search" clearable
|
<el-tab-pane label="菜谱" name="recipe">
|
||||||
:maxlength="20" show-word-limit
|
<food-recipe v-loading="foodStore.isLoading" element-loading-text="加载中"/>
|
||||||
/>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="日历" name="daily">
|
||||||
<div class="query-item">
|
<food-daily v-loading="foodStore.isLoading" element-loading-text="加载中"/>
|
||||||
<span>查看类型:</span>
|
</el-tab-pane>
|
||||||
<el-segmented v-model="foodStore.recordSegment"
|
<el-tab-pane label="时间轴" name="timeline">
|
||||||
:options="recordSegmentList"/>
|
<food-timeline v-loading="foodStore.isLoading" element-loading-text="加载中"/>
|
||||||
</div>
|
</el-tab-pane>
|
||||||
</div>
|
</el-tabs>
|
||||||
|
|
||||||
<hr style="width: 100%;"/>
|
|
||||||
|
|
||||||
<div v-if="foodStore.recordSegment === 'recipe'" class="food-calendar">
|
|
||||||
<food-recipe />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="foodStore.recordSegment === 'daily'" class="food-calendar">
|
|
||||||
<food-daily />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="foodStore.recordSegment === 'timeline'" class="food-calendar">
|
|
||||||
<food-timeline />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -34,28 +20,35 @@
|
|||||||
import FoodRecipe from '@/components/Food/FoodRecipe.vue'
|
import FoodRecipe from '@/components/Food/FoodRecipe.vue'
|
||||||
import FoodDaily from '@/components/Food/FoodDaily.vue'
|
import FoodDaily from '@/components/Food/FoodDaily.vue'
|
||||||
import FoodTimeline from '@/components/Food/FoodTimeline.vue'
|
import FoodTimeline from '@/components/Food/FoodTimeline.vue'
|
||||||
import { Search } from '@element-plus/icons-vue'
|
|
||||||
import { reactive } from 'vue'
|
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
|
import type { TabsPaneContext } from 'element-plus'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|
||||||
const foodInfo = reactive({
|
onMounted(async () => {
|
||||||
query: ''
|
await foodStore.queryCategory()
|
||||||
|
await foodStore.queryRecipeList()
|
||||||
})
|
})
|
||||||
|
|
||||||
const recordSegmentList = [{
|
const handleClick = async (tab: TabsPaneContext) => {
|
||||||
label: '菜谱',
|
switch (tab.paneName) {
|
||||||
value: 'recipe'
|
case 'recipe':
|
||||||
},
|
await foodStore.queryCategory()
|
||||||
{
|
await foodStore.queryRecipeList()
|
||||||
label: '日历',
|
break
|
||||||
value: 'daily'
|
case 'daily':
|
||||||
},
|
foodStore.queryDateType = 'month'
|
||||||
{
|
await foodStore.queryRecordList()
|
||||||
label: '时间轴',
|
break
|
||||||
value: 'timeline'
|
case 'timeline':
|
||||||
}]
|
foodStore.queryDateType = 'year'
|
||||||
|
await foodStore.queryRecordList()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -63,12 +56,5 @@ const recordSegmentList = [{
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
|
||||||
.food-segment {
|
|
||||||
padding: 5px 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="food-recipe-form common-mobile-view">
|
<div class="food-recipe-form common-mobile-view">
|
||||||
|
<h2 style="text-align: center; margin: 0;">
|
||||||
|
{{ foodStore.recipeApiType === 'ADD' ? '新增菜谱' : '编辑菜谱' }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<el-steps :active="currentStepIndex"
|
||||||
|
align-center finish-status="success">
|
||||||
|
<el-step title="基础信息" />
|
||||||
|
<el-step title="食材信息" />
|
||||||
|
<el-step title="步骤信息" />
|
||||||
|
</el-steps>
|
||||||
|
|
||||||
<el-form ref="formRef" :model="foodStore.currentRecipe"
|
<el-form ref="formRef" :model="foodStore.currentRecipe"
|
||||||
:rules="rules" label-width="60px">
|
:rules="rules" label-width="60px">
|
||||||
<el-form-item label="名称" prop="name">
|
<el-form-item v-if="currentStepIndex === 0" label="名称" prop="name">
|
||||||
<el-input v-model="foodStore.currentRecipe.name" autocomplete="off"
|
<el-input v-model="foodStore.currentRecipe.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="category">
|
<el-form-item v-if="currentStepIndex === 0" label="菜系" prop="category">
|
||||||
<el-select v-model="foodStore.currentRecipe.category"
|
<el-select v-model="foodStore.currentRecipe.category"
|
||||||
placeholder="请选择或输入菜系" allow-create filterable>
|
placeholder="请选择或输入菜系" allow-create filterable>
|
||||||
<el-option v-for="(item, index) in foodCategoryList"
|
<el-option v-for="(item, index) in foodCategoryList"
|
||||||
@@ -17,32 +28,42 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="推荐" prop="recommendRate">
|
<el-form-item v-if="currentStepIndex === 0" label="推荐" prop="recommendRate">
|
||||||
<el-rate v-model="foodStore.currentRecipe.recommendRate"
|
<el-rate v-model="foodStore.currentRecipe.recommendRate"
|
||||||
:colors="foodStore.rateColorList"/>
|
:colors="foodStore.rateColorList"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="食材">
|
<el-form-item v-if="currentStepIndex === 0" label="共享">
|
||||||
<food-material-form />
|
<el-radio-group v-model="foodStore.currentRecipe.isShare">
|
||||||
|
<el-radio :value="false">否</el-radio>
|
||||||
|
<el-radio :value="true">是</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="步骤">
|
<el-form-item v-if="currentStepIndex === 0" label="备注">
|
||||||
<food-step-form />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="备注">
|
|
||||||
<el-input v-model="foodStore.currentRecipe.remark" autocomplete="off"
|
<el-input v-model="foodStore.currentRecipe.remark" autocomplete="off"
|
||||||
placeholder="请输入备注" clearable
|
placeholder="请输入备注" clearable
|
||||||
type="textarea" :rows="3"
|
type="textarea" :rows="3"
|
||||||
show-word-limit maxlength="50">
|
show-word-limit maxlength="50">
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="currentStepIndex === 1" label="食材">
|
||||||
|
<food-material-form />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="currentStepIndex === 2" label="步骤">
|
||||||
|
<food-step-form />
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<el-button type="primary" @click="confirmClick">确认</el-button>
|
<el-button v-if="currentStepIndex !== 0"
|
||||||
<el-button v-if="foodStore.recipeApiType === 'UPDATE'"
|
type="primary" @click="currentStepIndex--">上一步</el-button>
|
||||||
type="danger" @click="deleteClick">删除</el-button>
|
<el-button v-if="currentStepIndex !== 2"
|
||||||
|
type="primary" @click="currentStepIndex++">下一步</el-button>
|
||||||
|
<el-button v-if="currentStepIndex === 2"
|
||||||
|
type="primary" @click="confirmClick">确认</el-button>
|
||||||
<el-button type="info" @click="cancelClick">取消</el-button>
|
<el-button type="info" @click="cancelClick">取消</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,7 +81,6 @@ import { useFoodStore } from '@/store/food'
|
|||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
import { foodRules } from '@/utils/element/elRules'
|
import { foodRules } from '@/utils/element/elRules'
|
||||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
|
||||||
|
|
||||||
const rules = reactive<FormRules>(foodRules)
|
const rules = reactive<FormRules>(foodRules)
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
@@ -68,6 +88,8 @@ const foodStore = useFoodStore()
|
|||||||
|
|
||||||
const foodCategoryList = ['家常菜', '淮扬菜', '甜点', '其他']
|
const foodCategoryList = ['家常菜', '淮扬菜', '甜点', '其他']
|
||||||
|
|
||||||
|
const currentStepIndex = ref(0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点击确认按钮
|
* 点击确认按钮
|
||||||
*/
|
*/
|
||||||
@@ -81,14 +103,6 @@ const confirmClick = () => {
|
|||||||
const cancelClick = () => {
|
const cancelClick = () => {
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteClick = () => {
|
|
||||||
commonElMessageBox('是否确认删除该菜谱?').then(() => {
|
|
||||||
foodStore.recipeApiType = 'DELETE'
|
|
||||||
foodStore.handleRecipeApi(foodStore.currentRecipe)
|
|
||||||
history.back()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="food-record-form common-mobile-view">
|
<div class="food-record-form common-mobile-view">
|
||||||
|
<h2 style="text-align: center; margin: 0;">
|
||||||
|
{{ foodStore.recordApiType === 'ADD' ? '新增记录' : '编辑记录' }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
<el-form ref="formRef" :model="foodStore.currentRecord"
|
<el-form ref="formRef" :model="foodStore.currentRecord"
|
||||||
:rules="rules" label-width="80">
|
:rules="rules" label-width="80">
|
||||||
<el-form-item label="菜谱名称" prop="name">
|
<el-form-item label="菜谱名称" prop="name">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
base: './',
|
base: '/food-hub/',
|
||||||
resolve: {
|
resolve: {
|
||||||
// 配置路径别名
|
// 配置路径别名
|
||||||
alias: {
|
alias: {
|
||||||
@@ -20,12 +20,11 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 5175,
|
|
||||||
proxy: {
|
proxy: {
|
||||||
'/food-service': {
|
'/food-api': {
|
||||||
target: 'http://127.0.0.1:8100',
|
target: 'http://127.0.0.1:8083',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (item) => item.replace(/^\/food-service/, '')
|
rewrite: (item) => item.replace(/^\/food-api/, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user