vue使用语法糖

This commit is contained in:
鼠子
2025-07-15 13:27:02 +08:00
parent 7b4cef5d35
commit a96efc9f07
12 changed files with 1030 additions and 1317 deletions

View File

@@ -1,3 +1,12 @@
<script setup>
const systemInfo = {
version: '2.0.0 Alpha',
releaseDate: '2025-07-15',
framework: 'FastAPI + Vue'
}
</script>
<template>
<div>
<h2 class="text-h4 mb-4">关于 Findreve</h2>
@@ -61,19 +70,4 @@
</v-card-text>
</v-card>
</div>
</template>
<script>
export default {
name: 'AboutSystemComponent',
data() {
return {
systemInfo: {
version: '2.0.0 Alpha',
releaseDate: '2025-07-15',
framework: 'FastAPI + Vue'
}
}
}
};
</script>
</template>

View File

@@ -1,3 +1,45 @@
<script setup>
import { ref, watch, computed } from 'vue'
const props = defineProps({
items: {
type: Array,
default: () => []
}
})
// 仪表盘数据
const itemStats = ref({
total: 0,
normal: 0,
lost: 0,
scans: 0
})
/**
* 更新统计信息
*/
const updateStats = (items) => {
itemStats.value.total = items.length
itemStats.value.normal = items.filter(item => item.status === 'ok').length
itemStats.value.lost = items.filter(item => item.status === 'lost').length
itemStats.value.scans = items.reduce((sum, item) => sum + (item.views || 0), 0)
if (itemStats.value.scans === 0) itemStats.value.scans = Math.floor(Math.random() * 100) + 50
}
/**
* 计算百分比
*/
const getPercentage = (type) => {
if (itemStats.value.total === 0) return 0
return Math.round((itemStats.value[type] / itemStats.value.total) * 100)
}
watch(() => props.items, (newItems) => {
updateStats(newItems)
}, { immediate: true })
</script>
<template>
<div>
<h2 class="text-h4 mb-4">仪表盘</h2>
@@ -40,74 +82,4 @@
</v-col>
</v-row>
</div>
</template>
<script>
/**
* 仪表盘组件
*
* 显示物品统计信息和系统概览
*/
export default {
name: 'DashboardComponent',
props: {
items: {
type: Array,
default: () => []
}
},
data() {
return {
// 仪表盘数据
itemStats: {
total: 0,
normal: 0,
lost: 0,
scans: 0
}
};
},
watch: {
items: {
handler(newItems) {
this.updateStats(newItems);
},
immediate: true
}
},
methods: {
/**
* 更新统计信息
*
* @param {Array} items - 物品列表
*/
updateStats(items) {
// 计算物品总数
this.itemStats.total = items.length;
// 计算正常和丢失物品数量
this.itemStats.normal = items.filter(item => item.status === 'ok').length;
this.itemStats.lost = items.filter(item => item.status === 'lost').length;
// 假设扫描次数是从物品中累计的一个属性
this.itemStats.scans = items.reduce((sum, item) => sum + (item.views || 0), 0);
if (this.itemStats.scans === 0) this.itemStats.scans = Math.floor(Math.random() * 100) + 50; // 示例数据
},
/**
* 计算百分比
*
* @param {string} type - 物品类型normal或lost
* @returns {number} 百分比值
*/
getPercentage(type) {
if (this.itemStats.total === 0) return 0;
return Math.round((this.itemStats[type] / this.itemStats.total) * 100);
}
}
};
</script>
</template>

View File

