Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

307 rader
10 KiB

  1. {% extends "../layout.html" %}
  2. {% block title %}药品援助记录{% endblock %}
  3. {% block content %}
  4. <div id="medicineApp">
  5. <!-- 搜索栏 -->
  6. <el-card shadow="never" class="mb-4">
  7. <el-form :inline="true" @submit.prevent="loadList()" class="flex items-center flex-wrap gap-2">
  8. <el-form-item label="关键词" class="!mb-0">
  9. <el-input v-model="keyword" placeholder="药品名称/受助人..." clearable style="width:180px;" />
  10. </el-form-item>
  11. <el-form-item label="年度" class="!mb-0">
  12. <el-select v-model="yearFilter" placeholder="全部" clearable style="width:100px;">
  13. <el-option v-for="y in years" :key="y" :label="y" :value="y"></el-option>
  14. </el-select>
  15. </el-form-item>
  16. <el-form-item label="状态" class="!mb-0">
  17. <el-select v-model="statusFilter" placeholder="全部" clearable style="width:110px;">
  18. <el-option label="已发放" :value="1"></el-option>
  19. <el-option label="待发放" :value="2"></el-option>
  20. <el-option label="已取消" :value="3"></el-option>
  21. </el-select>
  22. </el-form-item>
  23. <el-form-item class="!mb-0">
  24. <el-button type="primary" @click="loadList()">搜索</el-button>
  25. <el-button @click="resetFilter">重置</el-button>
  26. </el-form-item>
  27. </el-form>
  28. </el-card>
  29. <!-- 列表 -->
  30. <el-card shadow="never">
  31. <template #header>
  32. <el-button type="primary" @click="showAddModal">+ 新增记录</el-button>
  33. </template>
  34. <el-table :data="tableData" v-loading="loading" stripe border>
  35. <el-table-column prop="name" label="药品名称" min-width="200" ></el-table-column>
  36. <el-table-column prop="person" label="受助人" width="100" ></el-table-column>
  37. <el-table-column prop="region" label="地区" width="120">
  38. <template #default="{ row }">${ row.region || '—' }</template>
  39. </el-table-column>
  40. <el-table-column prop="quantity" label="数量" width="80" ></el-table-column>
  41. <el-table-column prop="amount" label="金额 (元)" width="120" align="right">
  42. <template #default="{ row }">¥ ${ formatMoney(row.amount) }</template>
  43. </el-table-column>
  44. <el-table-column prop="status" label="状态" width="100" align="center">
  45. <template #default="{ row }">
  46. <el-tag v-if="row.status === 1" type="success" size="small">已发放</el-tag>
  47. <el-tag v-else-if="row.status === 2" type="warning" size="small">待发放</el-tag>
  48. <el-tag v-else type="info" size="small">已取消</el-tag>
  49. </template>
  50. </el-table-column>
  51. <el-table-column prop="distribute_date" label="发放日期" width="120">
  52. <template #default="{ row }">${ row.distribute_date || '—' }</template>
  53. </el-table-column>
  54. <el-table-column label="操作" width="140" align="center">
  55. <template #default="{ row }">
  56. <el-button type="primary" link @click="editItem(row)">编辑</el-button>
  57. <el-button type="danger" link @click="deleteItem(row)">删除</el-button>
  58. </template>
  59. </el-table-column>
  60. </el-table>
  61. <div class="flex justify-end mt-4">
  62. <el-pagination
  63. v-model:current-page="pagination.page"
  64. :page-size="pagination.pageSize"
  65. :total="pagination.total"
  66. layout="total, prev, pager, next"
  67. @current-change="loadList"
  68. />
  69. </div>
  70. </el-card>
  71. <!-- 新增/编辑弹窗 -->
  72. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px" destroy-on-close>
  73. <el-form :model="form" label-width="100px">
  74. <el-form-item label="药品名称" required>
  75. <el-input v-model="form.name" placeholder="请输入药品名称" />
  76. </el-form-item>
  77. <el-row :gutter="16">
  78. <el-col :span="12">
  79. <el-form-item label="受助人" required>
  80. <el-input v-model="form.person" placeholder="如:张*三" />
  81. </el-form-item>
  82. </el-col>
  83. <el-col :span="12">
  84. <el-form-item label="地区">
  85. <el-input v-model="form.region" placeholder="如:北京" />
  86. </el-form-item>
  87. </el-col>
  88. </el-row>
  89. <el-row :gutter="16">
  90. <el-col :span="12">
  91. <el-form-item label="数量" required>
  92. <el-input v-model="form.quantity" placeholder="如:1盒" />
  93. </el-form-item>
  94. </el-col>
  95. <el-col :span="12">
  96. <el-form-item label="金额 (元)" required>
  97. <el-input-number v-model="form.amount" :min="0" :precision="2" style="width:100%;" />
  98. </el-form-item>
  99. </el-col>
  100. </el-row>
  101. <el-row :gutter="16">
  102. <el-col :span="12">
  103. <el-form-item label="状态">
  104. <el-select v-model="form.status" style="width:100%;" :teleported="false">
  105. <el-option label="已发放" :value="1"></el-option>
  106. <el-option label="待发放" :value="2"></el-option>
  107. <el-option label="已取消" :value="3"></el-option>
  108. </el-select>
  109. </el-form-item>
  110. </el-col>
  111. <el-col :span="12">
  112. <el-form-item label="发放日期">
  113. <el-date-picker v-model="form.distribute_date" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width:100%;" />
  114. </el-form-item>
  115. </el-col>
  116. </el-row>
  117. <el-form-item label="备注">
  118. <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
  119. </el-form-item>
  120. </el-form>
  121. <template #footer>
  122. <el-button @click="dialogVisible = false">取消</el-button>
  123. <el-button type="primary" @click="saveItem" :loading="saving">保存</el-button>
  124. </template>
  125. </el-dialog>
  126. </div>
  127. {% endblock %}
  128. {% block js %}
  129. <script>
  130. const yearsData = {{ years | dump | safe }};
  131. const { createApp, ref, reactive, onMounted } = Vue;
  132. const app = createApp({
  133. delimiters: ['${', '}'],
  134. setup() {
  135. const keyword = ref('');
  136. const yearFilter = ref('');
  137. const statusFilter = ref('');
  138. const loading = ref(false);
  139. const tableData = ref([]);
  140. const pagination = reactive({ page: 1, pageSize: 10, total: 0 });
  141. const years = ref(yearsData || []);
  142. // 弹窗
  143. const dialogVisible = ref(false);
  144. const dialogTitle = ref('新增记录');
  145. const saving = ref(false);
  146. const form = reactive({
  147. id: null,
  148. name: '',
  149. person: '',
  150. region: '',
  151. quantity: '1盒',
  152. amount: 0,
  153. status: 2,
  154. distribute_date: '',
  155. remark: ''
  156. });
  157. // 格式化金额
  158. function formatMoney(n) {
  159. return Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2 });
  160. }
  161. // 加载列表
  162. async function loadList(page) {
  163. if (typeof page === 'number') pagination.page = page;
  164. loading.value = true;
  165. try {
  166. const params = new URLSearchParams({
  167. page: pagination.page,
  168. pageSize: pagination.pageSize,
  169. keyword: keyword.value,
  170. year: yearFilter.value,
  171. status: statusFilter.value
  172. });
  173. const res = await fetch('/admin/data/medicine/list?' + params).then(r => r.json());
  174. if (res.code === 0) {
  175. tableData.value = res.data.data || [];
  176. pagination.total = res.data.count || 0;
  177. } else {
  178. ElementPlus.ElMessage.error(res.msg || '加载失败');
  179. }
  180. } finally {
  181. loading.value = false;
  182. }
  183. }
  184. // 重置筛选
  185. function resetFilter() {
  186. keyword.value = '';
  187. yearFilter.value = '';
  188. statusFilter.value = '';
  189. pagination.page = 1;
  190. loadList();
  191. }
  192. // 显示新增弹窗
  193. function showAddModal() {
  194. dialogTitle.value = '新增记录';
  195. Object.assign(form, {
  196. id: null,
  197. name: '',
  198. person: '',
  199. region: '',
  200. quantity: '1盒',
  201. amount: 0,
  202. status: 2,
  203. distribute_date: '',
  204. remark: ''
  205. });
  206. dialogVisible.value = true;
  207. }
  208. // 编辑
  209. function editItem(row) {
  210. dialogTitle.value = '编辑记录';
  211. Object.assign(form, {
  212. id: row.id,
  213. name: row.name,
  214. person: row.person,
  215. region: row.region || '',
  216. quantity: row.quantity || '1盒',
  217. amount: row.amount || 0,
  218. status: row.status || 2,
  219. distribute_date: row.distribute_date || '',
  220. remark: row.remark || ''
  221. });
  222. dialogVisible.value = true;
  223. }
  224. // 保存
  225. async function saveItem() {
  226. if (!form.name.trim()) {
  227. ElementPlus.ElMessage.warning('请输入药品名称');
  228. return;
  229. }
  230. if (!form.person.trim()) {
  231. ElementPlus.ElMessage.warning('请输入受助人');
  232. return;
  233. }
  234. saving.value = true;
  235. try {
  236. const url = form.id ? '/admin/data/medicine/edit' : '/admin/data/medicine/add';
  237. const body = { ...form };
  238. if (!form.id) delete body.id;
  239. const res = await fetch(url, {
  240. method: 'POST',
  241. headers: { 'Content-Type': 'application/json' },
  242. body: JSON.stringify(body)
  243. }).then(r => r.json());
  244. if (res.code === 0) {
  245. ElementPlus.ElMessage.success('保存成功');
  246. dialogVisible.value = false;
  247. loadList();
  248. } else {
  249. ElementPlus.ElMessage.error(res.msg || '保存失败');
  250. }
  251. } finally {
  252. saving.value = false;
  253. }
  254. }
  255. // 删除
  256. async function deleteItem(row) {
  257. try {
  258. await ElementPlus.ElMessageBox.confirm('确定要删除该记录吗?', '提示', { type: 'warning' });
  259. const res = await fetch('/admin/data/medicine/delete', {
  260. method: 'POST',
  261. headers: { 'Content-Type': 'application/json' },
  262. body: JSON.stringify({ id: row.id })
  263. }).then(r => r.json());
  264. if (res.code === 0) {
  265. ElementPlus.ElMessage.success('删除成功');
  266. loadList();
  267. } else {
  268. ElementPlus.ElMessage.error(res.msg || '删除失败');
  269. }
  270. } catch {}
  271. }
  272. onMounted(() => loadList());
  273. return {
  274. keyword, yearFilter, statusFilter, loading, tableData, pagination, years,
  275. dialogVisible, dialogTitle, saving, form,
  276. formatMoney, loadList, resetFilter, showAddModal, editItem, saveItem, deleteItem
  277. };
  278. }
  279. });
  280. app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
  281. app.mount('#medicineApp');
  282. </script>
  283. {% endblock %}