feat:增加vant组件库

This commit is contained in:
2026-02-12 17:27:14 +08:00
parent 1b8bd5a7e2
commit 8940b3972a
29 changed files with 241 additions and 269 deletions

View File

@@ -23,7 +23,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 +31,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",

50
pnpm-lock.yaml generated
View File

@@ -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
@@ -77,6 +77,9 @@ importers:
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)
@@ -975,6 +978,14 @@ 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/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==}
@@ -1043,6 +1054,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:
@@ -1075,8 +1089,8 @@ 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'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
@@ -3199,6 +3213,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'}
@@ -4368,6 +4387,12 @@ 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/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))':
@@ -4473,6 +4498,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
@@ -4509,13 +4536,13 @@ 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: {}
ajv@6.12.6: ajv@6.12.6:
dependencies: dependencies:
@@ -5378,8 +5405,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: {}
@@ -6940,6 +6967,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: {}

View File

@@ -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',

View File

@@ -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>

View File

@@ -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">

View File

@@ -15,22 +15,14 @@
<el-empty v-else description="暂无成果" /> <el-empty v-else description="暂无成果" />
<div class="record-info"> <van-swipe-cell>
<div class="record-info" @click="viewFoodRecipeClick()">
<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.foodItem.name }}</span>
</div> </div>
</div> </div>
<div class="button-info">
<el-button type="primary" :icon="Edit" circle :size="'small'"
@click="editFoodRecipeClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
@click="viewFoodRecipeClick()" />
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
@click="deleteFoodRecipeClick()" />
</div>
</div> </div>
<div class="bottom-container"> <div class="bottom-container">
@@ -57,11 +49,18 @@
</div> </div>
</div> </div>
</div> </div>
<template #right>
<van-button square type="primary" text="编辑" style="height: 100%;"
@click="editFoodRecipeClick"/>
<van-button square type="danger" text="删除" style="height: 100%;"
@click="deleteFoodRecipeClick()"/>
</template>
</van-swipe-cell>
</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 { computed, defineProps, onMounted, PropType, reactive } from 'vue' import { computed, defineProps, onMounted, PropType, reactive } from 'vue'
import { IFoodRecipe } from '@/types/food.ts' import { IFoodRecipe } from '@/types/food.ts'

View File

@@ -1,68 +1,32 @@
<template> <template>
<div v-if="isMobile()" class="mobile-common-query card-item"> <div class="mobile-common-query">
<div class="query-item"> <el-select
<span>分类</span> v-model="foodStore.queryRecipe.name"
<el-segmented v-model="foodStore.queryRecipe.category" filterable clearable
:options="queryCategoryList" placeholder="请选择菜谱名称"
@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: '按月选择', const result = await searchFoodRecipeApi(value)
value: 'month' foodStore.foodRecipeList = [result]
},
{
label: '按年选择',
value: 'year'
}]
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({
label: '全部',
value: ''
})
foodStore.foodCategoryList.forEach((item) => {
queryCategoryList.value.push({
label: item,
value: item
})
})
}
const queryFoodSegmented = async () => {
foodStore.pageInfo = {
currentPage: 1,
pageSize: 3,
totalSize: 1
}
foodStore.foodRecipeList = []
await foodStore.queryMobileFoodRecipeList()
} }
</script> </script>

View File

@@ -1,25 +1,26 @@
<template> <template>
<div class="password-card card-item"> <van-swipe-cell>
<div class="password-card card-item" @click="editClick">
<el-avatar class="avatar-info">{{ passwordItem.title.charAt(0) }}</el-avatar> <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;

View File

@@ -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>
@@ -174,12 +169,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>

View File

@@ -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()

View File

