// 使用Vue 3 API
const { createApp, ref, reactive, computed, onMounted, onShow, watch } = Vue;
const { createRouter, createWebHistory } = VueRouter;
const { createVuetify } = Vuetify;
var toTime = function (dateStr) {
var date = new Date(dateStr).toJSON();
return new Date(+new Date(date) + 8 * 3600 * 1000).toISOString().replace(/T/g, ' ').replace(/\.[\d]{3}Z/, '');
}
Date.prototype.Format = function (fmt) {
// 将当前
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
// 先替换年份
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
// 再依次替换其他时间日期内容
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
// 日期选择组件
const DatePickerDialog = {
name: 'DatePickerDialog',
props: {
modelValue: {
type: String,
default: ''
},
label: {
type: String,
default: '选择日期'
},
disabled: {
type: Boolean,
default: false
},
format: {
type: String,
default: 'yyyy-MM-dd'
}
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const showPicker = ref(false);
const internalValue = ref(new Date());
const displayText = ref('');
// 打开选择器
const openPicker = () => {
if (!props.disabled) {
showPicker.value = true;
}
};
// 确认选择
const confirmSelection = () => {
emit('update:modelValue', internalValue.value?.Format(props.format));
displayText.value = internalValue.value?.Format(props.format);
showPicker.value = false;
};
// 取消选择
const cancelSelection = () => {
// 重置为原始值
internalValue.value = new Date();
displayText.value = internalValue.value.Format(props.format);
showPicker.value = false;
};
// 组件挂载时格式化显示
onMounted(() => {
if (props.modelValue != '') {
internalValue.value = new Date(props.modelValue);
displayText.value = props.modelValue;
}else{
displayText.value = internalValue.value.Format(props.format);
emit('update:modelValue', displayText.value);
}
});
return {
showPicker,
internalValue,
displayText,
openPicker,
confirmSelection,
cancelSelection
};
},
template: `
{{ label }}
{{ displayText }}
mdi-calendar
{{ label }}
mdi-close
取消
确认
`
};
// 颜色选择按钮组件
const ColorPickerButton = {
name: 'ColorPickerButton',
props: {
modelValue: {
type: String,
default: '#000000'
}
},
emits: ['update:modelValue'],
template: `
{{ modelValue }}
选择颜色
取消
确定
`,
setup(props, { emit }) {
const showColorPicker = ref(false);
const internalColor = ref(props.modelValue);
// 根据背景色计算文字颜色(黑色或白色)
const getTextColor = (bgColor) => {
// 移除#号
const hex = bgColor.replace('#', '');
// 转换为RGB
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
// 计算亮度
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128 ? '#000000' : '#ffffff';
};
// 处理颜色变化
const handleColorChange = () => {
// 实时更新模型值
emit('update:modelValue', internalColor.value);
};
// 确认颜色选择
const confirmColorSelection = () => {
emit('update:modelValue', internalColor.value);
showColorPicker.value = false;
};
return {
showColorPicker,
internalColor,
getTextColor,
handleColorChange,
confirmColorSelection
};
}
};
// 创建Vuetify实例
const vuetify = createVuetify({
theme: {
defaultTheme: 'light',
themes: {
light: {
colors: {
primary: '#1976D2',
secondary: '#424242',
accent: '#82B1FF',
error: '#FF5252',
info: '#2196F3',
success: '#4CAF50',
warning: '#FFC107'
}
}
}
}
});
const snackbarStore = reactive({
queue: ref([]),
// 成功消息
success(message, options = {}) {
this.queue.push({ text: message, color: 'success', timeout: 1000 })
},
// 错误消息
error(message, options = {}) {
this.queue.push({ text: message, color: 'error', timeout: 1000 })
},
// 警告消息
warning(message, options = {}) {
this.queue.push({ text: message, color: 'warning', timeout: 1000 })
},
// 信息消息
info(message, options = {}) {
this.queue.push({ text: message, color: 'info', timeout: 1000 })
},
// 清空队列
clear() {
this.queue = [];
}
});
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 全局状态管理
const store = reactive({
isLoggedIn: false,
token: localStorage.getItem('token') || '',
userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null'),
// 保存token和用户信息
saveCredentials(token, userInfo) {
this.token = token;
this.userInfo = userInfo;
localStorage.setItem('token', token);
localStorage.setItem('userInfo', JSON.stringify(userInfo));
this.isLoggedIn = true;
},
// 清除token和用户信息
clearCredentials() {
this.token = '';
this.userInfo = null;
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
this.isLoggedIn = false;
},
// 检查登录状态
checkLogin() {
return !!this.token;
},
// 退出登录
logout() {
this.clearCredentials();
router.push('/login');
},
});
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
const LoginComponent = {
template: `
听力健康管家 - 登录
{{ store.error }}
登录
`,
setup() {
const username = ref('');
const password = ref('');
const loginForm = ref(null);
const loading = ref(false);
const handleLogin = async () => {
// 验证表单
if (!loginForm.value.validate()) {
return;
}
loading.value = true;
// 这里应该调用实际的登录API
fetch('/admin/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: username.value, password: md5(password.value) })
})
.then(response => response.json())
.then(result => {
loading.value = false;
if (result.code === 0) {
store.saveCredentials(result.data.token, { username: username.value });
router.push('/');
} else {
snackbarStore.error( result.message || '登录失败,请重试');
}
})
.catch(error => {
console.error('登录失败:', error);
loading.value = false;
snackbarStore.error('网络错误,请重试');
});
};
return {
store,
username,
password,
handleLogin,
loading,
loginForm
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 听力师管理组件
const AudiologistsComponent = {
template: `
听力师管理
mdi-refresh刷新
重置密码
删除
确认删除
确定要删除听力师 "{{ selectedAudiologist?.nickname }}" 吗?此操作不可撤销。
取消
{{ deleting ? '删除中...' : '删除' }}
确认重置密码
确定要重置听力师 "{{ selectedAudiologist?.nickname }}" 的密码吗?
取消
{{ resetting ? '重置中...' : '重置密码' }}
`,
setup() {
const showDeleteDialog = ref(false);
const selectedAudiologist = ref(null);
const deleting = ref(false);
const showResetPasswordDialog = ref(false);
const resetting = ref(false);
const audiologists = ref([]);
const headers = ref([
{ title: 'ID', key: 'id', width: '280px' },
{ title: '手机号', key: 'phone' },
{ title: '昵称', key: 'nickname' },
{ title: '医院', key: 'hospital' },
{ title: '简介', key: 'description' },
{ title: '创建时间', key: 'created_at', value: (row) => toTime(row.created_at) },
{ title: '操作', key: 'actions', width: '180px' }
]);
const loading = ref(false);
const searchParams = reactive({
keyword: ''
});
const pagination = reactive({
page: 1,
pageSize: 10,
total: 0
});
const loadAudiologists = async (page = 1, pageSize = 10, params = {}) => {
loading.value = true;
fetch('/admin/audiologists', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
body: JSON.stringify({
page,
pageSize,
...params
})
})
.then((response) => response.json())
.then((result) => {
loading.value = false;
if (result.code === 0) {
audiologists.value = result.data || [];
pagination.total = result.total || 0;
} else {
snackbarStore.error(result.message || '加载听力师数据失败');
}
}).catch((error) => {
loading.value = false;
snackbarStore.error('加载听力师数据失败');
console.error('加载听力师列表失败:', error);
});
};
const searchAudiologists = () => {
pagination.page = 1;
loadAudiologists(pagination.page, pagination.pageSize, searchParams);
};
const resetSearch = () => {
searchParams.keyword = '';
pagination.page = 1;
loadAudiologists(pagination.page, pagination.pageSize, {});
};
const refreshData = () => {
loadAudiologists(pagination.page, pagination.pageSize, searchParams);
};
const handlePageChange = (newPage) => {
pagination.page = newPage;
loadAudiologists(pagination.page, pagination.pageSize, searchParams);
};
const handlePageSizeChange = (newSize) => {
pagination.pageSize = newSize;
pagination.page = 1;
loadAudiologists(pagination.page, pagination.pageSize, searchParams);
};
const confirmResetPassword = (audiologist) => {
selectedAudiologist.value = audiologist;
showResetPasswordDialog.value = true;
};
const resetPassword = async () => {
loading.value = true;
showResetPasswordDialog.value = false;
fetch(`/admin/audiologists/${selectedAudiologist.value.id}/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
}).then((response) => response.json())
.then((result) => {
loading.value = false;
if (result.code === 0) {
alert(`密码重置成功,请牢记新密码为:${result.data.password}`);
} else {
snackbarStore.error(result.message || '密码重置失败');
}
}).catch((error) => {
loading.value = false;
snackbarStore.error('密码重置失败');
console.error('重置密码失败:', error);
}).finally(()=>{
selectedAudiologist.value = null;
});
};
// 确认删除
const confirmDelete = (audiologist) => {
selectedAudiologist.value = audiologist;
showDeleteDialog.value = true;
};
const deleteAudiologist = async () => {
deleting.value = true;
showDeleteDialog.value = false;
fetch(`/admin/audiologists/${selectedAudiologist.value.id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
}).then((response) => response.json())
.then((result) => {
deleting.value = false;
if (result.code === 0) {
snackbarStore.success('听力师删除成功');
loadAudiologists(pagination.page, pagination.pageSize, searchParams);
} else {
snackbarStore.error(result.message || '听力师删除失败');
}
}).catch((error) => {
deleting.value = false;
snackbarStore.error('听力师删除失败');
console.error('删除听力师失败:', error);
}).finally(()=>{
selectedAudiologist.value = null;
});
};
// 初始加载
loadAudiologists();
return {
selectedAudiologist,
audiologists,
headers,
loading,
searchParams,
pagination,
searchAudiologists,
resetSearch,
refreshData,
handlePageChange,
handlePageSizeChange,
resetPassword,
confirmDelete,
deleteAudiologist,
deleting,
showDeleteDialog,
confirmResetPassword,
resetting,
showResetPasswordDialog,
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 问卷分类管理组件
const QuestionnaireCategoriesComponent = {
template: `
问卷分类管理
编辑
删除
暂无分类数据
{{ showEditDialog ? '编辑分类' : '添加分类' }}
取消
{{ saving ? '保存中...' : '保存' }}
确认删除
确定要删除分类 "{{ selectedCategory?.name }}" 吗?此操作不可撤销。
取消
{{ deleting ? '删除中...' : '删除' }}
`,
setup() {
const loading = ref(false);
const saving = ref(false);
const deleting = ref(false);
const showAddDialog = ref(false);
const showEditDialog = ref(false);
const showDeleteDialog = ref(false);
const categories = ref([]);
const selectedCategory = ref(null);
const newCategoryName = ref('');
// 表格列定义
const headers = [
{ title: 'ID', key: 'id', width: '280px' },
{ title: '分类名称', key: 'name' },
{ title: '创建时间', key: 'created_at', value: (row) => toTime(row.created_at) },
{ title: '操作', key: 'actions', width: '180px' }
];
// 加载分类列表
const loadCategories = async () => {
loading.value = true;
fetch('/admin/categories', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
})
.then(response => response.json())
.then(data => {
loading.value = false;
if (data.code === 0) {
categories.value = data.data || [];
} else {
snackbarStore.error(data.message || '获取分类列表失败');
}
}).catch(err => {
loading.value = false;
snackbarStore.error('网络错误,请稍后重试');
console.error('加载分类失败:', err);
});
};
// 保存分类(添加或编辑)
const saveCategory = async () => {
if (!newCategoryName.value?.trim()) {
snackbarStore.error('请输入分类名称');
return;
}
saving.value = true;
const isEdit = showEditDialog.value;
const url = isEdit ? `/admin/categories/${selectedCategory.value.id}` : '/admin/categories/create';
fetch(url, {
method: isEdit ? 'PUT' : 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
body: JSON.stringify({ name: newCategoryName.value.trim() })
})
.then(response => response.json())
.then(async (data) => {
saving.value = false;
if (data.code === 0) {
closeDialog();
await loadCategories();
snackbarStore.success(isEdit ? '分类信息更新成功' : '分类添加成功');
} else {
snackbarStore.error(data.message || '操作失败,请重试');
}
}).catch(err => {
saving.value = false;
snackbarStore.error('网络错误,请稍后重试');
console.error('保存分类失败:', err);
});
};
// 删除分类
const deleteCategory = async () => {
if (!selectedCategory.value) return;
deleting.value = true;
fetch(`/admin/categories/${selectedCategory.value.id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
})
.then(response => response.json())
.then(async (data) => {
deleting.value = false;
if (data.code === 0) {
closeDialog();
await loadCategories();
snackbarStore.success('分类删除成功');
} else {
snackbarStore.error(data.message || '删除失败,请重试');
}
}).catch(err => {
deleting.value = false;
snackbarStore.error('网络错误,请稍后重试');
console.error('删除分类失败:', err);
});
};
// 编辑分类
const editCategory = (category) => {
selectedCategory.value = category;
newCategoryName.value = category.name;
showEditDialog.value = true;
};
// 确认删除
const confirmDelete = (category) => {
selectedCategory.value = category;
showDeleteDialog.value = true;
};
// 关闭对话框
const closeDialog = () => {
showAddDialog.value = false;
showEditDialog.value = false;
showDeleteDialog.value = false;
newCategoryName.value = '';
selectedCategory.value = null;
};
// 初始加载数据
loadCategories();
return {
showAEDlg: computed(() => showAddDialog.value || showEditDialog.value),
loading,
saving,
deleting,
showAddDialog,
showEditDialog,
showDeleteDialog,
categories,
selectedCategory,
headers,
saveCategory,
deleteCategory,
editCategory,
confirmDelete,
closeDialog,
loadCategories,
newCategoryName
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
const DashboardComponent = {
template: `
仪表盘
{{ stat.title }}
{{ stat.value }}
{{ stat.description }}
`,
setup() {
const stats = reactive([
{ title: '用户总数', value: 0, description: '注册用户数量' },
{ title: '听力师总数', value: 0, description: '认证听力师数量' },
{ title: '问卷总数', value: 0, description: '已创建问卷数量' },
{ title: '答案总数', value: 0, description: '已提交答案数量' },
{ title: '分享总数', value: 0, description: '报告分享记录数量' }
]);
const loading = ref(false);
const loadDashboardData = async () => {
loading.value = true;
fetch('/admin/dashboard/stats', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
})
.then((response) => response.json())
.then((data) => {
loading.value = false;
if ( data.code === 0) {
stats[0].value = data.data.total_users || 0;
stats[1].value = data.data.total_audiologists || 0;
stats[2].value = data.data.total_questionnaires || 0;
stats[3].value = data.data.total_answers || 0;
stats[4].value = data.data.total_shares || 0;
} else if (data.code === 8003) {
store.logout();
} else {
snackbarStore.error(data.message || '加载仪表盘数据失败');
}
})
.catch((error) => {
loading.value = false;
snackbarStore.error('网络错误,请稍后重试');
console.error('加载仪表盘数据失败:', error);
});
};
// 页面加载时获取数据
loadDashboardData();
return {
stats,
loading,
store
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
const UsersComponent = {
template: `
用户管理
mdi-refresh刷新
重置密码
删除用户
确认删除
确定要删除用户 "{{ selectedUser?.nickname }}" 吗?此操作不可撤销。
取消
{{ deleting ? '删除中...' : '删除' }}
确认重置密码
确定要重置用户 "{{ selectedUser?.nickname }}" 的密码吗?
取消
{{ resetting ? '重置中...' : '重置密码' }}
`,
setup() {
const showDeleteDialog = ref(false);
const selectedUser = ref(null);
const deleting = ref(false);
const showResetPasswordDialog = ref(false);
const resetting = ref(false);
const users = ref([]);
const headers = ref([
{ title: 'ID', key: 'id', width: '280px' },
{ title: '手机号', key: 'phone' },
{ title: '昵称', key: 'nickname' },
{ title: '头像', key: 'avatar' },
{ title: '注册时间', key: 'created_at' },
{ title: '操作', key: 'actions', sortable: false }
]);
const loading = ref(false);
const searchParams = reactive({
keyword: ''
});
const pagination = reactive({
page: 1,
pageSize: 10,
total: 0
});
const loadUsers = async (page = 1, pageSize = 10, params = {}) => {
loading.value = true;
try {
const response = await fetch('/admin/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
body: JSON.stringify({
page,
pageSize,
...params
})
});
const result = await response.json();
if (result.code === 0) {
users.value = result.data || [];
pagination.total = result.total || 0;
} else {
snackbarStore.error(result.message || '加载用户数据失败');
}
} catch (error) {
console.error('加载用户列表失败:', error);
snackbarStore.error('网络错误,请重试');
} finally {
loading.value = false;
}
};
const searchUsers = () => {
pagination.page = 1;
loadUsers(pagination.page, pagination.pageSize, searchParams);
};
const resetSearch = () => {
searchParams.keyword = '';
pagination.page = 1;
loadUsers(pagination.page, pagination.pageSize, {});
};
const refreshData = () => {
loadUsers(pagination.page, pagination.pageSize, searchParams);
};
const handlePageChange = (newPage) => {
pagination.page = newPage;
loadUsers(pagination.page, pagination.pageSize, searchParams);
};
const handlePageSizeChange = (newSize) => {
pagination.pageSize = newSize;
pagination.page = 1;
loadUsers(pagination.page, pagination.pageSize, searchParams);
};
// 确认重置密码
const confirmResetPassword = (user) => {
selectedUser.value = user;
showResetPasswordDialog.value = true;
};
const resetPassword = async () => {
showResetPasswordDialog.value = false;
try {
loading.value = true;
const response = await fetch(`/admin/users/${selectedUser.value.id}/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
});
const result = await response.json();
if (result.code === 0) {
alert(`密码重置成功,请牢记新密码为:${result.data.password}`);
} else {
snackbarStore.error(result.message || '密码重置失败');
}
} catch (error) {
console.error('重置密码失败:', error);
snackbarStore.error('网络错误,请重试');
} finally {
loading.value = false;
selectedUser.value = null;
}
};
// 确认删除
const confirmDelete = (user) => {
selectedUser.value = user;
showDeleteDialog.value = true;
};
const deleteUser = async () => {
showDeleteDialog.value = false;
try {
deleting.value = true;
const response = await fetch(`/admin/users/${selectedUser.value.id}`, {
method: 'DELETE',
headers: {
'Authorization': `${store.token}`
}
});
const result = await response.json();
if (result.code === 0) {
snackbarStore.success('用户删除成功');
loadUsers(pagination.page, pagination.pageSize, searchParams);
} else {
snackbarStore.error(result.message || '用户删除失败');
}
} catch (error) {
console.error('删除用户失败:', error);
snackbarStore.error('网络错误,请重试');
} finally {
deleting.value = false;
selectedUser.value = null;
}
};
// 初始加载
loadUsers();
return {
users,
headers,
loading,
searchParams,
pagination,
searchUsers,
resetSearch,
refreshData,
handlePageChange,
handlePageSizeChange,
resetPassword,
confirmDelete,
deleteUser,
deleting,
showDeleteDialog,
confirmResetPassword,
resetPassword,
showResetPasswordDialog,
resetting,
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 管理员密码修改组件
const PasswordChangeComponent = {
template: `
管理员密码修改
确认修改
重置
`,
setup() {
const loading = ref(false);
const oldPassword = ref('');
const newPassword = ref('');
const confirmPassword = ref('');
const passwordForm = ref(null);
// 修改密码
const changePassword = async () => {
if (!passwordForm.value.validate()) {
return;
}
loading.value = true;
fetch('/admin/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
body: JSON.stringify({
old_password: md5(oldPassword.value),
new_password: md5(newPassword.value)
})
})
.then(response => response.json())
.then((res) => {
loading.value = false;
if (data.code === 0) {
resetForm();
snackbarStore.info('密码修改成功!');
} else {
snackbarStore.error(res.message || '密码修改失败,请重试');
}
})
.catch(err => {
loading.value = false;
console.error('修改密码失败:', err);
snackbarStore.error(err.message || '密码修改失败,请重试');
});
};
// 重置表单
const resetForm = () => {
oldPassword.value = '';
newPassword.value = '';
confirmPassword.value = '';
if (passwordForm.value) {
passwordForm.value.reset();
}
};
return {
store,
oldPassword,
newPassword,
confirmPassword,
passwordForm,
changePassword,
resetForm,
loading
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 问卷管理组件
const QuestionnaireComponent = {
template: `
问卷管理
编辑
删除
{{ editingQuestionnaire.id ? '编辑问卷' : '创建问卷' }}
mdi-close取消
{{ saving ? '保存中...' : '保存' }}
问卷分类
题型选择
mdi-radiobox-marked
单选题
mdi-checkbox-marked-outline
多选题
mdi-text-box-outline
文本输入
mdi-slide
滑动条
问卷介绍
问卷内容
维度管理
结果展示
主屏简介
副屏介绍
暂无问题,请点击左侧题型添加
{{ index + 1 }}. {{ questionTypeLabels[question.type] }}
选项
mdi-delete
添加选项
选项
mdi-delete
添加选项
滑动条设置
添加维度
注意:每个维度的管理在折叠面板中进行,点击维度名称可展开/折叠。
mdi-delete
添加分数段
mdi-delete删除
mdi-book-open-page-variant
暂无维度数据
添加分数段
mdi-delete删除
确认删除
确定要删除问卷 "{{ selectedQuestionnaire?.title }}" 吗?
取消
{{ deleting ? '删除中...' : '删除' }}
`,
setup() {
// 选项卡相关
const activeTab = ref("desc");
// 问卷列表相关
const questionnaires = ref([]);
const categories = ref([]);
const loading = ref(false);
const saving = ref(false);
const deleting = ref(false);
// 对话框状态
const showEditDialog = ref(false);
const showDeleteDialog = ref(false);
// 选中的问卷
const selectedQuestionnaire = ref(null);
// 题型标签映射
const questionTypeLabels = {
single: '单选题',
multiple: '多选题',
text: '文本输入',
slider: '滑动条'
};
// 编辑中的问卷 - 基于模型结构
const editingQuestionnaire = reactive({
id: '',
title: '',
description: '',
category_id: '',
category_name: '',
questions: [],
dimensions: [],
score_infos: [],
tips: '',
//副屏
fit_tips: '',
notice_tips: '',
reference_tips: '',
detail_tips: '',
scoring_mode: '',
});
// 计算属性:当前维度的分数段 - 已移除,直接使用dimension.score_infos
// 表格头部定义
const questionnaireHeaders = ref([
{ title: '问卷名称', key: 'title' },
{ title: '描述', key: 'description' },
{ title: '所属分类', key: 'category_name' },
{ title: '操作', key: 'actions', sortable: false }
]);
const dimensionHeaders = ref([
{ title: '维度名称', key: 'name', width: '60%' },
{ title: '操作', key: 'actions', sortable: false, width: '40%' }
]);
const scoreInfoHeaders = ref([
{ title: '最小值', key: 'min_score', width: '12%' },
{ title: '最大值', key: 'max_score', width: '12%' },
{ title: '提示颜色', key: 'color', width: '10%' },
{ title: '提示文字', key: 'prompt', width: '51%' },
{ title: '操作', key: 'actions', sortable: false, width: '15%' }
]);
const dimensionScoreHeaders = ref([
{ title: '最小值', key: 'min_score', width: '12%' },
{ title: '最大值', key: 'max_score', width: '12%' },
{ title: '提示颜色', key: 'color', width: '10%' },
{ title: '提示文字', key: 'prompt', width: '51%' },
{ title: '操作', key: 'actions', sortable: false, width: '15%' }
]);
// 加载分类列表
const loadCategories = async () => {
fetch('/admin/categories', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
}
})
.then((response) => response.json())
.then((data) => {
if (data.code === 0) {
categories.value = data.data || [];
} else {
snackbarStore.error(data.message || '获取分类列表失败');
}
})
.catch((error) => {
snackbarStore.error('网络错误,请稍后重试');
console.error('加载分类失败:', error);
});
};
// 加载问卷列表
const loadQuestionnaires = async () => {
loading.value = true;
// 先加载分类
loadCategories().then(() => {
// 加载问卷列表
fetch('/api/questionnaires', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
})
.then((response) => response.json())
.then((data) => {
loading.value = false;
if (data.code === 0) {
questionnaires.value = data.data || [];
} else {
snackbarStore.error(data.message || '获取问卷列表失败');
}
}).catch((error) => {
questionnaires.value = [];
loading.value = false;
snackbarStore.error('网络错误,请稍后重试');
console.error('加载问卷失败:', error);
});
});
};
// 刷新数据
const refreshData = () => {
loadQuestionnaires();
};
// 创建新问卷
const createNewQuestionnaire = () => {
// 重置编辑对象
Object.assign(editingQuestionnaire, {
id: '',
title: '',
description: '',
category_id: '',
category_name: '',
questions: [],
dimensions: [],
score_infos: [],
tips: '',
//副屏
fit_tips: '',
notice_tips: '',
reference_tips: '',
detail_tips: '',
scoring_mode: '',
});
activeTab.value = "content";
showEditDialog.value = true;
};
// 编辑问卷
const editQuestionnaire = async (questionnaireId) => {
loading.value = true;
// 加载问卷详情
fetch(`/admin/questionnaires/${questionnaireId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
})
.then((response) => response.json())
.then((data) => {
loading.value = false;
if (data.code === 0) {
//对比是否有所有字段,没有则添加默认值。数组字段默认值为空数组。
data.data.questions = data.data.questions || [];
data.data.dimensions = data.data.dimensions || [];
data.data.score_infos = data.data.score_infos || [];
// 为问题添加默认的反向计分字段
data.data.questions.forEach(q => {
if (q.reverse_scoring === undefined) {
q.reverse_scoring = false;
}
});
// 为维度添加默认的计分方式
data.data.dimensions.forEach(d => {
if (d.scoring_mode === undefined) {
d.scoring_mode = 'sum';
}
});
Object.assign(editingQuestionnaire, data.data || {});
activeTab.value = "content";
showEditDialog.value = true;
} else {
snackbarStore.error(data.message || '获取问卷详情失败');
showEditDialog.value = false;
}
}).catch((error) => {
loading.value = false;
console.error('加载问卷详情失败:', error);
snackbarStore.error('加载问卷详情失败,请重试');
showEditDialog.value = false;
}).finally(() => {
loading.value = false;
});
};
// 添加问题
const addQuestion = (type) => {
const newQuestion = {
id: `q${Date.now()}`,
title: '',
description: '',
type: type,
dimension_id: editingQuestionnaire.dimensions.length > 0 ? editingQuestionnaire.dimensions[0].id : '',
options: [],
reverse_scoring: false
};
// 为单选和多选题添加默认选项
if (type === 'single' || type === 'multiple') {
newQuestion.options.push(
{ value: '1', text: '选项1', score: 0 },
);
} else if (type === 'slider') {
newQuestion.min = 0;
newQuestion.max = 100;
newQuestion.step = 1;
}
editingQuestionnaire.questions.push(newQuestion);
// 自动切换到问卷内容选项卡
activeTab.value = "content";
//this.$forceUpdate();
};
// 添加选项
const addOption = (question) => {
const optionValue = (question.options.length + 1).toString();
question.options.push({
value: optionValue,
text: `选项${optionValue}`,
score: 0
});
};
// 删除选项
const deleteOption = (question, index) => {
if (question.options.length > 1) {
question.options.splice(index, 1);
// 重新编号选项
question.options.forEach((opt, idx) => {
opt.value = (idx + 1).toString();
});
} else {
snackbarStore.warning('至少保留一个选项');
}
};
// 删除问题
const deleteQuestion = (index) => {
if (confirm('确定要删除这个问题吗?')) {
editingQuestionnaire.questions.splice(index, 1);
snackbarStore.success('问题删除成功');
}
};
// 移动问题上移
const moveQuestionUp = (index) => {
if (index > 0) {
const temp = editingQuestionnaire.questions[index];
editingQuestionnaire.questions[index] = editingQuestionnaire.questions[index - 1];
editingQuestionnaire.questions[index - 1] = temp;
}
};
// 移动问题下移
const moveQuestionDown = (index) => {
if (index < editingQuestionnaire.questions.length - 1) {
const temp = editingQuestionnaire.questions[index];
editingQuestionnaire.questions[index] = editingQuestionnaire.questions[index + 1];
editingQuestionnaire.questions[index + 1] = temp;
}
};
// 添加维度
const addDimension = () => {
editingQuestionnaire.dimensions.push({
id: `d${Date.now()}`,
name: '',
score_infos: [],
scoring_mode: 'sum'
});
};
// 删除维度
const deleteDimension = (dimension) => {
if (confirm(`确定要删除维度 "${dimension.name}" 吗?`)) {
const index = editingQuestionnaire.dimensions.findIndex(d => d.id === dimension.id);
if (index > -1) {
// 更新使用该维度的问题
editingQuestionnaire.questions.forEach(question => {
if (question.dimension_id === dimension.id) {
question.dimension_id = editingQuestionnaire.dimensions.length > 1 ? editingQuestionnaire.dimensions[index === 0 ? 1 : 0].id : '';
}
});
editingQuestionnaire.dimensions.splice(index, 1);
snackbarStore.success('维度删除成功');
}
}
};
// 显示维度分数段管理对话框 - 已移除,功能已整合到v-expansion-panel中
// 添加维度分数段
const addDimensionScore = (dimension) => {
if (!dimension?.score_infos) {
dimension.score_infos = [];
}
let nextMinScore = 0;
let nextMaxScore = 20;
if (dimension.score_infos.length > 0) {
// 找到当前最大的 max_score,在其基础上+1
const maxMaxScore = Math.max(...dimension.score_infos.map(s => s.max_score || s.score || 0));
nextMinScore = maxMaxScore + 1;
nextMaxScore = nextMinScore + 20;
}
dimension.score_infos.push({
id: `ds${Date.now()}`,
min_score: nextMinScore,
max_score: nextMaxScore,
color: ['#4CAF50', '#8BC34A', '#FFC107', '#FF9800', '#FF5722'][dimension.score_infos.length % 5],
prompt: '请输入提示文字'
});
};
// 删除维度分数段
const deleteDimensionScore = (dimension, scoreInfo) => {
if (confirm(`确定要删除区间 [${scoreInfo.min_score || scoreInfo.score}-${scoreInfo.max_score || scoreInfo.score}] 的分数段吗?`)) {
const index = dimension.score_infos.findIndex(s => s.id === scoreInfo.id);
if (index > -1) {
dimension.score_infos.splice(index, 1);
snackbarStore.success('分数段删除成功');
}
}
};
// 添加问卷分数段
const addScoreInfo = () => {
let nextMinScore = 0;
let nextMaxScore = 20;
if (editingQuestionnaire.score_infos.length > 0) {
// 找到当前最大的 max_score,在其基础上+1
const maxMaxScore = Math.max(...editingQuestionnaire.score_infos.map(s => s.max_score || s.score || 0));
nextMinScore = maxMaxScore + 1;
nextMaxScore = nextMinScore + 20;
}
editingQuestionnaire.score_infos.push({
id: `si${Date.now()}`,
min_score: nextMinScore,
max_score: nextMaxScore,
color: ['#4CAF50', '#8BC34A', '#FFC107', '#FF9800', '#FF5722'][editingQuestionnaire.score_infos.length % 5],
prompt: '请输入提示文字'
});
};
// 删除问卷分数段
const deleteScoreInfo = (scoreInfo) => {
if (confirm(`确定要删除区间 [${scoreInfo.min_score || scoreInfo.score}-${scoreInfo.max_score || scoreInfo.score}] 的分数段吗?`)) {
const index = editingQuestionnaire.score_infos.findIndex(s => s.id === scoreInfo.id);
if (index > -1) {
editingQuestionnaire.score_infos.splice(index, 1);
snackbarStore.success('分数段删除成功');
}
}
};
// 保存问卷
const saveQuestionnaire = async () => {
// 验证表单
if (!editingQuestionnaire.title.trim()) {
snackbarStore.warning('请输入问卷标题');
return;
}
if (!editingQuestionnaire.category_id) {
snackbarStore.warning('请选择问卷分类');
return;
}
if (editingQuestionnaire.questions.length === 0) {
snackbarStore.warning('请至少添加一个问题');
return;
}
if (!editingQuestionnaire.tips.trim()) {
snackbarStore.warning('请输入问卷结果提示');
return;
}
// 验证副屏提示
if (!editingQuestionnaire.detail_tips.trim()) {
snackbarStore.warning('请输入副屏介绍');
return;
}
// 验证每个问题
for (let i = 0; i < editingQuestionnaire.questions.length; i++) {
const question = editingQuestionnaire.questions[i];
if (!question.title.trim()) {
snackbarStore.warning(`第 ${i + 1} 个问题的标题不能为空`);
return;
}
if (!question.dimension_id) {
snackbarStore.warning(`第 ${i + 1} 个问题请选择所属维度`);
return;
}
// 验证单选和多选题的选项
if ((question.type === 'single' || question.type === 'multiple') && question.options.length === 0) {
snackbarStore.warning(`第 ${i + 1} 个问题请至少添加一个选项`);
return;
}
// 验证滑动条设置
if (question.type === 'slider') {
if (question.min === undefined || question.max === undefined || question.step === undefined) {
snackbarStore.warning(`第 ${i + 1} 个问题请完善滑动条设置`);
return;
}
if (question.min >= question.max) {
snackbarStore.warning(`第 ${i + 1} 个问题的最大值必须大于最小值`);
return;
}
}
}
// 验证维度数据
if (editingQuestionnaire.dimensions.length === 0) {
snackbarStore.warning('请至少添加一个维度');
return;
}
for (let i = 0; i < editingQuestionnaire.dimensions.length; i++) {
const dimension = editingQuestionnaire.dimensions[i];
if (!dimension.name.trim()) {
snackbarStore.warning(`第 ${i + 1} 个维度的名称不能为空`);
return;
}
}
saving.value = true;
let rsp = null;
if(!editingQuestionnaire.id || editingQuestionnaire.id.trim().length < 1){
rsp = fetch(`/admin/questionnaires/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `${store.token}`
},
body: JSON.stringify(editingQuestionnaire),
});
}else{
rsp = fetch(`/admin/questionnaires/${editingQuestionnaire.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `${store.token}`
},
body: JSON.stringify(editingQuestionnaire),
});
console.log(editingQuestionnaire);
}
rsp.then((response) => response.json())
.then((data) => {
saving.value = false;
if (data.code === 0) {
if (editingQuestionnaire.id) {
// 编辑模式
snackbarStore.success('问卷更新成功');
} else {
// 创建模式
snackbarStore.success('问卷创建成功');
}
showEditDialog.value = false;
refreshData();
} else {
snackbarStore.error(data.message || '获取问卷详情失败');
//showEditDialog.value = false;
}
}).catch((error) => {
console.error('保存问卷失败:', error);
snackbarStore.error('保存问卷失败,请重试');
}).finally(() => {
saving.value = false;
})
};
// 确认删除问卷
const confirmDeleteQuestionnaire = (questionnaire) => {
selectedQuestionnaire.value = questionnaire;
showDeleteDialog.value = true;
};
// 删除问卷
const deleteQuestionnaire = async () => {
deleting.value = true;
showDeleteDialog.value = false;
fetch(`/admin/questionnaires/${selectedQuestionnaire.value.id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `${store.token}`
},
}).then((response) => response.json())
.then((data) => {
deleting.value = false;
if (data.code === 0) {
snackbarStore.success('问卷删除成功');
showDeleteDialog.value = false;
refreshData();
} else {
snackbarStore.error(data.message || '删除问卷失败');
}
}).catch((error) => {
deleting.value = false;
console.error('删除问卷失败:', error);
snackbarStore.error('删除问卷失败,请重试');
}).finally(() => {
deleting.value = false;
})
};
// 初始加载
loadQuestionnaires();
return {
questionnaires,
categories,
loading,
saving,
deleting,
showEditDialog,
showDeleteDialog,
selectedQuestionnaire,
editingQuestionnaire,
activeTab,
questionTypeLabels,
questionnaireHeaders,
dimensionHeaders,
scoreInfoHeaders,
dimensionScoreHeaders,
refreshData,
editQuestionnaire,
saveQuestionnaire,
confirmDeleteQuestionnaire,
deleteQuestionnaire,
addDimension,
deleteDimension,
addDimensionScore,
deleteDimensionScore,
addScoreInfo,
deleteScoreInfo,
addQuestion,
addOption,
deleteOption,
deleteQuestion,
moveQuestionUp,
moveQuestionDown,
createNewQuestionnaire,
loadQuestionnaires
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
//报告列表管理
const AnswersComponent = {
template: `
报告管理
{{ toTime(item.create_time) }}
查看
删除
mdi-close
报告详情 - {{ selectedAnswer?.questionnaire_name }}
{{ item.question }}
{{ formatAnswer(item.answer, item.type) }}
确认删除
确定要删除报告 "{{ selectedAnswer?.title }}" 用户:{{ selectedAnswer?.nick_name}} 吗?此操作不可撤销。
取消
{{ deleting ? '删除中...' : '删除' }}
导出报告
取消
{{ exporting ? '导出中...' : '导出' }}
`,
setup() {
const loading = ref(false);
const deleting = ref(false);
const exporting = ref(false);
const answers = ref([]);
const questionnaires = ref([]);
const selectedAnswer = ref(null);
const selectedAnswerDetails = ref([]);
const showDetailDialog = ref(false);
const showDeleteDialog = ref(false);
const showExportDialog = ref(false);
const startDate = ref('');
const endDate = ref('');
const selectedQuestionnaireIds = ref([]);
const pagination = reactive({
page: 1,
pageSize: 10,
total: 0
});
const headers = [
//{ title: '报告ID', key: 'id', width: '100px' },
{ title: '问卷名称', key: 'title' },
{ title: '用户', key: 'nick_name' },
{ title: '问卷分类', key: 'category_name' },
{ title: '提交时间', key: 'created_at',value: (row) => toTime(row.created_at) },
{ title: '操作', key: 'actions', width: '180px' }
];
const detailHeaders = [
{ title: '题目', key: 'title' },
{ title: '维度', key: 'dimension_name' },
{ title: '得分', key: 'score' },
{ title: '回答', key: 'value' }
];
// 加载问卷列表
const loadQuestionnaires = async () => {
fetch('/api/questionnaires', {
method: 'post',
headers: {
'Authorization': `${store.token}`,
'Content-Type': 'application/json'
}
}).then((response) => response.json())
.then((data) => {
if (data.code === 0) {
questionnaires.value = data.data;
} else {
snackbarStore.error('加载问卷列表失败:' + data.msg);
}
}).catch((error) => {
snackbarStore.error('加载问卷列表失败:' + error.message);
});
};
// 加载报告列表
const loadAnswers = async () => {
loading.value = true;
fetch("/admin/reports", {
method: 'post',
headers: {
'Authorization': `${store.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
page: pagination.page,
pageSize: pagination.pageSize,
})
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
answers.value = data.data || [];
pagination.total = data.total;
} else {
snackbarStore.error('加载报告列表失败:' + data.message);
}
loading.value = false;
})
.catch(error => {
loading.value = false;
snackbarStore.error('加载报告列表失败:' + error.message);
})
.finally(() => {
loading.value = false;
});
};
// 刷新数据
const refreshData = () => {
pagination.page = 1;
loadAnswers();
};
// 重置搜索
const resetSearch = () => {
startDate.value = '';
endDate.value = '';
pagination.page = 1;
loadAnswers();
};
// 处理页码变化
const handlePageChange = (page) => {
pagination.page = page;
loadAnswers();
};
// 处理每页条数变化
const handlePageSizeChange = (pageSize) => {
pagination.pageSize = pageSize;
pagination.page = 1;
loadAnswers();
};
// 查看报告详情
const viewAnswer = async (answerId) => {
loading.value = true;
fetch(`/admin/reports/${answerId}`, {
method: 'POST',
headers: {
'Authorization': `${store.token}`,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log(data);
if (data.code === 0) {
selectedAnswerDetails.value = data.data;
showDetailDialog.value = true;
} else {
snackbarStore.error('加载报告详情失败:' + data.message);
}
loading.value = false;
}).catch(error => {
loading.value = false;
snackbarStore.error('加载报告详情失败:' + error.message);
});
};
// 确认删除
const confirmDelete = (answer) => {
selectedAnswer.value = answer;
showDeleteDialog.value = true;
};
// 删除报告
const deleteAnswer = async () => {
if (!selectedAnswer.value) return;
deleting.value = true;
fetch(`/admin/reports/${selectedAnswer.value.id}`, {
method: 'DELETE',
headers: {
'Authorization': `${store.token}`,
'Content-Type': 'application/json'
}
})
.then((response) => response.json())
.then((data) => {
deleting.value = false;
if (data.code === 0) {
snackbarStore.success('删除成功');
showDeleteDialog.value = false;
loadAnswers();
} else {
snackbarStore.error('删除失败:' + data.msg);
}
}).catch((error) => {
deleting.value = false;
snackbarStore.error('删除失败:' + error.message);
}).finally(() => {
deleting.value = false;
});
};
// 导出报告
const exportAnswers = async () => {
if (selectedQuestionnaireIds.value.length === 0) {
snackbarStore.warning('请至少选择一个报告题目');
return;
}
exporting.value = true;
fetch(`/admin/reports/export`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `${store.token}`
},
body: JSON.stringify({
questionnaireIds: selectedQuestionnaireIds.value,
startDate:startDate.value,
endDate: endDate.value,
})
}).then(async (response) => {
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `报告数据_${new Date().getTime()}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
snackbarStore.success('导出成功');
showExportDialog.value = false;
} else {
snackbarStore.error('导出失败');
}
}).catch((error) => {
snackbarStore.error('导出失败:' + error.message);
}).finally(() => {
exporting.value = false;
});
};
// 初始化
onMounted(async () => {
await loadQuestionnaires();
await loadAnswers();
});
return {
loading,
deleting,
exporting,
answers,
startDate,
endDate,
questionnaires,
selectedAnswer,
selectedAnswerDetails,
showDetailDialog,
showDeleteDialog,
showExportDialog,
selectedQuestionnaireIds,
pagination,
headers,
detailHeaders,
refreshData,
resetSearch,
handlePageChange,
handlePageSizeChange,
viewAnswer,
confirmDelete,
deleteAnswer,
exportAnswers
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
//分享管理
const SharesComponent = {
template: `
报告分类管理
{{ toTime(item.create_time) }}
删除
确认删除
确定要删除分享记录 "{{ selectedShare?.questionnaire_name }}" 吗?此操作不可撤销。
取消
{{ deleting ? '删除中...' : '删除' }}
`,
setup() {
const loading = ref(false);
const deleting = ref(false);
const shares = ref([]);
const questionnaires = ref([]);
const selectedShare = ref(null);
const showDeleteDialog = ref(false);
const keyword = ref('');
const pagination = reactive({
page: 1,
pageSize: 10,
total: 0
});
const headers = [
{ title: '问卷名称', key: 'title' },
{ title: '分享人', key: 'user_nickname' },
{ title: '听力师', key: 'audiologist_nickname' },
{ title: '分享时间', key: 'shared_at' },
{ title: '得分', key: 'score' },
{ title: '总分', key: 'max_score' },
{ title: '操作', key: 'actions', width: '100px' }
];
// 加载分享列表
const loadShares = async () => {
loading.value = true;
const params = {
page: pagination.page,
pageSize: pagination.pageSize,
keyword: keyword.value,
};
fetch(`/admin/shares`, {
method: 'POST',
body: JSON.stringify(params),
headers: {
'Authorization': `${store.token}`,
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
loading.value = false;
if (data.code === 0) {
shares.value = data.data || [];
pagination.total = data.total || 0;
} else {
snackbarStore.error('加载分享列表失败:' + data.msg);
}
})
.catch(error => {
loading.value = false;
snackbarStore.error('加载分享列表失败:' + error.message);
});
};
// 刷新数据
const refreshData = () => {
pagination.page = 1;
loadShares();
};
// 搜索分享
const searchShares = () => {
pagination.page = 1;
loadShares();
};
// 重置搜索
const resetSearch = () => {
keyword.value = '';
pagination.page = 1;
loadShares();
};
// 处理页码变化
const handlePageChange = (page) => {
pagination.page = page;
loadShares();
};
// 处理每页条数变化
const handlePageSizeChange = (pageSize) => {
pagination.pageSize = pageSize;
pagination.page = 1;
loadShares();
};
// 确认删除
const confirmDelete = (share) => {
selectedShare.value = share;
showDeleteDialog.value = true;
};
// 删除分享
const deleteShare = async () => {
if (!selectedShare.value) return;
deleting.value = true;
console.log(selectedShare.value);
fetch(`/admin/shares/${selectedShare.value.id}`, {
method: 'DELETE',
headers: {
'Authorization': `${store.token}`,
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
console.log(data);
deleting.value = false;
showDeleteDialog.value = false;
if (data.code === 0) {
snackbarStore.success('删除成功');
showDeleteDialog.value = false;
loadShares();
} else {
snackbarStore.error('删除失败:' + data.message);
}
})
.catch(error => {
showDeleteDialog.value = false;
selectedShare.value = null;
deleting.value = false;
snackbarStore.error('删除失败:' + error.message);
})
};
// 初始化
onMounted(async () => {
await loadShares();
});
return {
loading,
deleting,
shares,
questionnaires,
selectedShare,
showDeleteDialog,
keyword,
pagination,
headers,
refreshData,
searchShares,
resetSearch,
handlePageChange,
handlePageSizeChange,
confirmDelete,
deleteShare
};
}
};
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 创建路由
const router = createRouter({
history: createWebHistory('/'),
routes: [
{
path: '/login',
component: LoginComponent,
meta: { requiresAuth: false }
},
{
path: '/',
component: DashboardComponent,
meta: { requiresAuth: true }
},
{ path: '/users', component: UsersComponent, meta: { requiresAuth: true } },
// 添加更多路由
{
path: '/questionnaire-categories',
component: QuestionnaireCategoriesComponent,
meta: { requiresAuth: true }
},
{ path: '/questionnaires', component: QuestionnaireComponent, meta: { requiresAuth: true } },
{
path: '/audiologists',
component: AudiologistsComponent,
meta: { requiresAuth: true }
},
{
path: '/answers',
component: AnswersComponent,
meta: { requiresAuth: true }
},
{
path: '/shares',
component: SharesComponent,
meta: { requiresAuth: true }
},
{
path: '/settings',
component: PasswordChangeComponent,
meta: { requiresAuth: true }
},
{
path: '/:pathMatch(.*)*',
redirect: '/' // 重定向到首页
}
]
});
// 路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.isLoggedIn) {
// 如果需要认证但未登录,重定向到登录页
next('/login');
} else {
next();
}
});
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
// 创建应用实例
const app = createApp({
template: `
`,
setup() {
// 检查登录状态
const checkLoginStatus = () => {
if (store.checkLogin()) {
store.isLoggedIn = true;
}
};
// 侧边栏导航项
const navItems = [
{ title: '首页', icon: 'mdi-home', path: '/' },
{
title: '问卷分类管理',
icon: 'mdi-google-circles-extended',
path: '/questionnaire-categories'
},
{
title: '问卷管理',
icon: 'mdi-file-document',
path: '/questionnaires'
},
{
title: '用户管理',
icon: 'mdi-account-group',
path: '/users'
},
{
title: '听力师管理',
icon: 'mdi-stethoscope',
path: '/audiologists'
},
{
title: '答卷列表',
icon: 'mdi-chart-pie',
path: '/answers'
},
{
title: '分享记录',
icon: 'mdi-share-variant',
path: '/shares'
},
{
title: '管理员设置',
icon: 'mdi-cog',
path: '/settings'
},
{ title: '退出登录', icon: 'mdi-logout', action: () => store.logout() }
];
// 侧边栏分组状态
const activeGroup = ref([]);
// 切换分组展开/收起状态
const toggleGroup = (groupTitle) => {
const index = activeGroup.value.indexOf(groupTitle);
if (index > -1) {
activeGroup.value.splice(index, 1);
} else {
activeGroup.value.push(groupTitle);
}
};
// 检查分组是否展开
const isGroupOpen = (groupTitle) => {
return activeGroup.value.includes(groupTitle);
};
// 初始化时检查登录状态
checkLoginStatus();
return {
snackbarStore,
isLoggedIn: computed(() => store.isLoggedIn),
username: computed(() => store.userInfo.username || store.userInfo.nickname),
navItems,
activeGroup,
toggleGroup,
isGroupOpen
};
}
});
// 全局可用的通知函数
//app.config.globalProperties.$notify = showNotification;
// 注册全局组件
app.component('ColorPickerButton', ColorPickerButton);
app.component('DatePickerDialog', DatePickerDialog);
// 使用插件
app.use(vuetify);
app.use(router);
// 挂载应用
app.mount('#app');
// 添加淡入淡出过渡样式和ColorPickerButton组件样式
const style = document.createElement('style');
style.textContent = `
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
`;
document.head.appendChild(style);