|
- {% extends "./layout.html" %}
-
- {% block title %}医院管理{% endblock %}
-
- {% block css %}
- <style>
- .drag-handle { cursor: move; color: #999; font-size: 18px; }
- .drag-handle:hover { color: #ff7800; }
- .sortable-ghost { opacity: 0.4; background: #fff7ed; }
- </style>
- {% endblock %}
-
- {% block content %}
- <div id="hospitalApp" v-cloak>
- <el-card shadow="never">
- <template #header>
- <div class="flex flex-col gap-3">
- <div class="flex items-center justify-between">
- <div class="flex items-center gap-3">
- <el-input v-model="keyword" placeholder="搜索医院名称" style="width:240px;" clearable @keyup.enter="onSearch"></el-input>
- <el-button type="primary" @click="onSearch">搜索</el-button>
- <el-button @click="resetQuery">重置</el-button>
- </div>
- <span class="text-xs text-gray-400">拖拽行可调整排序</span>
- </div>
- <div class="flex items-center gap-3">
- <el-button type="primary" :icon="Plus" @click="showAdd">新增医院</el-button>
- <el-button :icon="Upload" @click="showImport = true">导入医院</el-button>
- </div>
- </div>
- </template>
-
- <el-table :data="list" v-loading="loading" stripe border row-key="id">
- <el-table-column width="60" align="center">
- <template #header>排序</template>
- <template #default>
- <span class="drag-handle">⠿</span>
- </template>
- </el-table-column>
- <el-table-column prop="name" label="医院名称"></el-table-column>
- <el-table-column label="是否展示" width="120" align="center">
- <template #default="{ row }">
- <el-switch :model-value="row.is_show === 1" @change="(v) => handleToggle(row, v)" />
- </template>
- </el-table-column>
- <el-table-column label="操作" width="180" align="center">
- <template #default="{ row }">
- <el-button type="primary" link @click="showEdit(row)">编辑</el-button>
- <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
-
- <div class="flex justify-end mt-4">
- <el-pagination background layout="total, sizes, prev, pager, next" :total="total" :page-sizes="[10, 20, 50, 100]"
- v-model:page-size="pageSize" v-model:current-page="page" @current-change="loadList" @size-change="onSizeChange"></el-pagination>
- </div>
- </el-card>
-
- <!-- 新增/编辑弹窗 -->
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="460px" destroy-on-close draggable :close-on-click-modal="false">
- <el-form :model="form" label-width="90px">
- <el-form-item label="医院名称" required>
- <el-input v-model="form.name" placeholder="请输入医院名称" maxlength="100" />
- </el-form-item>
- <el-form-item label="排序">
- <el-input-number v-model="form.sort" :min="0" :max="9999" />
- </el-form-item>
- <el-form-item label="是否展示">
- <el-switch v-model="form.is_show" :active-value="1" :inactive-value="0" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="handleSave" :loading="saving">确定</el-button>
- </template>
- </el-dialog>
-
- <!-- 批量导入弹窗 -->
- <el-dialog v-model="showImport" title="批量导入医院" width="500px" destroy-on-close :close-on-click-modal="false">
- <p style="font-size:13px;color:var(--el-color-primary);margin-bottom:12px;">每行输入一个医院名称,重复的将自动跳过。</p>
- <el-input v-model="importText" type="textarea" :rows="10" :placeholder="importPlaceholder"></el-input>
- <template #footer>
- <div style="text-align:right;">
- <el-button @click="showImport = false">取消</el-button>
- <el-button type="primary" @click="handleImport" :loading="importing">确认导入</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- {% endblock %}
-
- {% block js %}
- <script src="/static/lib/sortablejs/Sortable.min.js"></script>
- <script>
- const { createApp, ref, reactive, onMounted, nextTick } = Vue;
- const { Plus, Upload } = ElementPlusIconsVue;
-
- const app = createApp({
- delimiters: ['${', '}'],
- setup() {
- const loading = ref(false);
- const list = ref([]);
- const keyword = ref('');
- const total = ref(0);
- const page = ref(1);
- const pageSize = ref(10);
- const dialogVisible = ref(false);
- const dialogTitle = ref('新增医院');
- const saving = ref(false);
- const form = reactive({ id: null, name: '', sort: 0, is_show: 1 });
- const showImport = ref(false);
- const importText = ref('');
- const importing = ref(false);
- const importPlaceholder = '北京协和医院\n复旦大学附属肿瘤医院\n中山大学肿瘤防治中心';
- let sortableInstance = null;
-
- async function loadList() {
- loading.value = true;
- try {
- const params = new URLSearchParams({
- keyword: keyword.value, page: page.value, pageSize: pageSize.value
- });
- const res = await fetch('/admin/hospital/list?' + params).then(r => r.json());
- if (res.code === 0) {
- list.value = res.data.data || [];
- total.value = res.data.count || 0;
- nextTick(() => initSortable());
- }
- } finally { loading.value = false; }
- }
-
- function initSortable() {
- if (sortableInstance) { sortableInstance.destroy(); sortableInstance = null; }
- const tbody = document.querySelector('#hospitalApp .el-table__body-wrapper tbody');
- if (!tbody) return;
- sortableInstance = Sortable.create(tbody, {
- handle: '.drag-handle',
- animation: 150,
- ghostClass: 'sortable-ghost',
- onEnd(evt) {
- if (evt.oldIndex === evt.newIndex) return;
- const arr = list.value.slice();
- const item = arr.splice(evt.oldIndex, 1)[0];
- arr.splice(evt.newIndex, 0, item);
- list.value = arr;
- fetch('/admin/hospital/sort', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ ids: arr.map(i => i.id) })
- }).then(r => r.json()).then(res => {
- if (res.code === 0) ElementPlus.ElMessage.success('排序已保存');
- else loadList();
- });
- }
- });
- }
-
- function showAdd() {
- dialogTitle.value = '新增医院';
- form.id = null; form.name = ''; form.sort = 0; form.is_show = 1;
- dialogVisible.value = true;
- }
-
- function onSearch() {
- page.value = 1;
- loadList();
- }
-
- function resetQuery() {
- keyword.value = '';
- page.value = 1;
- loadList();
- }
-
- function onSizeChange() {
- page.value = 1;
- loadList();
- }
- function showEdit(row) {
- dialogTitle.value = '编辑医院';
- form.id = row.id; form.name = row.name; form.sort = row.sort; form.is_show = row.is_show;
- dialogVisible.value = true;
- }
-
- async function handleSave() {
- if (!form.name.trim()) { ElementPlus.ElMessage.warning('请输入医院名称'); return; }
- saving.value = true;
- try {
- const url = form.id ? '/admin/hospital/edit' : '/admin/hospital/add';
- const res = await fetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(form)
- }).then(r => r.json());
- if (res.code === 0) {
- ElementPlus.ElMessage.success('保存成功');
- dialogVisible.value = false;
- loadList();
- } else {
- ElementPlus.ElMessage.error(res.msg || '保存失败');
- }
- } finally { saving.value = false; }
- }
-
- async function handleDelete(row) {
- try {
- await ElementPlus.ElMessageBox.confirm(`确定要删除「${row.name}」吗?`, '提示', { type: 'warning' });
- const res = await fetch('/admin/hospital/delete', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: row.id })
- }).then(r => r.json());
- if (res.code === 0) { ElementPlus.ElMessage.success('删除成功'); loadList(); }
- else { ElementPlus.ElMessage.error(res.msg || '删除失败'); }
- } catch (e) {}
- }
-
- async function handleToggle(row, val) {
- const res = await fetch('/admin/hospital/toggleShow', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: row.id, is_show: val ? 1 : 0 })
- }).then(r => r.json());
- if (res.code === 0) { row.is_show = val ? 1 : 0; ElementPlus.ElMessage.success('已更新'); }
- else { ElementPlus.ElMessage.error(res.msg || '操作失败'); }
- }
-
- async function handleImport() {
- if (!importText.value.trim()) { ElementPlus.ElMessage.warning('请输入医院名称'); return; }
- importing.value = true;
- try {
- const res = await fetch('/admin/hospital/import', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ text: importText.value })
- }).then(r => r.json());
- if (res.code === 0) {
- ElementPlus.ElMessage.success(`导入成功 ${res.data.count} 条,跳过 ${res.data.skipped} 条`);
- showImport.value = false;
- importText.value = '';
- loadList();
- } else {
- ElementPlus.ElMessage.error(res.msg || '导入失败');
- }
- } finally { importing.value = false; }
- }
-
- onMounted(() => loadList());
-
- return { loading, list, keyword, total, page, pageSize, dialogVisible, dialogTitle, saving, form, showImport, importText, importing, importPlaceholder, loadList, onSearch, resetQuery, onSizeChange, showAdd, showEdit, handleSave, handleDelete, handleToggle, handleImport, Plus, Upload };
- }
- });
-
- app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
- app.mount('#hospitalApp');
- </script>
- {% endblock %}
|