@@ -1,4 +1,5 @@
<template> <template>
<van-config-provider :theme="appStore.isDark ? 'dark' : 'light'">
<section class="layout-mobile-home" <section class="layout-mobile-home"
:class="{ 'hide-tab-bar': appStore.isShowMobileBack }"> :class="{ 'hide-tab-bar': appStore.isShowMobileBack }">
<title-bar /> <title-bar />
@@ -7,6 +8,7 @@
<tab-bar v-if="!appStore.isShowMobileBack"/> <tab-bar v-if="!appStore.isShowMobileBack"/>
</section> </section>
</van-config-provider>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -25,6 +25,9 @@ import 'animate.css'
// 注册svg-icon // 注册svg-icon
import 'virtual:svg-icons-register' import 'virtual:svg-icons-register'
import Vant from 'vant'
import 'vant/lib/index.css'
// 创建Vue3实例 // 创建Vue3实例
const app = createApp(App) const app = createApp(App)
// 使用路由 // 使用路由
@@ -34,5 +37,6 @@ app.use(createPinia())
// 使用Element UI Plus // 使用Element UI Plus
app.use(ElementPlus, { locale: zhCn }) app.use(ElementPlus, { locale: zhCn })
app.use(VCalendar, {}) app.use(VCalendar, {})
app.use(Vant)
// 挂载到根组件上 // 挂载到根组件上
app.mount('#app') app.mount('#app')

View File

@@ -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)
} }
} }

View File