@@ -1,3 +1,254 @@
<script setup>
import { ref, computed, watch } from 'vue'
import apiService from '@/services/api_service'
const props = defineProps({
items: {
type: Array,
required: true
}
})
const emit = defineEmits(['refresh'])
// 界面控制
const loading = ref(false)
const search = ref('')
const statusFilter = ref('all')
// 物品管理
const editItem = ref({
id: null,
key: '',
name: '',
icon: '',
phone: '',
status: 'ok',
context: ''
})
const defaultItem = {
id: null,
key: '',
name: '',
icon: '',
phone: '',
status: 'ok',
context: ''
}
// 对话框控制
const itemDialog = ref(false)
const deleteDialog = ref(false)
const qrDialog = ref(false)
const saving = ref(false)
const deleting = ref(false)
const formValid = ref(false)
// 选中的物品和删除项
const selectedItem = ref(null)
const deleteItem = ref(null)
// 表格配置
const headers = [
{ title: 'ID', key: 'id', sortable: true },
{ title: '物品名称', key: 'name', sortable: true },
{ title: '标识码', key: 'key', sortable: true },
{ title: '状态', key: 'status', sortable: true },
{ title: '创建时间', key: 'created_at', sortable: true },
{ title: '操作', key: 'actions', sortable: false }
]
const statusOptions = [
{ title: '全部状态', value: 'all' },
{ title: '正常', value: 'ok' },
{ title: '丢失', value: 'lost' }
]
/**
* 过滤后的物品列表
*/
const filteredItems = computed(() => {
let result = [...props.items]
if (statusFilter.value !== 'all') {
result = result.filter(item => item.status === statusFilter.value)
}
return result
})
/**
* 打开物品对话框
*/
const openItemDialog = (item = null) => {
editItem.value = item ? JSON.parse(JSON.stringify(item)) : JSON.parse(JSON.stringify(defaultItem))
if (!item) {
editItem.value.key = generateRandomKey()
}
itemDialog.value = true
}
/**
* 保存物品
*/
const saveItem = async () => {
if (!formValid.value) return
try {
saving.value = true
let data
if (editItem.value.id) {
const params = new URLSearchParams()
const { id, key, name, icon, phone, status, context } = editItem.value
params.append('id', id)
params.append('key', key)
params.append('name', name)
params.append('icon', icon || '')
params.append('phone', phone)
params.append('status', status)
if (status === 'lost' && context) {
params.append('context', context)
}
data = await apiService.patch(`/api/admin/items?${params.toString()}`, '')
} else {
const params = new URLSearchParams()
const { key, name, icon, phone } = editItem.value
params.append('key', key)
params.append('name', name)
params.append('icon', icon || '')
params.append('phone', phone)
data = await apiService.post(`/api/admin/items?${params.toString()}`, '')
}
if (data.code !== 0) {
throw new Error(data.msg || '保存物品失败')
}
itemDialog.value = false
emit('refresh')
} catch (error) {
console.error('保存物品错误:', error)
} finally {
saving.value = false
}
}
/**
* 确认删除物品
*/
const confirmDelete = (item) => {
deleteItem.value = item
deleteDialog.value = true
}
/**
* 确认删除物品
*/
const deleteItemConfirm = async () => {
if (!deleteItem.value?.id) return
try {
deleting.value = true
const data = await apiService.delete(`/api/admin/items?id=${encodeURIComponent(deleteItem.value.id)}`)
if (data.code !== 0) {
throw new Error(data.msg || '删除物品失败')
}
deleteDialog.value = false
emit('refresh')
} catch (error) {
console.error('删除物品错误:', error)
} finally {
deleting.value = false
}
}
/**
* 显示二维码
*/
const showQRCode = (item) => {
selectedItem.value = item
qrDialog.value = true
}
/**
* 获取二维码URL
*/
const getQRCodeUrl = (key) => {
const currentUrl = window.location.origin
const foundUrl = `${currentUrl}/found?key=${encodeURIComponent(key)}`
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(foundUrl)}`
}
/**
* 生成随机标识码
*/
const generateRandomKey = () => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
for (let i = 0; i < 8; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
return result
}
/**
* 获取状态对应的颜色
*/
const getStatusColor = (status) => {
const statusMap = {
ok: "success",
lost: "error",
default: "grey"
}
return statusMap[status] || statusMap.default
}
/**
* 获取状态对应的文本
*/
const getStatusText = (status) => {
const statusMap = {
ok: "正常",
lost: "丢失",
default: "未知"
}
return statusMap[status] || statusMap.default
}
/**
* 格式化日期显示
*/
const formatDate = (dateStr) => {
if (!dateStr) return "未知时间"
try {
const date = new Date(dateStr)
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).format(date)
} catch (e) {
return dateStr
}
}
/**
* 重置所有筛选条件
*/
const resetFilters = () => {
search.value = ''
statusFilter.value = 'all'
}
</script>
<template>
<div>
<div class="d-flex justify-space-between align-center mb-4">
@@ -260,345 +511,6 @@
</div>
</template>
<script>
import apiService from '@/services/api_service';
export default {
name: 'ItemsManagementComponent',
props: {
items: {
type: Array,
required: true
}
},
data() {
return {
// 界面控制
loading: false,
search: '',
statusFilter: 'all',
// 物品管理
editItem: {
id: null,
key: '',
name: '',
icon: '',
phone: '',
status: 'ok',
context: ''
},
defaultItem: {
id: null,
key: '',
name: '',
icon: '',
phone: '',
status: 'ok',
context: ''
},
// 对话框控制
itemDialog: false,
deleteDialog: false,
qrDialog: false,
saving: false,
deleting: false,
formValid: false,
// 选中的物品和删除项
selectedItem: null,
deleteItem: null,
// 表格配置
headers: [
{ title: 'ID', key: 'id', sortable: true },
{ title: '物品名称', key: 'name', sortable: true },
{ title: '标识码', key: 'key', sortable: true },
{ title: '状态', key: 'status', sortable: true },
{ title: '创建时间', key: 'created_at', sortable: true },
{ title: '操作', key: 'actions', sortable: false }
],
statusOptions: [
{ title: '全部状态', value: 'all' },
{ title: '正常', value: 'ok' },
{ title: '丢失', value: 'lost' }
]
}
},
computed: {
/**
* 过滤后的物品列表
*
* 根据搜索文本和状态筛选条件过滤物品列表
* @returns {Array} 过滤后的物品数组
*/
filteredItems() {
let result = [...this.items];
// 应用状态筛选
if (this.statusFilter !== 'all') {
result = result.filter(item => item.status === this.statusFilter);
}
return result;
}
},
methods: {
/**
* 打开物品对话框
*
* @param {Object|null} item - 要编辑的物品为null时表示添加新物品
*/
openItemDialog(item = null) {
if (item) {
this.editItem = JSON.parse(JSON.stringify(item)); // 深拷贝
} else {
this.editItem = JSON.parse(JSON.stringify(this.defaultItem));
// 为新物品生成一个随机标识码
this.editItem.key = this.generateRandomKey();
}
this.$nextTick(() => {
if (this.$refs.itemForm) {
this.$refs.itemForm.resetValidation();
}
});
this.itemDialog = true;
},
/**
* 保存物品
*
* 根据是否有ID决定是添加新物品还是更新现有物品
*/
async saveItem() {
if (!this.formValid) return;
try {
this.saving = true;
let data;
if (this.editItem.id) {
// 更新现有物品
const params = new URLSearchParams();
const { id, key, name, icon, phone, status, context } = this.editItem;
params.append('id', id);
params.append('key', key);
params.append('name', name);
params.append('icon', icon || '');
params.append('phone', phone);
params.append('status', status);
// 只有在状态为lost且有context时才添加context参数
if (status === 'lost' && context) {
params.append('context', context);
}
data = await apiService.patch(`/api/admin/items?${params.toString()}`, '');
} else {
// 添加新物品
const params = new URLSearchParams();
const { key, name, icon, phone } = this.editItem;
params.append('key', key);
params.append('name', name);
params.append('icon', icon || '');
params.append('phone', phone);
data = await apiService.post(`/api/admin/items?${params.toString()}`, '');
}
if (data.code !== 0) {
throw new Error(data.msg || '保存物品失败');
}
this.$nextTick(() => {
this.$root.$emit('show-toast', {
color: 'success',
message: this.editItem.id ? '物品更新成功' : '物品添加成功'
});
});
this.itemDialog = false;
this.refreshItems(); // 刷新物品列表
} catch (error) {
console.error('保存物品错误:', error);
this.$nextTick(() => {
this.$root.$emit('show-toast', {
color: 'error',
message: error.message || '保存物品失败'
});
});
} finally {
this.saving = false;
}
},
/**
* 确认删除物品
*
* @param {Object} item - 要删除的物品
*/
confirmDelete(item) {
this.deleteItem = item;
this.deleteDialog = true;
},
/**
* 确认删除物品
*/
async deleteItemConfirm() {
if (!this.deleteItem || !this.deleteItem.id) return;
try {
this.deleting = true;
const data = await apiService.delete(`/api/admin/items?id=${encodeURIComponent(this.deleteItem.id)}`);
if (data.code !== 0) {
throw new Error(data.msg || '删除物品失败');
}
this.$nextTick(() => {
this.$root.$emit('show-toast', {
color: 'success',
message: '物品已成功删除'
});
});
this.deleteDialog = false;
this.refreshItems(); // 刷新物品列表
} catch (error) {
console.error('删除物品错误:', error);
this.$nextTick(() => {
this.$root.$emit('show-toast', {
color: 'error',
message: error.message || '删除物品失败'
});
});
} finally {
this.deleting = false;
}
},
/**
* 显示二维码
*
* @param {Object} item - 要显示二维码的物品
*/
showQRCode(item) {
this.selectedItem = item;
this.qrDialog = true;
},
/**
* 获取二维码URL
*
* @param {string} key - 物品标识码
* @returns {string} 二维码图片URL
*/
getQRCodeUrl(key) {
// 使用QR Server API生成二维码
const currentUrl = window.location.origin;
const foundUrl = `${currentUrl}/found?key=${encodeURIComponent(key)}`;
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(foundUrl)}`;
},
/**
* 生成随机标识码
*
* @returns {string} 随机生成的标识码
*/
generateRandomKey() {
// 生成一个8位的随机字母数字组合
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < 8; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
},
/**
* 获取状态对应的颜色
*
* @param {string} status - 物品状态
* @returns {string} 对应的颜色名称
*/
getStatusColor(status) {
const statusMap = {
ok: "success",
lost: "error",
default: "grey"
};
return statusMap[status] || statusMap.default;
},
/**
* 获取状态对应的文本
*
* @param {string} status - 物品状态
* @returns {string} 对应的状态文本
*/
getStatusText(status) {
const statusMap = {
ok: "正常",
lost: "丢失",
default: "未知"
};
return statusMap[status] || statusMap.default;
},
/**
* 格式化日期显示
*
* @param {string} create_time - 日期字符串
* @returns {string} 格式化的日期文本
*/
formatDate(dateStr) {
if (!dateStr) return "未知时间";
try {
const date = new Date(dateStr);
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).format(date);
} catch (e) {
return dateStr;
}
},
/**
* 重置所有筛选条件
*/
resetFilters() {
this.search = '';
this.statusFilter = 'all';
},
/**
* 刷新物品列表
*/
refreshItems() {
this.$emit('refresh');
}
}
};
</script>
<style scoped>
/* 确保数据表格在移动设备上响应式滚动 */
@media (max-width: 768px) {

View File

@@ -1,3 +1,18 @@
<script setup>
import CacheStatus from '@/components/CacheStatus.vue'
const userInfo = {
username: 'admin',
email: 'admin@example.com'
}
const settings = {
darkMode: false,
notifications: true
}
</script>
<template>
<div>
<h2 class="text-h4 mb-4">用户设置</h2>
@@ -67,27 +82,4 @@
<p class="text-caption text-center mt-4">更多设置功能正在开发中...</p>
</div>
</template>
<script>
import CacheStatus from '@/components/CacheStatus.vue';
export default {
name: 'UserSettingsComponent',
components: {
CacheStatus
},
data() {
return {
userInfo: {
username: 'admin',
email: 'admin@example.com'
},
settings: {
darkMode: false,
notifications: true
}
}
}
};
</script>
</template>