index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <template>
  2. <div class="app-container">
  3. <el-row :gutter="20">
  4. <el-col>
  5. <!-- 查询表单 -->
  6. <el-form v-show="showSearch" :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
  7. <el-form-item label="姓名">
  8. <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" style="width: 200px"/>
  9. </el-form-item>
  10. <el-form-item label="手机号">
  11. <el-input v-model="queryParams.phone" placeholder="请输入手机号" clearable @keyup.enter="handleQuery" style="width: 200px"/>
  12. </el-form-item>
  13. <el-form-item label="园所名称">
  14. <el-select
  15. v-model="queryParams.school_id"
  16. placeholder="请选择园所名称"
  17. clearable
  18. filterable
  19. remote
  20. style="width: 200px"
  21. >
  22. <el-option v-for="s in schoolList" :key="s._id" :label="s.name" :value="s._id"/>
  23. </el-select>
  24. </el-form-item>
  25. <el-form-item>
  26. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  27. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  28. </el-form-item>
  29. </el-form>
  30. <!-- 批量操作 -->
  31. <el-row :gutter="10" class="mb8">
  32. <el-col :span="1.5">
  33. <el-button
  34. type="primary"
  35. plain
  36. icon="Plus"
  37. @click="handleAdd"
  38. >新增</el-button
  39. >
  40. </el-col>
  41. <el-col :span="1.5">
  42. <el-button
  43. type="info"
  44. plain
  45. icon="Upload"
  46. @click="handleImport"
  47. >导入</el-button>
  48. </el-col>
  49. <right-toolbar
  50. v-model:showSearch="showSearch"
  51. @queryTable="getList"
  52. ></right-toolbar>
  53. </el-row>
  54. <!-- 表格 -->
  55. <el-table v-loading="loading" :data="userList" >
  56. <el-table-column type="index" label="序号" width="60" align="center"
  57. :index="(index) => (queryParams.pageNum - 1) * queryParams.pageSize + index + 1"/>
  58. <el-table-column label="姓名" prop="name" align="center"/>
  59. <el-table-column label="用户名" prop="user_name" align="center"/>
  60. <el-table-column label="性别" align="center">
  61. <template #default="{ row }">{{ row.gender === 1 ? '男' : row.gender === 0 ? '女' : '未知' }}</template>
  62. </el-table-column>
  63. <el-table-column label="手机号" prop="phone" align="center"/>
  64. <el-table-column label="园所" align="center">
  65. <template #default="{ row }">{{ getSchoolName(row.school_id) }}</template>
  66. </el-table-column>
  67. <el-table-column label="备注" prop="remark" align="center"/>
  68. <el-table-column label="操作" align="center" width="160">
  69. <template #default="{ row }">
  70. <el-button type="primary" link icon="Edit" size="small" @click="handleEdit(row)"></el-button>
  71. <el-button type="danger" link icon="Delete" size="small" @click="handleDelete(row)"></el-button>
  72. </template>
  73. </el-table-column>
  74. </el-table>
  75. <!-- 分页 -->
  76. <pagination v-if="total > 0" :total="total"
  77. v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
  78. @pagination="getList"/>
  79. </el-col>
  80. </el-row>
  81. <!-- 新增/编辑弹窗 -->
  82. <el-dialog :title="title" v-model="open" width="500px">
  83. <el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
  84. <el-form-item label="姓名" prop="name">
  85. <el-input v-model="form.name" placeholder="请输入姓名"/>
  86. </el-form-item>
  87. <el-form-item label="用户名" prop="user_name">
  88. <el-input v-model="form.user_name" placeholder="请输入用户名"/>
  89. </el-form-item>
  90. <el-form-item label="手机号" prop="phone">
  91. <el-input v-model="form.phone" placeholder="请输入手机号"/>
  92. </el-form-item>
  93. <el-form-item label="性别" prop="gender">
  94. <el-select v-model="form.gender" placeholder="请选择">
  95. <el-option label="男" :value="1"/>
  96. <el-option label="女" :value="0"/>
  97. </el-select>
  98. </el-form-item>
  99. <el-form-item label="园所" prop="school_id">
  100. <el-select
  101. v-model="form.school_id"
  102. placeholder="请选择园所名称"
  103. clearable
  104. filterable
  105. remote
  106. style="width: 200px"
  107. >
  108. <el-option v-for="s in schoolList" :key="s._id" :label="s.name" :value="s._id"/>
  109. </el-select>
  110. </el-form-item>
  111. <el-form-item label="备注" prop="remark">
  112. <el-input type="textarea" v-model="form.remark" placeholder="请输入备注"/>
  113. </el-form-item>
  114. </el-form>
  115. <template #footer>
  116. <el-button type="primary" @click="submitForm">确 定</el-button>
  117. <el-button @click="open=false">取 消</el-button>
  118. </template>
  119. </el-dialog>
  120. <!-- 用户导入对话框 -->
  121. <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
  122. <el-upload
  123. ref="uploadRef"
  124. :limit="1"
  125. accept=".xlsx, .xls"
  126. :disabled="upload.isUploading"
  127. :auto-upload="true"
  128. :before-upload="beforeUpload"
  129. :on-remove="handleRemove"
  130. drag
  131. >
  132. <el-icon class="el-icon--upload"><upload-filled /></el-icon>
  133. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  134. <template #tip>
  135. <div class="el-upload__tip text-center">
  136. <span>仅允许导入xls、xlsx格式文件。</span>
  137. <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
  138. </div>
  139. </template>
  140. </el-upload>
  141. </el-dialog>
  142. </div>
  143. </template>
  144. <script setup>
  145. import { getToken } from "@/utils/auth";
  146. import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
  147. const { proxy } = getCurrentInstance();
  148. const userList = ref([]);
  149. const schoolList = ref([]);
  150. const loading = ref(false);
  151. const open = ref(false);
  152. const title = ref('');
  153. const total = ref(0);
  154. const showSearch = ref(true);
  155. /*** 用户导入参数 */
  156. const upload = reactive({
  157. // 是否显示弹出层(用户导入)
  158. open: false,
  159. // 弹出层标题(用户导入)
  160. title: "",
  161. // 是否禁用上传
  162. isUploading: false,
  163. // 是否更新已经存在的用户数据
  164. // updateSupport: 0,
  165. // 设置上传的请求头部
  166. headers: { Authorization: "Bearer " + getToken() },
  167. // 上传的地址
  168. url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
  169. });
  170. const data = reactive({
  171. form: {},
  172. queryParams: { pageNum: 1, pageSize: 10, name: '', user_name:'', phone:'', school_id:'' },
  173. rules: {
  174. name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
  175. user_name: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
  176. phone: [
  177. { required: true, message: '手机号不能为空', trigger: 'blur' },
  178. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
  179. ],
  180. // gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
  181. school_id: [{ required: true, message: '请选择园所', trigger: 'change' }]
  182. }
  183. });
  184. const { queryParams, form, rules } = toRefs(data);
  185. /** 获取学校名称 */
  186. function getSchoolName(id) {
  187. const s = schoolList.value.find(x => x._id === id);
  188. return s ? s.name : '';
  189. }
  190. /** 获取用户列表 */
  191. async function getList() {
  192. loading.value = true;
  193. const filter = { where: { delete: 0 } };
  194. if(queryParams.value.name) filter.where.name = queryParams.value.name;
  195. if(queryParams.value.user_name) filter.where.user_name = queryParams.value.user_name;
  196. if(queryParams.value.phone) filter.where.phone = queryParams.value.phone;
  197. if(queryParams.value.school_id) filter.where.school_id = queryParams.value.school_id;
  198. const res = await proxy.$models.wx_teacher_user.list({
  199. filter,
  200. pageNumber: queryParams.value.pageNum,
  201. pageSize: queryParams.value.pageSize,
  202. getCount: true,
  203. envType: 'prod'
  204. });
  205. userList.value = res.data.records || [];
  206. total.value = res.data.total || 0;
  207. loading.value = false;
  208. }
  209. /** 获取学校列表 */
  210. async function getSchoolList() {
  211. const res = await proxy.$models.wx_school.list({ filter:{}, envType:'prod' });
  212. schoolList.value = res.data.records || [];
  213. }
  214. /** 搜索 */
  215. function handleQuery() { queryParams.value.pageNum = 1; getList(); }
  216. /** 重置 */
  217. function resetQuery() {
  218. queryParams.value = { pageNum: 1, pageSize: 10, name: '', user_name:'', phone:'', school_id:'' };
  219. getList();
  220. }
  221. /** 新增 */
  222. function handleAdd() { form.value = {}; open.value = true; title.value = '新增教师'; }
  223. /** 编辑 */
  224. function handleEdit(row) { form.value = { ...row }; open.value = true; title.value = '编辑教师'; }
  225. /** 删除 */
  226. async function handleDelete(row) {
  227. try {
  228. await proxy.$modal.confirm(`是否确认删除 ${row.name} ?`);
  229. await proxy.$models.wx_teacher_user.update({
  230. filter: { where: { _id: { $eq: row._id } } },
  231. data: { delete: 1 },
  232. envType: 'prod'
  233. });
  234. proxy.$modal.msgSuccess('删除成功');
  235. getList();
  236. } catch(err) {}
  237. }
  238. /** 提交表单 */
  239. async function submitForm() {
  240. await proxy.$refs.formRef.validate();
  241. if(form.value._id){
  242. await proxy.$models.wx_teacher_user.update({ data: form.value, filter:{ where:{ _id: { $eq: form.value._id } } }, envType:'prod' });
  243. proxy.$modal.msgSuccess('修改成功');
  244. } else {
  245. await proxy.$models.wx_teacher_user.create({ data: form.value, envType:'prod' });
  246. proxy.$modal.msgSuccess('新增成功');
  247. }
  248. open.value = false;
  249. getList();
  250. }
  251. /** 导入按钮操作 */
  252. function handleImport() {
  253. upload.title = "用户导入";
  254. upload.open = true;
  255. };
  256. /** 下载模板操作 */
  257. async function importTemplate() {
  258. try {
  259. // 获取存储中的模板文件下载地址
  260. const res = await proxy.$apps
  261. // .storage()
  262. .getTempFileURL({
  263. fileList: ["cloud://honghgaier-5guiffgcf17a2eea.686f-honghgaier-5guiffgcf17a2eea-1373037829/teachermoban.xlsx"], // 云存储里的路径
  264. });
  265. console.log(res.fileList[0].tempFileURL, 'res.fileList[0].tempFileURL');
  266. const fileUrl = res.fileList[0].tempFileURL;
  267. // 触发浏览器下载
  268. const a = document.createElement("a");
  269. a.href = fileUrl;
  270. a.download = `system_teacher_${Date.now()}.xlsx`;
  271. a.click();
  272. } catch (err) {
  273. proxy.$modal.msgError("下载失败");
  274. }
  275. };
  276. /** 上传前处理(改用腾讯云存储) */
  277. async function beforeUpload(file) {
  278. upload.isUploading = true;
  279. try {
  280. // 1. file 就是原生 File 对象
  281. console.log(file, '上传文件对象');
  282. if (!(file instanceof File)) {
  283. throw new Error('请选择本地 Excel 文件上传');
  284. }
  285. // 2. 安全文件名
  286. const safeFileName = file.name.replace(/[^a-zA-Z0-9_.-]/g, '_');
  287. const cloudPath = `excel/teacher/${Date.now()}_${safeFileName}`;
  288. // 3. 上传文件
  289. const res = await proxy.$apps.uploadFile({
  290. cloudPath,
  291. filePath: file
  292. });
  293. console.log('上传成功 fileID:', res.fileID);
  294. // 4. 调用云函数解析
  295. const cloudResult = await proxy.$apps.callFunction({
  296. name: "teacherexcel",
  297. data: { fileID: res.fileID }
  298. });
  299. console.log('云函数返回:', cloudResult);
  300. const fnResult = cloudResult.result || cloudResult;
  301. if (fnResult.code === 0) {
  302. proxy.$modal.msgSuccess('导入成功');
  303. upload.open = false;
  304. getList();
  305. } else {
  306. proxy.$modal.msgError('导入失败:' + (fnResult.msg || '未知错误'));
  307. }
  308. } catch (err) {
  309. console.error('上传失败:', err);
  310. proxy.$modal.msgError('上传失败: ' + err.message);
  311. } finally {
  312. upload.isUploading = false;
  313. }
  314. return false; // 阻止默认上传
  315. }
  316. /** 移除文件 */
  317. function handleRemove(file) {
  318. console.log("文件移除:", file);
  319. }
  320. // /**文件上传中处理 */
  321. // const handleFileUploadProgress = (event, file, fileList) => {
  322. // upload.isUploading = true;
  323. // };
  324. // /** 文件上传成功处理 */
  325. // const handleFileSuccess = (response, file, fileList) => {
  326. // upload.open = false;
  327. // upload.isUploading = false;
  328. // proxy.$refs["uploadRef"].handleRemove(file);
  329. // proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
  330. // getList();
  331. // };
  332. // /** 提交上传文件 */
  333. // function submitFileForm() {
  334. // proxy.$refs["uploadRef"].submit();
  335. // };
  336. getSchoolList();
  337. getList();
  338. </script>
  339. <style scoped lang="scss">
  340. .app-container{ padding: 20px; }
  341. </style>