@@ -50,7 +50,8 @@ export const useFoodStore = defineStore('food', {
} as IFoodRecord, } as IFoodRecord,
foodNameList: [] as string[], foodNameList: [] as string[],
queryRecipe: { queryRecipe: {
category: '' category: '',
name: ''
}, },
queryRecord: { queryRecord: {
year: getCurrentYear(), year: getCurrentYear(),

View File

@@ -0,0 +1,47 @@
interface RGB {
r: number;
g: number;
b: number;
}
/**
* 十六进制颜色转RGB对象
*/
export const hexToRgb = (hex: string): RGB | null => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
}
/**
* 计算变亮后的颜色
*/
export const calcLighten = (baseColor: string, ratio: number): string => {
const rgb = hexToRgb(baseColor)
if (!rgb) return baseColor
const actualRatio = ratio / 100
const r = rgb.r * (1 - actualRatio) + 255 * actualRatio
const g = rgb.g * (1 - actualRatio) + 255 * actualRatio
const b = rgb.b * (1 - actualRatio) + 255 * actualRatio
return `rgb(${r}, ${g}, ${b})`
}
/**
* 计算变暗后的颜色
*/
export const calcDarken = (baseColor: string, ratio: number): string => {
const rgb = hexToRgb(baseColor)
if (!rgb) return baseColor
const actualRatio = ratio / 100
const r = rgb.r * (1 - actualRatio)
const g = rgb.g * (1 - actualRatio)
const b = rgb.b * (1 - actualRatio)
return `rgb(${r}, ${g}, ${b})`
}

View File

@@ -9,12 +9,11 @@
:key="index" :date-item="item" /> :key="index" :date-item="item" />
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import DraggableButton from '@/components/Common/DraggableButton.vue'
import AnniversaryQuery from '@/components/Anniversary/AnniversaryQuery.vue' import AnniversaryQuery from '@/components/Anniversary/AnniversaryQuery.vue'
import AnniversaryCard from '@/components/Anniversary/AnniversaryCard.vue' import AnniversaryCard from '@/components/Anniversary/AnniversaryCard.vue'
import { useAnniversaryStore } from '@/store/anniversary.ts' import { useAnniversaryStore } from '@/store/anniversary.ts'

View File

@@ -14,14 +14,13 @@
:key="index" :item="item"/> :key="index" :item="item"/>
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import AssetQuery from '@/components/Asset/AssetQuery.vue' import AssetQuery from '@/components/Asset/AssetQuery.vue'
import AssetCard from '@/components/Asset/AssetCard.vue' import AssetCard from '@/components/Asset/AssetCard.vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useAssetStore } from '@/store/asset.ts' import { useAssetStore } from '@/store/asset.ts'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'

View File

@@ -4,8 +4,7 @@
<bill-daily-list v-if="billStore.billDateType === 'year'"/> <bill-daily-list v-if="billStore.billDateType === 'year'"/>
<bill-calendar v-if="billStore.billDateType === 'month'" /> <bill-calendar v-if="billStore.billDateType === 'month'" />
<el-button type="primary" :icon="Plus" circle size="large" <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
@click="addNewBillRecordClick" class="add-new" />
</div> </div>
</template> </template>
@@ -15,7 +14,6 @@ import BillCalendar from '@/components/Bill/BillCalendar.vue'
import BillDailyList from '@/components/Bill/BillDailyList.vue' import BillDailyList from '@/components/Bill/BillDailyList.vue'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useBillStore } from '@/store/bill.ts' import { useBillStore } from '@/store/bill.ts'
import { Plus } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { getCurrentMonth, getCurrentYear } from 'vue3-common/utils/dateUtil' import { getCurrentMonth, getCurrentYear } from 'vue3-common/utils/dateUtil'
@@ -41,7 +39,7 @@ onMounted(async () => {
await billStore.refreshInfo() await billStore.refreshInfo()
}) })
const addNewBillRecordClick = () => { const addClick = () => {
billStore.billApiType = 'ADD' billStore.billApiType = 'ADD'
billStore.currentBillRecord = billStore.getEmptyBillRecord() billStore.currentBillRecord = billStore.getEmptyBillRecord()
router.push({ name: 'BillDetailId', params: { id: 0 } }) router.push({ name: 'BillDetailId', params: { id: 0 } })

View File

@@ -11,7 +11,7 @@
</div> </div>
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
<arrive-bottom v-if="foodStore.isScrollEnd"/> <arrive-bottom v-if="foodStore.isScrollEnd"/>
</div> </div>
@@ -23,7 +23,6 @@ import FoodCard from '@/components/Food/FoodCard.vue'
import ArriveBottom from '@/components/Common/ArriveButton.vue' import ArriveBottom from '@/components/Common/ArriveButton.vue'
import { useFoodStore } from '@/store/food.ts' import { useFoodStore } from '@/store/food.ts'
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const foodRecipeRef = ref() const foodRecipeRef = ref()

View File

@@ -4,14 +4,13 @@
<food-calendar /> <food-calendar />
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import FoodStats from '@/components/Food/FoodStats.vue' import FoodStats from '@/components/Food/FoodStats.vue'
import FoodCalendar from '@/components/Food/FoodCalendar.vue' import FoodCalendar from '@/components/Food/FoodCalendar.vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useFoodStore } from '@/store/food.ts' import { useFoodStore } from '@/store/food.ts'
import { onMounted } from 'vue' import { onMounted } from 'vue'

View File

@@ -2,13 +2,12 @@
<div class="mobile-journal-view common-mobile-view"> <div class="mobile-journal-view common-mobile-view">
<journal-calendar /> <journal-calendar />
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import JournalCalendar from '@/components/Journal/JournalCalendar.vue' import JournalCalendar from '@/components/Journal/JournalCalendar.vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useJournalStore } from '@/store/journal.ts' import { useJournalStore } from '@/store/journal.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { onMounted } from 'vue' import { onMounted } from 'vue'

View File

@@ -7,13 +7,12 @@
<kpi-calendar /> <kpi-calendar />
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import KpiCalendar from '@/components/kpi/KpiCalendar.vue' import KpiCalendar from '@/components/kpi/KpiCalendar.vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useKpiStore } from '@/store/kpi.ts' import { useKpiStore } from '@/store/kpi.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { computed, onMounted } from 'vue' import { computed, onMounted } from 'vue'

View File

@@ -14,14 +14,13 @@
:key="index" :item="item"/> :key="index" :item="item"/>
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import LedgerQuery from '@/components/Ledger/LedgerQuery.vue' import LedgerQuery from '@/components/Ledger/LedgerQuery.vue'
import LedgerCard from '@/components/Ledger/LedgerCard.vue' import LedgerCard from '@/components/Ledger/LedgerCard.vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useLedgerStore } from '@/store/ledger.ts' import { useLedgerStore } from '@/store/ledger.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { formatAmount } from 'vue3-common/utils/numberUtil' import { formatAmount } from 'vue3-common/utils/numberUtil'

View File

@@ -1,7 +1,8 @@
<template> <template>
<div class="mobile-password-detail common-mobile-view"> <div class="mobile-password-detail common-mobile-view">
<el-form ref="formRef" :model="passwordStore.currentPassword" <el-form ref="formRef" :model="passwordStore.currentPassword"
:rules="rules" label-width="70px"> :rules="rules" label-position="top" label-width="70px"
class="form-item">
<el-form-item label="标题" prop="title"> <el-form-item label="标题" prop="title">
<el-input v-model="passwordStore.currentPassword.title" autocomplete="off" <el-input v-model="passwordStore.currentPassword.title" autocomplete="off"
placeholder="请输入标题" clearable placeholder="请输入标题" clearable
@@ -63,6 +64,8 @@
<div class="button-container"> <div class="button-container">
<el-button type="primary" @click="confirmClick">确认</el-button> <el-button type="primary" @click="confirmClick">确认</el-button>
<el-button v-if="passwordStore.passwordApiType === 'UPDATE'"
type="danger" @click="deleteClick">删除</el-button>
<el-button type="info" @click="cancelClick">取消</el-button> <el-button type="info" @click="cancelClick">取消</el-button>
</div> </div>
</div> </div>
@@ -74,6 +77,8 @@ import { useAppStore } from '@/store/app.ts'
import { FormRules } from 'element-plus' import { FormRules } from 'element-plus'
import { passwordRules } from '@/utils/element/elRules.ts' import { passwordRules } from '@/utils/element/elRules.ts'
import { onMounted, reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { IPassword } from '@/types/password.ts'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const formRef = ref() const formRef = ref()
const rules = reactive<FormRules>(passwordRules) const rules = reactive<FormRules>(passwordRules)
@@ -95,6 +100,13 @@ const confirmClick = () => {
}) })
} }
const deleteClick = () => {
commonElMessageBox(`请确认是否删除该密码记录: ${passwordStore.currentPassword.title}?`).then(() => {
passwordStore.passwordApiType = 'DELETE'
passwordStore.handlePasswordApi(passwordStore.currentPassword)
})
}
const cancelClick = () => { const cancelClick = () => {
history.back() history.back()
appStore.isShowMobileBack = false appStore.isShowMobileBack = false

View File

@@ -9,12 +9,11 @@
:key="index" :password-item="item"/> :key="index" :password-item="item"/>
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import DraggableButton from '@/components/Common/DraggableButton.vue'
import PasswordQuery from '@/components/Password/PasswordQuery.vue' import PasswordQuery from '@/components/Password/PasswordQuery.vue'
import PasswordCard from '@/components/Password/PasswordCard.vue' import PasswordCard from '@/components/Password/PasswordCard.vue'
import { usePasswordStore } from '@/store/password.ts' import { usePasswordStore } from '@/store/password.ts'

View File

@@ -9,12 +9,11 @@
:key="index" :product-item="item"/> :key="index" :product-item="item"/>
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import DraggableButton from '@/components/Common/DraggableButton.vue'
import ProductQuery from '@/components/Product/ProductQuery.vue' import ProductQuery from '@/components/Product/ProductQuery.vue'
import ProductCard from '@/components/Product/ProductCard.vue' import ProductCard from '@/components/Product/ProductCard.vue'
import { useProductStore } from '@/store/product.ts' import { useProductStore } from '@/store/product.ts'

View File

@@ -4,14 +4,13 @@
<travel-calendar /> <travel-calendar />
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import TravelStats from '@/components/Travel/TravelStats.vue' import TravelStats from '@/components/Travel/TravelStats.vue'
import TravelCalendar from '@/components/Travel/TravelCalendar.vue' import TravelCalendar from '@/components/Travel/TravelCalendar.vue'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useTravelStore } from '@/store/travel.ts' import { useTravelStore } from '@/store/travel.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { onMounted } from 'vue' import { onMounted } from 'vue'

View File

@@ -11,7 +11,7 @@
</div> </div>
</div> </div>
<draggable-button @click="addClick"/> <van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
<arrive-bottom v-if="travelStore.isScrollEnd"/> <arrive-bottom v-if="travelStore.isScrollEnd"/>
</div> </div>
@@ -23,7 +23,6 @@ import ArriveBottom from '@/components/Common/ArriveButton.vue'
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
import TravelCard from '@/components/Travel/TravelCard.vue' import TravelCard from '@/components/Travel/TravelCard.vue'
import { useTravelStore } from '@/store/travel.ts' import { useTravelStore } from '@/store/travel.ts'
import DraggableButton from '@/components/Common/DraggableButton.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const travelStore = useTravelStore() const travelStore = useTravelStore()

View File

@@ -1,6 +1,5 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import path from 'path' import path from 'path'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { viteMockServe } from 'vite-plugin-mock' import { viteMockServe } from 'vite-plugin-mock'