Explorar o código

Merge remote-tracking branch 'origin/dev1.0' into dev1.0

adminAndroot hai 9 meses
pai
achega
8ef5be8ab0
Modificáronse 38 ficheiros con 2703 adicións e 99 borrados
  1. 91 0
      admin-ui/src/views/work/notice/edit.vue
  2. 109 0
      admin-ui/src/views/work/notice/index.vue
  3. 1 1
      app/App.vue
  4. 55 8
      app/common/common.scss
  5. 79 0
      app/components/back/back.vue
  6. 1 1
      app/components/leditor/leditor.vue
  7. 254 0
      app/components/u-avatar-cropper/u-avatar-cropper.vue
  8. 1265 0
      app/components/u-avatar-cropper/weCropper.js
  9. 21 5
      app/pages.json
  10. 20 7
      app/pages/follow/detail.vue
  11. 1 0
      app/pages/follow/doctor/index.vue
  12. 1 1
      app/pages/follow/index.vue
  13. 49 3
      app/pages/follow/remind.vue
  14. 11 14
      app/pages/index/index.vue
  15. 87 0
      app/pages/notice/detail.vue
  16. 98 0
      app/pages/notice/index.vue
  17. 24 2
      app/pages/other/setting.vue
  18. 12 5
      app/pages/user/index.vue
  19. 105 8
      app/pages/user/info.vue
  20. 29 20
      app/pages/user/login.vue
  21. 9 15
      app/pages/user/loginDoctor.vue
  22. 88 0
      app/pages/user/pass.vue
  23. BIN=BIN
      app/static/my2.png
  24. 5 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_HomeController.java
  25. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_MdmController.java
  26. 42 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_NoticeController.java
  27. 1 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/api/config/InterceptorConfig.java
  28. 63 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/controller/NoticeController.java
  29. 50 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/domain/Notice.java
  30. 32 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/domain/vo/NoticeVoList.java
  31. 17 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/mapper/NoticeMapper.java
  32. 0 6
      ruoyi-admin/src/main/java/com/ruoyi/web/work/service/IKnowledgeService.java
  33. 17 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/service/INoticeService.java
  34. 31 0
      ruoyi-admin/src/main/java/com/ruoyi/web/work/service/impl/NoticeServiceImpl.java
  35. 6 1
      ruoyi-admin/src/main/resources/mapper/work/KnowledgeMapper.xml
  36. 20 0
      ruoyi-admin/src/main/resources/mapper/work/NoticeMapper.xml
  37. 4 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
  38. 4 1
      ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

+ 91 - 0
admin-ui/src/views/work/notice/edit.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="cmain">
+    <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+      <el-form-item label="通知标题" prop="title">
+        <el-input v-model="form.title" placeholder="请输入标题" clearable maxlength="30" show-word-limit />
+      </el-form-item>
+      <el-form-item label="通知内容" prop="contents">
+        <editor v-model="form.contents" placeholder="请输入内容"></editor>
+      </el-form-item>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="通知状态" prop="state">
+            <el-select v-model="form.state" placeholder="请选择">
+              <el-option label="正常" :value="0"></el-option>
+              <el-option label="停用" :value="1"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="通知置顶" prop="top">
+            <el-select v-model="form.top" placeholder="请选择">
+              <el-option label="否" :value="0"></el-option>
+              <el-option label="是" :value="1"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="mfooter">
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="$layer.close(layerid)">取 消</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      form: { state: 0, top: 0 },
+      rules: {
+        title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
+        type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
+        contents: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
+        top: [{ required: true, message: '是否置顶不能为空', trigger: 'blur' }],
+        state: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+      }
+    };
+  },
+  props: {
+    param: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    layerid: {
+      type: String
+    }
+  },
+  mounted() {
+    if (this.param.id) {
+      this.ajax({ url: '/work/notice/detail/' + this.param.id }).then((response) => {
+        this.form = response.data;
+        this.form.contents = response.data.contents.replace(new RegExp('/profile/upload/', 'g'), this.baseUrl + '/profile/upload/');
+      });
+    }
+  },
+  methods: {
+    submitForm() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          if (this.form.id) {
+            this.ajax({ method: 'post', url: '/work/notice/edit', data: this.form }).then((response) => {
+              this.$modal.msgSuccess('修改成功');
+              this.$layer.close(this.layerid);
+              this.$parent.getList();
+            });
+          } else {
+            this.ajax({ method: 'post', url: '/work/notice/add', data: this.form }).then((response) => {
+              this.$modal.msgSuccess('新增成功');
+              this.$layer.close(this.layerid);
+              this.$parent.getList();
+            });
+          }
+        }
+      });
+    }
+  }
+};
+</script>

+ 109 - 0
admin-ui/src/views/work/notice/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent>
+      <el-form-item label="标题" prop="title">
+        <el-input v-model="queryParams.title" placeholder="请输入标题" @keyup.enter.native="handleQuery" clearable />
+      </el-form-item>
+      <el-form-item label="状态" prop="state">
+        <el-select v-model="queryParams.state" placeholder="状态" class="se" clearable>
+          <el-option value="0" label="启用"></el-option>
+          <el-option value="1" label="禁用"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-row :gutter="10" class="mb8">
+      <el-button type="primary" icon="el-icon-plus" :disabled="ids.length > 0" @click="op('add')" v-hasPermi="['work:notice:add']">新增</el-button>
+      <el-button type="success" icon="el-icon-edit" :disabled="ids.length != 1" @click="op('edit', ids)" v-hasPermi="['work:notice:edit']">修改</el-button>
+      <el-button type="danger" icon="el-icon-delete" :disabled="ids.length == 0" @click="del" v-hasPermi="['work:notice:remove']">删除{{ ids.length > 0 ? '(' + ids.length + ')' : '' }}</el-button>
+    </el-row>
+    <el-table :data="response.rows" border @selection-change="selects" height="calc(100vh - 270px)">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="通知标题" align="center" prop="title" />
+      <el-table-column label="是否置顶" align="center" prop="top" width="140">
+        <template slot-scope="scope">
+          <el-tag type="success" v-if="scope.row.top == 1">是</el-tag>
+          <el-tag type="info" v-else>否</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="通知状态" align="center" prop="state" width="140">
+        <template slot-scope="scope">
+          <el-tag type="success" v-if="scope.row.state == 0">启用</el-tag>
+          <el-tag type="danger" v-if="scope.row.state == 1">停用</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="200" />
+      <el-table-column label="操作" align="center" width="200">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="op('edit', scope.row)" v-hasPermi="['work:notice:edit']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="del(scope.row)" v-hasPermi="['work:notice:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+      <template slot="empty">
+        <el-empty></el-empty>
+      </template>
+    </el-table>
+    <pagination v-if="response.total > 0" :total="response.total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import edit from './edit';
+export default {
+  name: 'Notice',
+  data() {
+    return {
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: null,
+        top: null,
+        state: null,
+        orderByColumn: 'id', //排序字段
+        isAsc: 'desc' //排序方式
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    getList() {
+      this.ajax({ url: '/work/notice/list', data: this.queryParams }).then((response) => {
+        this.response = response;
+      });
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    resetQuery() {
+      this.resetForm('queryForm');
+      this.handleQuery();
+    },
+    selects(rows) {
+      this.ids = rows.map((item) => item.id);
+    },
+    op(tag, row) {
+      if (tag == 'add') {
+        this.iframe({ obj: edit, param: {}, title: '新增', width: '58%', height: '65%' });
+      }
+      if (tag == 'edit') {
+        const id = row.id || this.ids[0];
+        this.iframe({ obj: edit, param: { id: id }, title: '编辑', width: '58%', height: '65%' });
+      }
+    },
+    del(row) {
+      this.$confirm('是否确认删除选中数据?', '警告', { type: 'warning' }).then(() => {
+        this.get({ url: '/work/notice/remove/' + (row.id || this.ids) }).then((response) => {
+          this.$modal.msgSuccess('删除成功');
+          this.getList();
+        });
+      });
+    }
+  }
+};
+</script>

+ 1 - 1
app/App.vue

@@ -30,7 +30,7 @@ button::after {
 /**挂载iconfont字体图标*/
 @font-face {
 	font-family: 'iconfont';
-	src: url('https://at.alicdn.com/t/c/font_4620946_d1f699zeqx.ttf?t=1723622501301') format('truetype');
+	src: url('https://at.alicdn.com/t/c/font_4620946_wjo0wa9j5yc.ttf?t=1724155918797') format('truetype');
 	/* src: url('~@/static/font/iconfont.ttf') format('truetype'); */
 }
 .icon {

+ 55 - 8
app/common/common.scss

@@ -54,7 +54,7 @@
 	font-size: 15px;
 	text-align: center;
 	padding: 5px;
-	margin-top: 15px;
+	margin-top: 20px;
 }
 .search {
 	padding: 5px 15px 5px 15px;
@@ -141,6 +141,7 @@
 		overflow: hidden;
 		border-bottom: 1px solid #f0f2f7;
 		font-size: 15px;
+		position: relative;
 		.ic {
 			display: block;
 			float: left;
@@ -158,6 +159,16 @@
 			padding-top: 11px;
 			float: left;
 		}
+		.bage {
+			position: absolute;
+			padding: 0px 5px;
+			border-radius: 20px;
+			background-color: red;
+			color: white;
+			top: 22px;
+			right: 35px;
+			font-size: 13px;
+		}
 		.arrow {
 			font-size: 20px;
 			float: right;
@@ -199,17 +210,17 @@
 	position: relative;
 	background-color: white;
 	border-bottom: 1px solid $line;
+	font-size: 15px;
 	&.form_group:last-child {
 		border-bottom: 0px;
 	}
 	.lable {
-		font-size: 17px;
+		font-size: 15px;
 		text-align: left;
 		position: relative;
-		font-weight: bold;
 		float: left;
 		color: $font-c;
-		margin-top: 7px;
+		margin-top: 2px;
 		.limit {
 			padding-left: 8px;
 			font-size: 13px;
@@ -255,14 +266,15 @@
 	}
 	.desc {
 		float: right;
-		padding-top: 5px;
+		padding-top: 2px;
 		width: 72%;
 		text-align: right;
+		color: #9e9e9e;
 	}
 	input {
 		font-size: 15px;
 		border-radius: 5px;
-		padding: 10px 5px 10px 10px;
+		padding: 4px 10px 0px 3px;
 		width: 72%;
 		float: right;
 		text-align: right;
@@ -286,7 +298,7 @@
 	.more {
 		position: absolute;
 		right: -13px;
-		top: 20px;
+		top: 14px;
 		font-weight: normal;
 		color: $font-c;
 	}
@@ -407,7 +419,7 @@
 			.state {
 				float: right;
 			}
-			.edit{
+			.edit {
 				float: right;
 				color: $main-color;
 			}
@@ -419,3 +431,38 @@
 		}
 	}
 }
+.popup {
+	padding: 15px;
+	background-color: white;
+	border-radius: 10px;
+	.mtt {
+		text-align: center;
+		font-size: 18px;
+		padding-top: 15px;
+		padding-bottom: 20px;
+	}
+	.item {
+		overflow: hidden;
+		border-bottom: 1px solid $line;
+		padding: 10px;
+		&:first-child {
+			margin-top: 10px;
+		}
+		&:last-child {
+			border: 0px;
+		}
+		.tt {
+			float: left;
+		}
+		.la {
+			float: right;
+			width: 70%;
+			text-align: right;
+			image {
+				width: 50px;
+				height: 50px;
+				border-radius: 5px;
+			}
+		}
+	}
+}

+ 79 - 0
app/components/back/back.vue

@@ -0,0 +1,79 @@
+<template>
+	<view>
+		<view class="back" :style="{ paddingTop: top + 'px' }">
+			<view class="cont">
+				<text class="icon" @click="back()">&#xe8ef;</text>
+				<text class="title">{{ title }}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'back',
+	props: {
+		title: {
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		return {
+			top: 0,
+			mb: 140
+		};
+	},
+	mounted() {
+		uni.getSystemInfo({
+			success: res => {
+				this.top = parseInt(res.safeArea.top) - 5;
+				// #ifdef MP-WEIXIN
+				this.mb = this.mb - this.top + 10;
+				// #endif
+				// #ifdef H5
+				this.mb = this.mb - this.top;
+				// #endif
+			},
+			fail(err) {
+				console.error(err);
+			}
+		});
+	},
+	methods: {
+		back() {
+			uni.navigateBack();
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.back {
+	position: fixed;
+	top: 0px;
+	width: 100%;
+	height: 50px;
+	text-align: center;
+	color: white;
+	z-index: 11111;
+	.cont {
+		padding: 15px 0px 15px 0px;
+		position: relative;
+		.icon {
+			position: absolute;
+			font-size: 25px;
+			left: 10px;
+		}
+		.title {
+			font-size: 16px;
+			font-weight: bold;
+		}
+	}
+}
+.top {
+	image {
+		width: 100%;
+	}
+}
+</style>

+ 1 - 1
app/components/leditor/leditor.vue

@@ -14,7 +14,7 @@
 					<view :class="formats.lineHeight ? 'ql-active' : ''" class="icon" data-name="lineHeight" data-value="2" title="行距">&#xe7f8;</view>
 					<view :class="formats.list === 'ordered' ? 'ql-active' : ''" class="icon" data-name="list" data-value="ordered" title="编号">&#xe7f5;</view>
 					<!-- <view class="icon" title="分割线" @tap="insertDivider">&#xe620;</view> -->
-					<view class="icon" @tap="insertDate">&#xe8b8;</view>
+					<!-- <view class="icon" @tap="insertDate">&#xe8b8;</view> -->
 					<view class="icon" @tap="insertImage" title="插入图片">&#xe64a;</view>
 					<view class="icon" @tap="clear" title="清空内容">&#xe8b6;</view>
 				</view>

+ 254 - 0
app/components/u-avatar-cropper/u-avatar-cropper.vue

@@ -0,0 +1,254 @@
+<template>
+	<view class="content">
+		<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				@touchstart="touchStart"
+				@touchmove="touchMove"
+				@touchend="touchEnd"
+				:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
+				canvas-id="cropper"
+				id="cropper"
+			></canvas>
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				:style="{
+					position: 'fixed',
+					top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
+					width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					height: `${cropperOpt.height * cropperOpt.pixelRatio}`
+				}"
+				canvas-id="targetId"
+				id="targetId"
+			></canvas>
+		</view>
+		<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
+			<!-- #ifdef H5 -->
+			<view class="upload" @tap="uploadTap">选择图片</view>
+			<!-- #endif -->
+			<!-- #ifndef H5 -->
+			<view class="upload" @tap="uploadTap">重新选择</view>
+			<!-- #endif -->
+			<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import WeCropper from './weCropper.js';
+export default {
+	props: {
+		// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
+		// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
+		boundStyle: {
+			type: Object,
+			default() {
+				return {
+					lineWidth: 4,
+					borderColor: 'rgb(245, 245, 245)',
+					mask: 'rgba(0, 0, 0, 0.35)'
+				};
+			}
+		}
+	},
+	data() {
+		return {
+			// 底部导航的高度
+			bottomNavHeight: 50,
+			originWidth: 200,
+			width: 0,
+			height: 0,
+			cropperOpt: {
+				id: 'cropper',
+				targetId: 'targetCropper',
+				pixelRatio: 1,
+				width: 0,
+				height: 0,
+				scale: 2.5,
+				zoom: 8,
+				cut: {
+					x: (this.width - this.originWidth) / 2,
+					y: (this.height - this.originWidth) / 2,
+					width: this.originWidth,
+					height: this.originWidth
+				},
+				boundStyle: {
+					lineWidth: uni.upx2px(this.boundStyle.lineWidth),
+					mask: this.boundStyle.mask,
+					color: this.boundStyle.borderColor
+				}
+			},
+			// 裁剪框和输出图片的尺寸,高度默认等于宽度
+			// 输出图片宽度,单位px
+			destWidth: 200,
+			// 裁剪框宽度,单位px
+			rectWidth: 200,
+			// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
+			fileType: 'jpg',
+			src: '' // 选择的图片路径,用于在点击确定时,判断是否选择了图片
+		};
+	},
+	onLoad(option) {
+		let rectInfo = uni.getSystemInfoSync();
+		this.width = rectInfo.windowWidth;
+		this.height = rectInfo.windowHeight - this.bottomNavHeight;
+		this.cropperOpt.width = this.width;
+		this.cropperOpt.height = this.height;
+		this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
+
+		if (option.destWidth) this.destWidth = option.destWidth;
+		if (option.rectWidth) {
+			let rectWidth = Number(option.rectWidth);
+			this.cropperOpt.cut = {
+				x: (this.width - rectWidth) / 2,
+				y: (this.height - rectWidth) / 2,
+				width: 100,
+				height: 140
+			};
+		}
+		this.rectWidth = option.rectWidth;
+		if (option.fileType) this.fileType = option.fileType;
+		// 初始化
+		this.cropper = new WeCropper(this.cropperOpt)
+			.on('ready', ctx => {
+				// wecropper is ready for work!
+			})
+			.on('beforeImageLoad', ctx => {
+				// before picture loaded, i can do something
+			})
+			.on('imageLoad', ctx => {
+				// picture loaded
+			})
+			.on('beforeDraw', (ctx, instance) => {
+				// before canvas draw,i can do something
+			});
+		// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
+		uni.setNavigationBarColor({
+			frontColor: '#ffffff',
+			backgroundColor: '#000000'
+		});
+		uni.chooseImage({
+			count: 1, // 默认9
+			sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
+			sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+			success: res => {
+				this.src = res.tempFilePaths[0];
+				//  获取裁剪图片资源后,给data添加src属性及其值
+				this.cropper.pushOrign(this.src);
+			}
+		});
+	},
+	methods: {
+		touchStart(e) {
+			this.cropper.touchStart(e);
+		},
+		touchMove(e) {
+			this.cropper.touchMove(e);
+		},
+		touchEnd(e) {
+			this.cropper.touchEnd(e);
+		},
+		getCropperImage(isPre = false) {
+			if (!this.src) return this.$u.toast('请先选择图片再裁剪');
+
+			let cropper_opt = {
+				destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
+				destWidth: Number(this.destWidth),
+				fileType: this.fileType
+			};
+			this.cropper.getCropperImage(cropper_opt, (path, err) => {
+				if (err) {
+					uni.showModal({
+						title: '温馨提示',
+						content: err.message
+					});
+				} else {
+					if (isPre) {
+						uni.previewImage({
+							current: '', // 当前显示图片的 http 链接
+							urls: [path] // 需要预览的图片 http 链接列表
+						});
+					} else {
+						uni.$emit('uAvatarCropper', path);
+						uni.navigateBack();
+					}
+				}
+			});
+		},
+		uploadTap() {
+			const self = this;
+			uni.chooseImage({
+				count: 1, // 默认9
+				sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
+				sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+				success: res => {
+					self.src = res.tempFilePaths[0];
+					//  获取裁剪图片资源后,给data添加src属性及其值
+					self.cropper.pushOrign(this.src);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+.content {
+	background: rgba(255, 255, 255, 1);
+}
+.cropper {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 11;
+}
+
+.cropper-buttons {
+	background-color: #000000;
+	color: #eee;
+}
+
+.cropper-wrapper {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	width: 100%;
+	background-color: #000;
+}
+
+.cropper-buttons {
+	width: 100vw;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	font-size: 28rpx;
+}
+
+.cropper-buttons .upload,
+.cropper-buttons .getCropperImage {
+	width: 50%;
+	text-align: center;
+	cursor: pointer;
+}
+
+.cropper-buttons .upload {
+	text-align: left;
+	padding-left: 50rpx;
+}
+
+.cropper-buttons .getCropperImage {
+	text-align: right;
+	padding-right: 50rpx;
+}
+</style>

+ 1265 - 0
app/components/u-avatar-cropper/weCropper.js

@@ -0,0 +1,1265 @@
+/**
+ * we-cropper v1.3.9
+ * (c) 2020 dlhandsome
+ * @license MIT
+ */
+(function(global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+		typeof define === 'function' && define.amd ? define(factory) :
+		(global.WeCropper = factory());
+}(this, (function() {
+	'use strict';
+
+	var device = void 0;
+	var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
+
+	function firstLetterUpper(str) {
+		return str.charAt(0).toUpperCase() + str.slice(1)
+	}
+
+	function setTouchState(instance) {
+		var arg = [],
+			len = arguments.length - 1;
+		while (len-- > 0) arg[len] = arguments[len + 1];
+
+		TOUCH_STATE.forEach(function(key, i) {
+			if (arg[i] !== undefined) {
+				instance[key] = arg[i];
+			}
+		});
+	}
+
+	function validator(instance, o) {
+		Object.defineProperties(instance, o);
+	}
+
+	function getDevice() {
+		if (!device) {
+			device = uni.getSystemInfoSync();
+		}
+		return device
+	}
+
+	var tmp = {};
+
+	var ref = getDevice();
+	var pixelRatio = ref.pixelRatio;
+
+	var DEFAULT = {
+		id: {
+			default: 'cropper',
+			get: function get() {
+				return tmp.id
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("id:" + value + " is invalid"));
+				}
+				tmp.id = value;
+			}
+		},
+		width: {
+			default: 750,
+			get: function get() {
+				return tmp.width
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("width:" + value + " is invalid"));
+				}
+				tmp.width = value;
+			}
+		},
+		height: {
+			default: 750,
+			get: function get() {
+				return tmp.height
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("height:" + value + " is invalid"));
+				}
+				tmp.height = value;
+			}
+		},
+		pixelRatio: {
+			default: pixelRatio,
+			get: function get() {
+				return tmp.pixelRatio
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("pixelRatio:" + value + " is invalid"));
+				}
+				tmp.pixelRatio = value;
+			}
+		},
+		scale: {
+			default: 2.5,
+			get: function get() {
+				return tmp.scale
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("scale:" + value + " is invalid"));
+				}
+				tmp.scale = value;
+			}
+		},
+		zoom: {
+			default: 5,
+			get: function get() {
+				return tmp.zoom
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("zoom:" + value + " is invalid"));
+				} else if (value < 0 || value > 10) {
+					console.error("zoom should be ranged in 0 ~ 10");
+				}
+				tmp.zoom = value;
+			}
+		},
+		src: {
+			default: '',
+			get: function get() {
+				return tmp.src
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("src:" + value + " is invalid"));
+				}
+				tmp.src = value;
+			}
+		},
+		cut: {
+			default: {},
+			get: function get() {
+				return tmp.cut
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("cut:" + value + " is invalid"));
+				}
+				tmp.cut = value;
+			}
+		},
+		boundStyle: {
+			default: {},
+			get: function get() {
+				return tmp.boundStyle
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("boundStyle:" + value + " is invalid"));
+				}
+				tmp.boundStyle = value;
+			}
+		},
+		onReady: {
+			default: null,
+			get: function get() {
+				return tmp.ready
+			},
+			set: function set(value) {
+				tmp.ready = value;
+			}
+		},
+		onBeforeImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.beforeImageLoad
+			},
+			set: function set(value) {
+				tmp.beforeImageLoad = value;
+			}
+		},
+		onImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.imageLoad
+			},
+			set: function set(value) {
+				tmp.imageLoad = value;
+			}
+		},
+		onBeforeDraw: {
+			default: null,
+			get: function get() {
+				return tmp.beforeDraw
+			},
+			set: function set(value) {
+				tmp.beforeDraw = value;
+			}
+		}
+	};
+
+	var ref$1 = getDevice();
+	var windowWidth = ref$1.windowWidth;
+
+	function prepare() {
+		var self = this;
+
+		// v1.4.0 版本中将不再自动绑定we-cropper实例
+		self.attachPage = function() {
+			var pages = getCurrentPages();
+			// 获取到当前page上下文
+			var pageContext = pages[pages.length - 1];
+			// 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
+			Object.defineProperty(pageContext, 'wecropper', {
+				get: function get() {
+					console.warn(
+						'Instance will not be automatically bound to the page after v1.4.0\n\n' +
+						'Please use a custom instance name instead\n\n' +
+						'Example: \n' +
+						'this.mycropper = new WeCropper(options)\n\n' +
+						'// ...\n' +
+						'this.mycropper.getCropperImage()'
+					);
+					return self
+				},
+				configurable: true
+			});
+		};
+
+		self.createCtx = function() {
+			var id = self.id;
+			var targetId = self.targetId;
+
+			if (id) {
+				self.ctx = self.ctx || uni.createCanvasContext(id);
+				self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId);
+			} else {
+				console.error("constructor: create canvas context failed, 'id' must be valuable");
+			}
+		};
+
+		self.deviceRadio = windowWidth / 750;
+	}
+
+	var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !==
+		'undefined' ? self : {};
+
+
+
+
+
+	function createCommonjsModule(fn, module) {
+		return module = {
+			exports: {}
+		}, fn(module, module.exports), module.exports;
+	}
+
+	var tools = createCommonjsModule(function(module, exports) {
+		/**
+		 * String type check
+		 */
+		exports.isStr = function(v) {
+			return typeof v === 'string';
+		};
+		/**
+		 * Number type check
+		 */
+		exports.isNum = function(v) {
+			return typeof v === 'number';
+		};
+		/**
+		 * Array type check
+		 */
+		exports.isArr = Array.isArray;
+		/**
+		 * undefined type check
+		 */
+		exports.isUndef = function(v) {
+			return v === undefined;
+		};
+
+		exports.isTrue = function(v) {
+			return v === true;
+		};
+
+		exports.isFalse = function(v) {
+			return v === false;
+		};
+		/**
+		 * Function type check
+		 */
+		exports.isFunc = function(v) {
+			return typeof v === 'function';
+		};
+		/**
+		 * Quick object check - this is primarily used to tell
+		 * Objects from primitive values when we know the value
+		 * is a JSON-compliant type.
+		 */
+		exports.isObj = exports.isObject = function(obj) {
+			return obj !== null && typeof obj === 'object'
+		};
+
+		/**
+		 * Strict object type check. Only returns true
+		 * for plain JavaScript objects.
+		 */
+		var _toString = Object.prototype.toString;
+		exports.isPlainObject = function(obj) {
+			return _toString.call(obj) === '[object Object]'
+		};
+
+		/**
+		 * Check whether the object has the property.
+		 */
+		var hasOwnProperty = Object.prototype.hasOwnProperty;
+		exports.hasOwn = function(obj, key) {
+			return hasOwnProperty.call(obj, key)
+		};
+
+		/**
+		 * Perform no operation.
+		 * Stubbing args to make Flow happy without leaving useless transpiled code
+		 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
+		 */
+		exports.noop = function(a, b, c) {};
+
+		/**
+		 * Check if val is a valid array index.
+		 */
+		exports.isValidArrayIndex = function(val) {
+			var n = parseFloat(String(val));
+			return n >= 0 && Math.floor(n) === n && isFinite(val)
+		};
+	});
+
+	var tools_7 = tools.isFunc;
+	var tools_10 = tools.isPlainObject;
+
+	var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
+
+	function observer() {
+		var self = this;
+
+		self.on = function(event, fn) {
+			if (EVENT_TYPE.indexOf(event) > -1) {
+				if (tools_7(fn)) {
+					event === 'ready' ?
+						fn(self) :
+						self[("on" + (firstLetterUpper(event)))] = fn;
+				}
+			} else {
+				console.error(("event: " + event + " is invalid"));
+			}
+			return self
+		};
+	}
+
+	function wxPromise(fn) {
+		return function(obj) {
+			var args = [],
+				len = arguments.length - 1;
+			while (len-- > 0) args[len] = arguments[len + 1];
+
+			if (obj === void 0) obj = {};
+			return new Promise(function(resolve, reject) {
+				obj.success = function(res) {
+					resolve(res);
+				};
+				obj.fail = function(err) {
+					reject(err);
+				};
+				fn.apply(void 0, [obj].concat(args));
+			})
+		}
+	}
+
+	function draw(ctx, reserve) {
+		if (reserve === void 0) reserve = false;
+
+		return new Promise(function(resolve) {
+			ctx.draw(reserve, resolve);
+		})
+	}
+
+	var getImageInfo = wxPromise(uni.getImageInfo);
+
+	var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath);
+
+	var base64 = createCommonjsModule(function(module, exports) {
+		/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
+		(function(root) {
+
+			// Detect free variables `exports`.
+			var freeExports = 'object' == 'object' && exports;
+
+			// Detect free variable `module`.
+			var freeModule = 'object' == 'object' && module &&
+				module.exports == freeExports && module;
+
+			// Detect free variable `global`, from Node.js or Browserified code, and use
+			// it as `root`.
+			var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
+			if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+				root = freeGlobal;
+			}
+
+			/*--------------------------------------------------------------------------*/
+
+			var InvalidCharacterError = function(message) {
+				this.message = message;
+			};
+			InvalidCharacterError.prototype = new Error;
+			InvalidCharacterError.prototype.name = 'InvalidCharacterError';
+
+			var error = function(message) {
+				// Note: the error messages used throughout this file match those used by
+				// the native `atob`/`btoa` implementation in Chromium.
+				throw new InvalidCharacterError(message);
+			};
+
+			var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+			// http://whatwg.org/html/common-microsyntaxes.html#space-character
+			var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;
+
+			// `decode` is designed to be fully compatible with `atob` as described in the
+			// HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
+			// The optimized base64-decoding algorithm used is based on @atk’s excellent
+			// implementation. https://gist.github.com/atk/1020396
+			var decode = function(input) {
+				input = String(input)
+					.replace(REGEX_SPACE_CHARACTERS, '');
+				var length = input.length;
+				if (length % 4 == 0) {
+					input = input.replace(/==?$/, '');
+					length = input.length;
+				}
+				if (
+					length % 4 == 1 ||
+					// http://whatwg.org/C#alphanumeric-ascii-characters
+					/[^+a-zA-Z0-9/]/.test(input)
+				) {
+					error(
+						'Invalid character: the string to be decoded is not correctly encoded.'
+					);
+				}
+				var bitCounter = 0;
+				var bitStorage;
+				var buffer;
+				var output = '';
+				var position = -1;
+				while (++position < length) {
+					buffer = TABLE.indexOf(input.charAt(position));
+					bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
+					// Unless this is the first of a group of 4 characters…
+					if (bitCounter++ % 4) {
+						// …convert the first 8 bits to a single ASCII character.
+						output += String.fromCharCode(
+							0xFF & bitStorage >> (-2 * bitCounter & 6)
+						);
+					}
+				}
+				return output;
+			};
+
+			// `encode` is designed to be fully compatible with `btoa` as described in the
+			// HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
+			var encode = function(input) {
+				input = String(input);
+				if (/[^\0-\xFF]/.test(input)) {
+					// Note: no need to special-case astral symbols here, as surrogates are
+					// matched, and the input is supposed to only contain ASCII anyway.
+					error(
+						'The string to be encoded contains characters outside of the ' +
+						'Latin1 range.'
+					);
+				}
+				var padding = input.length % 3;
+				var output = '';
+				var position = -1;
+				var a;
+				var b;
+				var c;
+				var buffer;
+				// Make sure any padding is handled outside of the loop.
+				var length = input.length - padding;
+
+				while (++position < length) {
+					// Read three bytes, i.e. 24 bits.
+					a = input.charCodeAt(position) << 16;
+					b = input.charCodeAt(++position) << 8;
+					c = input.charCodeAt(++position);
+					buffer = a + b + c;
+					// Turn the 24 bits into four chunks of 6 bits each, and append the
+					// matching character for each of them to the output.
+					output += (
+						TABLE.charAt(buffer >> 18 & 0x3F) +
+						TABLE.charAt(buffer >> 12 & 0x3F) +
+						TABLE.charAt(buffer >> 6 & 0x3F) +
+						TABLE.charAt(buffer & 0x3F)
+					);
+				}
+
+				if (padding == 2) {
+					a = input.charCodeAt(position) << 8;
+					b = input.charCodeAt(++position);
+					buffer = a + b;
+					output += (
+						TABLE.charAt(buffer >> 10) +
+						TABLE.charAt((buffer >> 4) & 0x3F) +
+						TABLE.charAt((buffer << 2) & 0x3F) +
+						'='
+					);
+				} else if (padding == 1) {
+					buffer = input.charCodeAt(position);
+					output += (
+						TABLE.charAt(buffer >> 2) +
+						TABLE.charAt((buffer << 4) & 0x3F) +
+						'=='
+					);
+				}
+
+				return output;
+			};
+
+			var base64 = {
+				'encode': encode,
+				'decode': decode,
+				'version': '0.1.0'
+			};
+
+			// Some AMD build optimizers, like r.js, check for specific condition patterns
+			// like the following:
+			if (
+				typeof undefined == 'function' &&
+				typeof undefined.amd == 'object' &&
+				undefined.amd
+			) {
+				undefined(function() {
+					return base64;
+				});
+			} else if (freeExports && !freeExports.nodeType) {
+				if (freeModule) { // in Node.js or RingoJS v0.8.0+
+					freeModule.exports = base64;
+				} else { // in Narwhal or RingoJS v0.7.0-
+					for (var key in base64) {
+						base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
+					}
+				}
+			} else { // in Rhino or a web browser
+				root.base64 = base64;
+			}
+
+		}(commonjsGlobal));
+	});
+
+	function makeURI(strData, type) {
+		return 'data:' + type + ';base64,' + strData
+	}
+
+	function fixType(type) {
+		type = type.toLowerCase().replace(/jpg/i, 'jpeg');
+		var r = type.match(/png|jpeg|bmp|gif/)[0];
+		return 'image/' + r
+	}
+
+	function encodeData(data) {
+		var str = '';
+		if (typeof data === 'string') {
+			str = data;
+		} else {
+			for (var i = 0; i < data.length; i++) {
+				str += String.fromCharCode(data[i]);
+			}
+		}
+		return base64.encode(str)
+	}
+
+	/**
+	 * 获取图像区域隐含的像素数据
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param done 完成回调
+	 */
+	function getImageData(canvasId, x, y, width, height, done) {
+		uni.canvasGetImageData({
+			canvasId: canvasId,
+			x: x,
+			y: y,
+			width: width,
+			height: height,
+			success: function success(res) {
+				done(res, null);
+			},
+			fail: function fail(res) {
+				done(null, res);
+			}
+		});
+	}
+
+	/**
+	 * 生成bmp格式图片
+	 * 按照规则生成图片响应头和响应体
+	 * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
+	 * @returns {*} base64字符串
+	 */
+	function genBitmapImage(oData) {
+		//
+		// BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
+		// BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
+		//
+		var biWidth = oData.width;
+		var biHeight = oData.height;
+		var biSizeImage = biWidth * biHeight * 3;
+		var bfSize = biSizeImage + 54; // total header size = 54 bytes
+
+		//
+		//  typedef struct tagBITMAPFILEHEADER {
+		//  	WORD bfType;
+		//  	DWORD bfSize;
+		//  	WORD bfReserved1;
+		//  	WORD bfReserved2;
+		//  	DWORD bfOffBits;
+		//  } BITMAPFILEHEADER;
+		//
+		var BITMAPFILEHEADER = [
+			// WORD bfType -- The file type signature; must be "BM"
+			0x42, 0x4D,
+			// DWORD bfSize -- The size, in bytes, of the bitmap file
+			bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
+			// WORD bfReserved1 -- Reserved; must be zero
+			0, 0,
+			// WORD bfReserved2 -- Reserved; must be zero
+			0, 0,
+			// DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
+			54, 0, 0, 0
+		];
+
+		//
+		//  typedef struct tagBITMAPINFOHEADER {
+		//  	DWORD biSize;
+		//  	LONG  biWidth;
+		//  	LONG  biHeight;
+		//  	WORD  biPlanes;
+		//  	WORD  biBitCount;
+		//  	DWORD biCompression;
+		//  	DWORD biSizeImage;
+		//  	LONG  biXPelsPerMeter;
+		//  	LONG  biYPelsPerMeter;
+		//  	DWORD biClrUsed;
+		//  	DWORD biClrImportant;
+		//  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
+		//
+		var BITMAPINFOHEADER = [
+			// DWORD biSize -- The number of bytes required by the structure
+			40, 0, 0, 0,
+			// LONG biWidth -- The width of the bitmap, in pixels
+			biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
+			// LONG biHeight -- The height of the bitmap, in pixels
+			biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
+			// WORD biPlanes -- The number of planes for the target device. This value must be set to 1
+			1, 0,
+			// WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
+			// has a maximum of 2^24 colors (16777216, Truecolor)
+			24, 0,
+			// DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
+			0, 0, 0, 0,
+			// DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
+			biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
+			// LONG biXPelsPerMeter, unused
+			0, 0, 0, 0,
+			// LONG biYPelsPerMeter, unused
+			0, 0, 0, 0,
+			// DWORD biClrUsed, the number of color indexes of palette, unused
+			0, 0, 0, 0,
+			// DWORD biClrImportant, unused
+			0, 0, 0, 0
+		];
+
+		var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
+
+		var aImgData = oData.data;
+
+		var strPixelData = '';
+		var biWidth4 = biWidth << 2;
+		var y = biHeight;
+		var fromCharCode = String.fromCharCode;
+
+		do {
+			var iOffsetY = biWidth4 * (y - 1);
+			var strPixelRow = '';
+			for (var x = 0; x < biWidth; x++) {
+				var iOffsetX = x << 2;
+				strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX]);
+			}
+
+			for (var c = 0; c < iPadding; c++) {
+				strPixelRow += String.fromCharCode(0);
+			}
+
+			strPixelData += strPixelRow;
+		} while (--y)
+
+		var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
+
+		return strEncoded
+	}
+
+	/**
+	 * 转换为图片base64
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param type 转换图片类型
+	 * @param done 完成回调
+	 */
+	function convertToImage(canvasId, x, y, width, height, type, done) {
+		if (done === void 0) done = function() {};
+
+		if (type === undefined) {
+			type = 'png';
+		}
+		type = fixType(type);
+		if (/bmp/.test(type)) {
+			getImageData(canvasId, x, y, width, height, function(data, err) {
+				var strData = genBitmapImage(data);
+				tools_7(done) && done(makeURI(strData, 'image/' + type), err);
+			});
+		} else {
+			console.error('暂不支持生成\'' + type + '\'类型的base64图片');
+		}
+	}
+
+	var CanvasToBase64 = {
+		convertToImage: convertToImage,
+		// convertToPNG: function (width, height, done) {
+		//   return convertToImage(width, height, 'png', done)
+		// },
+		// convertToJPEG: function (width, height, done) {
+		//   return convertToImage(width, height, 'jpeg', done)
+		// },
+		// convertToGIF: function (width, height, done) {
+		//   return convertToImage(width, height, 'gif', done)
+		// },
+		convertToBMP: function(ref, done) {
+			if (ref === void 0) ref = {};
+			var canvasId = ref.canvasId;
+			var x = ref.x;
+			var y = ref.y;
+			var width = ref.width;
+			var height = ref.height;
+			if (done === void 0) done = function() {};
+
+			return convertToImage(canvasId, x, y, width, height, 'bmp', done)
+		}
+	};
+
+	function methods() {
+		var self = this;
+
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
+
+		var id = self.id;
+		var targetId = self.targetId;
+		var pixelRatio = self.pixelRatio;
+
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		self.updateCanvas = function(done) {
+			if (self.croperTarget) {
+				//  画布绘制图片
+				self.ctx.drawImage(
+					self.croperTarget,
+					self.imgLeft,
+					self.imgTop,
+					self.scaleWidth,
+					self.scaleHeight
+				);
+			}
+			tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
+
+			self.setBoundStyle(self.boundStyle); //	设置边界样式
+
+			self.ctx.draw(false, done);
+			return self
+		};
+
+		self.pushOrigin = self.pushOrign = function(src) {
+			self.src = src;
+
+			tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
+
+			return getImageInfo({
+					src: src
+				})
+				.then(function(res) {
+					var innerAspectRadio = res.width / res.height;
+					var customAspectRadio = width / height;
+
+					self.croperTarget = res.path;
+
+					if (innerAspectRadio < customAspectRadio) {
+						self.rectX = x;
+						self.baseWidth = width;
+						self.baseHeight = width / innerAspectRadio;
+						self.rectY = y - Math.abs((height - self.baseHeight) / 2);
+					} else {
+						self.rectY = y;
+						self.baseWidth = height * innerAspectRadio;
+						self.baseHeight = height;
+						self.rectX = x - Math.abs((width - self.baseWidth) / 2);
+					}
+
+					self.imgLeft = self.rectX;
+					self.imgTop = self.rectY;
+					self.scaleWidth = self.baseWidth;
+					self.scaleHeight = self.baseHeight;
+
+					self.update();
+
+					return new Promise(function(resolve) {
+						self.updateCanvas(resolve);
+					})
+				})
+				.then(function() {
+					tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self);
+				})
+		};
+
+		self.removeImage = function() {
+			self.src = '';
+			self.croperTarget = '';
+			return draw(self.ctx)
+		};
+
+		self.getCropperBase64 = function(done) {
+			if (done === void 0) done = function() {};
+
+			CanvasToBase64.convertToBMP({
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			}, done);
+		};
+
+		self.getCropperImage = function(opt, fn) {
+			var customOptions = opt;
+
+			var canvasOptions = {
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			};
+
+			var task = function() {
+				return Promise.resolve();
+			};
+
+			if (
+				tools_10(customOptions) &&
+				customOptions.original
+			) {
+				// original mode
+				task = function() {
+					self.targetCtx.drawImage(
+						self.croperTarget,
+						self.imgLeft * pixelRatio,
+						self.imgTop * pixelRatio,
+						self.scaleWidth * pixelRatio,
+						self.scaleHeight * pixelRatio
+					);
+
+					canvasOptions = {
+						canvasId: targetId,
+						x: x * pixelRatio,
+						y: y * pixelRatio,
+						width: width * pixelRatio,
+						height: height * pixelRatio
+					};
+
+					return draw(self.targetCtx)
+				};
+			}
+
+			return task()
+				.then(function() {
+					if (tools_10(customOptions)) {
+						canvasOptions = Object.assign({}, canvasOptions, customOptions);
+					}
+
+					if (tools_7(customOptions)) {
+						fn = customOptions;
+					}
+
+					var arg = canvasOptions.componentContext ?
+						[canvasOptions, canvasOptions.componentContext] :
+						[canvasOptions];
+
+					return canvasToTempFilePath.apply(null, arg)
+				})
+				.then(function(res) {
+					var tempFilePath = res.tempFilePath;
+
+					return tools_7(fn) ?
+						fn.call(self, tempFilePath, null) :
+						tempFilePath
+				})
+				.catch(function(err) {
+					if (tools_7(fn)) {
+						fn.call(self, null, err);
+					} else {
+						throw err
+					}
+				})
+		};
+	}
+
+	/**
+	 * 获取最新缩放值
+	 * @param oldScale 上一次触摸结束后的缩放值
+	 * @param oldDistance 上一次触摸结束后的双指距离
+	 * @param zoom 缩放系数
+	 * @param touch0 第一指touch对象
+	 * @param touch1 第二指touch对象
+	 * @returns {*}
+	 */
+	var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) {
+		var xMove, yMove, newDistance;
+		// 计算二指最新距离
+		xMove = Math.round(touch1.x - touch0.x);
+		yMove = Math.round(touch1.y - touch0.y);
+		newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+		return oldScale + 0.001 * zoom * (newDistance - oldDistance)
+	};
+
+	function update() {
+		var self = this;
+
+		if (!self.src) {
+			return
+		}
+
+		self.__oneTouchStart = function(touch) {
+			self.touchX0 = Math.round(touch.x);
+			self.touchY0 = Math.round(touch.y);
+		};
+
+		self.__oneTouchMove = function(touch) {
+			var xMove, yMove;
+			// 计算单指移动的距离
+			if (self.touchended) {
+				return self.updateCanvas()
+			}
+			xMove = Math.round(touch.x - self.touchX0);
+			yMove = Math.round(touch.y - self.touchY0);
+
+			var imgLeft = Math.round(self.rectX + xMove);
+			var imgTop = Math.round(self.rectY + yMove);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__twoTouchStart = function(touch0, touch1) {
+			var xMove, yMove, oldDistance;
+
+			self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
+			self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
+
+			// 计算两指距离
+			xMove = Math.round(touch1.x - touch0.x);
+			yMove = Math.round(touch1.y - touch0.y);
+			oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+			self.oldDistance = oldDistance;
+		};
+
+		self.__twoTouchMove = function(touch0, touch1) {
+			var oldScale = self.oldScale;
+			var oldDistance = self.oldDistance;
+			var scale = self.scale;
+			var zoom = self.zoom;
+
+			self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
+
+			//  设定缩放范围
+			self.newScale <= 1 && (self.newScale = 1);
+			self.newScale >= scale && (self.newScale = scale);
+
+			self.scaleWidth = Math.round(self.newScale * self.baseWidth);
+			self.scaleHeight = Math.round(self.newScale * self.baseHeight);
+			var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
+			var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__xtouchEnd = function() {
+			self.oldScale = self.newScale;
+			self.rectX = self.imgLeft;
+			self.rectY = self.imgTop;
+		};
+	}
+
+	var handle = {
+		//  图片手势初始监测
+		touchStart: function touchStart(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, true, null, null);
+
+			// 计算第一个触摸点的位置,并参照改点进行缩放
+			self.__oneTouchStart(touch0);
+
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchStart(touch0, touch1);
+			}
+		},
+
+		//  图片手势动态缩放
+		touchMove: function touchMove(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, null, true);
+
+			// 单指手势时触发
+			if (e.touches.length === 1) {
+				self.__oneTouchMove(touch0);
+			}
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchMove(touch0, touch1);
+			}
+		},
+
+		touchEnd: function touchEnd(e) {
+			var self = this;
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, false, false, true);
+			self.__xtouchEnd();
+		}
+	};
+
+	function cut() {
+		var self = this;
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height;
+		// 裁剪框默认高度,即整个画布高度
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		/**
+		 * 设置边界
+		 * @param imgLeft 图片左上角横坐标值
+		 * @param imgTop 图片左上角纵坐标值
+		 */
+		self.outsideBound = function(imgLeft, imgTop) {
+			self.imgLeft = imgLeft >= x ?
+				x :
+				self.scaleWidth + imgLeft - x <= width ?
+				x + width - self.scaleWidth :
+				imgLeft;
+
+			self.imgTop = imgTop >= y ?
+				y :
+				self.scaleHeight + imgTop - y <= height ?
+				y + height - self.scaleHeight :
+				imgTop;
+		};
+
+		/**
+		 * 设置边界样式
+		 * @param color	边界颜色
+		 */
+		self.setBoundStyle = function(ref) {
+			if (ref === void 0) ref = {};
+			var color = ref.color;
+			if (color === void 0) color = '#04b00f';
+			var mask = ref.mask;
+			if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)';
+			var lineWidth = ref.lineWidth;
+			if (lineWidth === void 0) lineWidth = 1;
+
+			var half = lineWidth / 2;
+			var boundOption = [{
+					start: {
+						x: x - half,
+						y: y + 10 - half
+					},
+					step1: {
+						x: x - half,
+						y: y - half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y - half
+					}
+				},
+				{
+					start: {
+						x: x - half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x - half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y + height + half
+					}
+				},
+				{
+					start: {
+						x: x + width - 10 + half,
+						y: y - half
+					},
+					step1: {
+						x: x + width + half,
+						y: y - half
+					},
+					step2: {
+						x: x + width + half,
+						y: y + 10 - half
+					}
+				},
+				{
+					start: {
+						x: x + width + half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x + width + half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + width - 10 + half,
+						y: y + height + half
+					}
+				}
+			];
+
+			// 绘制半透明层
+			self.ctx.beginPath();
+			self.ctx.setFillStyle(mask);
+			self.ctx.fillRect(0, 0, x, boundHeight);
+			self.ctx.fillRect(x, 0, width, y);
+			self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
+			self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
+			self.ctx.fill();
+
+			boundOption.forEach(function(op) {
+				self.ctx.beginPath();
+				self.ctx.setStrokeStyle(color);
+				self.ctx.setLineWidth(lineWidth);
+				self.ctx.moveTo(op.start.x, op.start.y);
+				self.ctx.lineTo(op.step1.x, op.step1.y);
+				self.ctx.lineTo(op.step2.x, op.step2.y);
+				self.ctx.stroke();
+			});
+		};
+	}
+
+	var version = "1.3.9";
+
+	var WeCropper = function WeCropper(params) {
+		var self = this;
+		var _default = {};
+
+		validator(self, DEFAULT);
+
+		Object.keys(DEFAULT).forEach(function(key) {
+			_default[key] = DEFAULT[key].default;
+		});
+		Object.assign(self, _default, params);
+
+		self.prepare();
+		self.attachPage();
+		self.createCtx();
+		self.observer();
+		self.cutt();
+		self.methods();
+		self.init();
+		self.update();
+
+		return self
+	};
+
+	WeCropper.prototype.init = function init() {
+		var self = this;
+		var src = self.src;
+
+		self.version = version;
+
+		typeof self.onReady === 'function' && self.onReady(self.ctx, self);
+
+		if (src) {
+			self.pushOrign(src);
+		} else {
+			self.updateCanvas();
+		}
+		setTouchState(self, false, false, false);
+
+		self.oldScale = 1;
+		self.newScale = 1;
+
+		return self
+	};
+
+	Object.assign(WeCropper.prototype, handle);
+
+	WeCropper.prototype.prepare = prepare;
+	WeCropper.prototype.observer = observer;
+	WeCropper.prototype.methods = methods;
+	WeCropper.prototype.cutt = cut;
+	WeCropper.prototype.update = update;
+
+	return WeCropper;
+
+})));

+ 21 - 5
app/pages.json

@@ -142,7 +142,8 @@
 			"path" : "pages/user/loginDoctor",
 			"style" : 
 			{
-				"navigationBarTitleText" : "医生登录"
+				"navigationBarTitleText" : "医生登录",
+				"navigationStyle": "custom"
 			}
 		},
 		{
@@ -181,15 +182,30 @@
 			}
 		},
 		{
-			"path": "pages/visit/doctor/selectUser",
+			"path": "components/u-avatar-cropper/u-avatar-cropper",
 			"style": {
-				"navigationBarTitleText": "选择患者"
+				"navigationBarTitleText": "图片裁剪",
+				"navigationBarBackgroundColor": "#000000"
 			}
 		},
 		{
-			"path": "pages/visit/doctor/list",
+			"path": "pages/user/pass",
 			"style": {
-				"navigationBarTitleText": "患者就诊记录"
+				"navigationBarTitleText": "修改密码"
+			}
+		},
+		{
+			"path" : "pages/notice/index",
+			"style" : 
+			{
+				"navigationBarTitleText" : "通知公告"
+			}
+		},
+		{
+			"path" : "pages/notice/detail",
+			"style" : 
+			{
+				"navigationBarTitleText" : "公告详情"
 			}
 		}
 	],

+ 20 - 7
app/pages/follow/detail.vue

@@ -1,7 +1,11 @@
 <template>
 	<view class="main">
-		<view class="title">{{ item.templateName }}</view>
-		<u-divider text="开始"></u-divider>
+		<view class="desc">
+			<view class="item">科室:{{ item.deptName }}</view>
+			<view class="item">医生:{{ item.doctorName }}</view>
+			<view class="item">日期:{{ item.createTime }}</view>
+		</view>
+		<u-divider text="随访内容"></u-divider>
 		<view class="item" v-for="(item, index) in item.op" :key="item.name">
 			<view class="mtt">
 				<text class="ifnull" v-if="item.ifnull == '必填'">*</text>
@@ -20,7 +24,6 @@
 				</view>
 			</view>
 		</view>
-		<u-divider text="结束"></u-divider>
 		<button class="btn" @click="add()" v-if="!look">立即提交</button>
 	</view>
 </template>
@@ -40,10 +43,15 @@ export default {
 				this.item = res.data.data;
 				if (this.getUser().doctor) {
 					this.look = true;
+					this.item.deptName = this.getUser().dept.deptName;
+					this.item.doctorName = this.getUser().nickName;
 				} else {
+					this.item.deptName = e.deptName;
+					this.item.doctorName = e.doctorName;
 					this.look = this.item.state != 0 ? true : false;
 				}
 				this.item.op = JSON.parse(this.item.op);
+				uni.setNavigationBarTitle({ title: this.item.templateName });
 			}
 		});
 	},
@@ -102,10 +110,15 @@ export default {
 page {
 	background-color: white;
 }
-.title {
-	text-align: center;
-	font-weight: bold;
-	padding-bottom: 10px;
+.desc {
+	background-color: $bg;
+	padding: 10px;
+	border-radius: 7px;
+	.item {
+		padding-top: 5px;
+		font-size: 15px;
+		color: $font-c;
+	}
 }
 .item {
 	.mtt {

+ 1 - 0
app/pages/follow/doctor/index.vue

@@ -15,6 +15,7 @@
 					<text class="del" @click.stop="del(item, index)">删除</text>
 					<text class="edit" @click.stop="go('/pages/follow/doctor/add?id=' + item.id + ' &patientName=' + item.patientName)">编辑</text>
 					<text class="icon state" :style="{ color: item.state == 0 ? '#b5b5b5' : '#4CAF50' }" v-if="param.type == 1">&#xe830;{{ item.state == 0 ? '待回访' : '已回访' }}</text>
+					<text class="icon state" :style="{ color: item.state == 0 ? '#b5b5b5' : '#4CAF50' }" v-else>&#xe830;{{ item.state == 0 ? '消息未读' : '已读' }}</text>
 				</view>
 			</view>
 			<view class="loading" v-if="loadMore"><u-loadmore :status="loadMore ? 'loading' : 'nomore'" /></view>

+ 1 - 1
app/pages/follow/index.vue

@@ -4,7 +4,7 @@
 			<u-tabs :list="tab" @click="click"></u-tabs>
 		</view>
 		<view class="list">
-			<view class="message-bubble-received" v-for="(item, index) in list" :key="index" @click="go('/pages/follow/detail?id=' + item.id)">
+			<view class="message-bubble-received" v-for="(item, index) in list" :key="index" @click="go('/pages/follow/detail?id=' + item.id + '&deptName=' + item.deptName + '&doctorName=' + item.doctorName)">
 				<view class="title">{{ item.templateName }}</view>
 				<view class="desc">尊敬的患者,为了更好的改善服务,我们向你发起了一个回访并且希望您认真填写。</view>
 				<view class="desc">{{ item.createTime }}</view>

+ 49 - 3
app/pages/follow/remind.vue

@@ -1,17 +1,41 @@
 <template>
 	<view>
 		<view class="list">
-			<view class="message-bubble-received" v-for="(item, index) in list" :key="index">
+			<view class="message-bubble-received" v-for="(item, index) in list" :key="index" @click="detail(item)">
 				<view class="title">
 					<text class="icon">&#xe813;</text>
 					<text>{{ item.templateName }}</text>
 				</view>
 				<view class="desc">{{ item.op }}</view>
-				<view class="desc">{{ item.createTime }}</view>
+				<view class="desc">
+					<text>{{ item.createTime }}</text>
+					<text class="look">查看详情</text>
+				</view>
 			</view>
 			<view class="loading" v-if="loadMore"><u-loadmore :status="loadMore ? 'loading' : 'nomore'" /></view>
 			<u-empty v-if="!loadMore && list.length == 0"></u-empty>
 		</view>
+		<u-popup :show="show" round="15" mode="center" :closeable="true" :customStyle="{ width: '95%' }" @close="show = false">
+			<view class="popup">
+				<view class="mtt">{{ item.templateName }}</view>
+				<view class="item">
+					<text class="tt">科室名称</text>
+					<text class="la">{{ item.deptName }}</text>
+				</view>
+				<view class="item">
+					<text class="tt">医生姓名</text>
+					<text class="la">{{ item.doctorName }}</text>
+				</view>
+				<view class="item">
+					<text class="tt">复诊内容</text>
+					<text class="la">{{ item.op }}</text>
+				</view>
+				<view class="item">
+					<text class="tt">提醒日期</text>
+					<text class="la">{{ item.createTime }}</text>
+				</view>
+			</view>
+		</u-popup>
 	</view>
 </template>
 
@@ -21,7 +45,9 @@ export default {
 		return {
 			list: [],
 			param: { pageNum: 1, pageSize: 10, type: 0 },
-			loadMore: true
+			loadMore: true,
+			show: false,
+			item: {}
 		};
 	},
 	onLoad(e) {
@@ -39,6 +65,21 @@ export default {
 				}
 			});
 		},
+		detail(item) {
+			this.item = item;
+			this.show = true;
+			//患者已查看
+			if (item.state == 0) {
+				this.http.request({
+					url: '/app/follow/read/' + item.id,
+					method: 'POST',
+					data: this.item,
+					success: (res) => {
+						item.state = 1;
+					}
+				});
+			}
+		},
 		//刷新数据
 		refresh() {
 			this.loadMore = true;
@@ -79,6 +120,11 @@ export default {
 			font-size: 13px;
 			padding-top: 10px;
 			color: $font-c;
+			.look {
+				float: right;
+				margin-top: 0px;
+				color: $main-color;
+			}
 		}
 	}
 }

+ 11 - 14
app/pages/index/index.vue

@@ -49,7 +49,7 @@
 			</view>
 		</view>
 		<!--权威专家-->
-		<view class="bos">
+		<view class="bos" style="margin-top: 8px" v-if="expertList.length > 0">
 			<view class="gg">
 				<view class="title">权威专家</view>
 				<view class="bor"></view>
@@ -60,9 +60,9 @@
 			</view>
 			<u-scroll-list :indicator="true" indicatorColor="#e0e0e0">
 				<view class="doctor">
-					<view class="item" v-for="(item, index) in doctorList" :key="index" @click="go('/pages/news/detail?id=' + item.id)">
-						<image :src="item.avatar" mode="scaleToFill" class="avatar"></image>
-						<view class="title">{{ item.name }}</view>
+					<view class="item" v-for="(item, index) in expertList" :key="index" @click="go('/pages/doctor/detail?id=' + item.userId)">
+						<image :src="ip + item.avatar" mode="scaleToFill" class="avatar"></image>
+						<view class="title">{{ item.nickName }}</view>
 					</view>
 				</view>
 			</u-scroll-list>
@@ -97,16 +97,8 @@ export default {
 	data() {
 		return {
 			ip: this.http.ip,
-			noticeList: ['医院开通线上小程序啦,快来体验吧', '首次使用如何绑定就诊卡'],
-			doctorList: [
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' },
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' },
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' },
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' },
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' },
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' },
-				{ name: '梁红斌', avatar: 'https://chenglantimes.com/prod-api/profile/upload/2024/08/05/1722848767545.jpg' }
-			],
+			noticeList: [],
+			expertList: [],
 			knowledgeList: []
 		};
 	},
@@ -119,6 +111,8 @@ export default {
 				url: '/app/home/index',
 				success: (res) => {
 					this.knowledgeList = res.data.data.knowledgeList;
+					this.expertList = res.data.data.expertList;
+					this.noticeList = res.data.data.noticeList.map((item) => item.title);
 				}
 			});
 		},
@@ -213,6 +207,9 @@ page {
 	border-radius: 7px;
 	overflow: hidden;
 	padding: 12px 0px 0px 0px;
+	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15);
+	padding: 12px;
+	margin-top: 12px;
 	.gg {
 		font-weight: bold;
 		margin-bottom: 5px;

+ 87 - 0
app/pages/notice/detail.vue

@@ -0,0 +1,87 @@
+<template>
+	<view class="main">
+		<view class="content">
+			<view class="title">{{ item.title }}</view>
+			<view class="desc">
+				<text class="time">发布于:{{ item.createTime }}</text>
+				<button class="fx" open-type="share">
+					<text class="icon">&#xe637;</text>
+					<text>分享</text>
+				</button>
+			</view>
+			<view class="con">
+				<u-divider :dot="true"></u-divider>
+				<u-parse :content="item.contents"></u-parse>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			item: {}
+		};
+	},
+	onLoad(e) {
+		this.http.request({
+			url: '/app/notice/detail/' + e.id,
+			success: (res) => {
+				this.item = res.data.data;
+				this.item.contents = res.data.data.contents.replace(new RegExp('/profile/upload/', 'g'), this.http.ip + '/profile/upload/');
+			}
+		});
+	},
+	methods: {},
+	//分享
+	onShareAppMessage: function (res) {
+		return {
+			title: this.item.title,
+			path: '/pages/notice/detail?id=' + this.item.id,
+			success: (res) => {},
+			fail: (res) => {}
+		};
+	}
+};
+</script>
+
+<style lang="scss">
+.main {
+	padding: 0px 10px 10px 10px;
+}
+.content {
+	padding: 15px;
+	background-color: white;
+	border-radius: 8px;
+	.title {
+		font-size: 18px;
+	}
+	.desc {
+		padding-top: 15px;
+		font-size: 13px;
+		color: #7c8388;
+		.time {
+			padding-right: 25px;
+		}
+		.fx {
+			background-color: white;
+			float: right;
+			font-size: 15px;
+			margin-right: -14px;
+			margin-top: -13px;
+			color: $main-color;
+			.icon{
+				padding-right: 3px;
+			}
+		}
+	}
+	.con {
+		font-size: 14px;
+		line-height: 23px;
+		image {
+			border-radius: 5px !important;
+		}
+	}
+}
+</style>

+ 98 - 0
app/pages/notice/index.vue

@@ -0,0 +1,98 @@
+<template>
+	<view class="list">
+		<view class="item" v-for="(item, index) in list" :key="index" @click="go('/pages/notice/detail?id=' + item.id)">
+			<view class="title omit">
+				<text class="icon" v-if="item.top === 1">&#xe620;</text>
+				<text>{{ item.title }}</text>
+			</view>
+			<view class="desc">
+				<text>发布于 {{ item.createTime }}</text>
+			</view>
+		</view>
+		<view class="loading" v-if="loadMore"><u-loadmore :status="loadMore ? 'loading' : 'nomore'" /></view>
+		<u-empty v-if="!loadMore && list.length == 0"></u-empty>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			list: [],
+			param: { pageNum: 1, pageSize: 10, type: '小程序通知' },
+			loadMore: true
+		};
+	},
+	onLoad(e) {
+		this.getData();
+	},
+	methods: {
+		getData() {
+			this.http.request({
+				url: '/app/notice/list',
+				data: this.param,
+				loading: 'false',
+				success: (res) => {
+					this.loadMore = res.data.pages > this.param.pageNum ? true : false;
+					res.data.rows.forEach((item) => {
+						item.createTime = uni.$u.timeFrom(Date.parse(item.createTime));
+						this.list.push(item);
+					});
+				}
+			});
+		},
+		go(url) {
+			uni.navigateTo({ url: url });
+		},
+		//刷新数据
+		refresh() {
+			this.loadMore = true;
+			this.param.pageNum = 1;
+			this.list = [];
+			this.getData();
+		}
+	},
+	//下拉刷新
+	onPullDownRefresh() {
+		setTimeout(() => {
+			this.refresh();
+			uni.stopPullDownRefresh();
+		}, 1000);
+	},
+	//上拉加载
+	onReachBottom() {
+		if (this.loadMore) {
+			this.param.pageNum++;
+			this.getData();
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.list {
+	padding: 5px 12px 12px 12px;
+	.item {
+		background-color: white;
+		border-radius: 5px;
+		padding: 12px;
+		margin-bottom: 10px;
+		.title {
+			font-size: 15px;
+			font-weight: bold;
+			.icon {
+				color: orangered;
+				padding-right: 3px;
+			}
+		}
+		.desc {
+			font-size: 14px;
+			color: $font-c;
+			padding-top: 7px;
+			text {
+				padding-right: 20px;
+			}
+		}
+	}
+}
+</style>

+ 24 - 2
app/pages/other/setting.vue

@@ -1,5 +1,22 @@
 <template>
-	<view class="main pt0">
+	<view class="main pdt0">
+		<view class="cmd pdt0">
+			<view class="s_item" @click="go('/pages/user/info')" v-if="user.doctor">
+				<text class="icon ic" style="color: #9e9e9e">&#xe616;</text>
+				<text class="title">个人信息</text>
+				<text class="icon arrow">&#xe62b;</text>
+			</view>
+			<view class="s_item" @click="go('/pages/user/bind/index')" v-if="!user.doctor">
+				<text class="icon ic" style="color: #9e9e9e">&#xe62d;</text>
+				<text class="title">绑定就诊人</text>
+				<text class="icon arrow">&#xe62b;</text>
+			</view>
+			<view class="s_item" @click="go('/pages/user/pass')" v-if="user.doctor">
+				<text class="icon ic" style="color: #4caf50">&#xe613;</text>
+				<text class="title">修改密码</text>
+				<text class="icon arrow">&#xe62b;</text>
+			</view>
+		</view>
 		<button class="btn" @click="exit()">退出登陆</button>
 	</view>
 </template>
@@ -7,9 +24,14 @@
 <script>
 export default {
 	data() {
-		return {};
+		return {
+			user: this.getUser()
+		};
 	},
 	methods: {
+		go(url) {
+			uni.navigateTo({ url: url });
+		},
 		exit() {
 			uni.showModal({
 				title: '提示',

+ 12 - 5
app/pages/user/index.vue

@@ -1,13 +1,13 @@
 <template>
 	<view class="main">
-		<view class="user">
-			<image :src="user.avatar ? ip + user.avatar : '../../static/favicon.png'"></image>
+		<view class="user" @click="go(user.doctor ? '/pages/user/info' : '/pages/user/bind/index')">
+			<image :src="user.avatar ? ip + user.avatar : '../../static/favicon.png'" :class="user.avatar ? 'head' : ''"></image>
 			<view class="con" v-if="user.id || user.token">
-				<view v-if="user.doctor" @click="go('/pages/user/info')">
+				<view v-if="user.doctor">
 					<view class="nickName">{{ user.nickName }}</view>
 					<view class="welcome">{{ user.dept.deptName || '欢迎使用岑溪人民医院小程序' }}</view>
 				</view>
-				<view v-else @click="go('/pages/user/bind/index')">
+				<view v-else>
 					<view class="nickName">
 						<text>{{ user.patientName ? user.patientName : '还未绑定就诊人' }}</text>
 						<text class="icon" v-if="user.bindUserList && user.bindUserList.length > 1" @click.stop="show = true">&#xe6a7;切换就诊人</text>
@@ -15,7 +15,7 @@
 					<view class="welcome">欢迎使用岑溪人民医院小程序</view>
 				</view>
 			</view>
-			<view class="con" v-else @click="go('/pages/user/info')">
+			<view class="con" v-else>
 				<view class="nickName">你还没登录</view>
 				<view class="welcome">欢迎使用岑溪人民医院小程序</view>
 			</view>
@@ -56,11 +56,13 @@
 					<text class="icon ic" style="color: #03a9f4">&#xe6a3;</text>
 					<text class="title">复诊提醒</text>
 					<text class="icon arrow">&#xe62b;</text>
+					<view class="bage" v-if="user.remind > 0">{{ user.remind > 99 ? '99+' : user.remind }}</view>
 				</view>
 				<view class="s_item" @click="go('/pages/follow/index')">
 					<text class="icon ic" style="color: #607d8b">&#xe60b;</text>
 					<text class="title">我的随访</text>
 					<text class="icon arrow">&#xe62b;</text>
+					<view class="bage" v-if="user.follow > 0">{{ user.follow > 99 ? '99+' : user.follow }}</view>
 				</view>
 				<view class="s_item" @click="go('/pages/visit/index')">
 					<text class="icon ic" style="color: #03a9f4">&#xe685;</text>
@@ -167,6 +169,11 @@ export default {
 			height: 60px;
 			border-radius: 50%;
 		}
+		.head {
+			width: 40px;
+			height: 60px;
+			border-radius: 3px;
+		}
 		.con {
 			float: left;
 			padding-left: 15px;

+ 105 - 8
app/pages/user/info.vue

@@ -1,19 +1,116 @@
 <template>
-	<view>
-		
+	<view class="main pdt0">
+		<view class="form">
+			<view class="form_group">
+				<view class="lable re" style="margin-top: 19px">头像</view>
+				<image :src="user.avatar ? ip + user.avatar : '../../static/head.png'" class="head" @click="choice()"></image>
+			</view>
+			<view class="form_group">
+				<view class="lable re">姓名</view>
+				<input v-model="user.nickName" placeholder="请输入姓名" />
+			</view>
+			<view class="form_group">
+				<view class="lable">科室</view>
+				<view class="desc">{{ user.dept.deptName }}</view>
+			</view>
+			<view class="form_group">
+				<view class="lable">角色</view>
+				<view class="desc" v-if="user.roles.length > 0">{{ user.roles[0].roleName }}</view>
+			</view>
+			<view class="form_group">
+				<view class="lable re">个人介绍</view>
+			</view>
+			<leditor ref="editor" v-model="user.introduce"></leditor>
+		</view>
+		<button class="btn" @click="save()">
+			<text class="icon">&#xe653;</text>
+			<text>确认修改</text>
+		</button>
 	</view>
 </template>
 
 <script>
-	export default {
-		data() {
-			return {
-				
-			};
+export default {
+	data() {
+		return {
+			ip: this.http.ip,
+			user: this.getUser()
+		};
+	},
+	onLoad() {
+		setTimeout(() => {
+			this.$refs.editor.setContents();
+		}, 300);
+		// 监听从裁剪页发布的事件,获得裁剪结果
+		uni.$on('uAvatarCropper', (path) => {
+			uni.showLoading({ title: '正在上传...', mask: true });
+			// 上传到服务端
+			uni.uploadFile({
+				url: this.ip + '/system/user/profile/avatar',
+				filePath: path,
+				name: 'avatarfile',
+				header: { Authorization: this.getUser().token },
+				contentType: 'application/x-www-form-urlencoded',
+				success: (res) => {
+					let r = JSON.parse(res.data);
+					if (r.code === 200) {
+						uni.showToast({ title: '更新成功' });
+						this.user.avatar = r.imgUrl;
+						let user = this.getUser();
+						user.avatar = this.user.avatar;
+						uni.setStorageSync('user', user);
+						this.$forceUpdate();
+					} else {
+						uni.hideLoading();
+						uni.showModal({ content: r.msg, showCancel: false });
+					}
+				}
+			});
+		});
+	},
+	methods: {
+		choice() {
+			uni.navigateTo({
+				url: '/components/u-avatar-cropper/u-avatar-cropper?destWidth=100&rectWidth=140&fileType=jpg'
+			});
+		},
+		save() {
+			let rule = [
+				{ name: 'avatar', checkType: 'notnull', errorMsg: '请上传头像' },
+				{ name: 'nickName', checkType: 'notnull', errorMsg: '请输入姓名' },
+				{ name: 'introduce', checkType: 'editor', errorMsg: '请输入个人介绍' }
+			];
+			if (!this.verify.check(this.user, rule)) {
+				uni.showModal({ content: this.verify.error, showCancel: false });
+				return false;
+			}
+			this.http.request({
+				url: '/system/user/editInfo',
+				data: { nickName: this.user.nickName, introduce: this.user.introduce },
+				method: 'POST',
+				success: (res) => {
+					let user = this.getUser();
+					user.nickName = this.user.nickName;
+					user.introduce = this.user.introduce;
+					uni.setStorageSync('user', user);
+					uni.showToast({ title: '编辑成功' });
+				}
+			});
 		}
 	}
+};
 </script>
 
 <style lang="scss">
-
+.form {
+	.head {
+		width: 40px;
+		height: 60px;
+		border-radius: 3px;
+		float: right;
+	}
+	input {
+		padding: 4px 0px 0px 3px;
+	}
+}
 </style>

+ 29 - 20
app/pages/user/login.vue

@@ -1,22 +1,23 @@
 <template>
-	<view class="bg">
-		<image src="https://axure-file.lanhuapp.com/md5__f93627286149f825890eb821ab5d5abb.png" class="pic" mode="widthFix"></image>
-		<view class="xy">
-			<u-checkbox-group class="checkbox" v-model="item.checked">
-				<u-checkbox size="15" shape="circle" label="我已阅读并同意" labelSize="14" name="true"></u-checkbox>
-			</u-checkbox-group>
-			<text class="a" @click="go('/pages/other/agreement?title=用户协议')">《用户协议》</text>
-			<text>和</text>
-			<text class="a" @click="go('/pages/other/agreement?title=隐私政策')">《隐私政策》</text>
+	<view>
+		<view class="bg">
+			<image src="https://axure-file.lanhuapp.com/md5__f93627286149f825890eb821ab5d5abb.png" class="pic" mode="widthFix"></image>
+			<view class="xy">
+				<u-checkbox-group class="checkbox" v-model="item.checked">
+					<u-checkbox size="15" shape="circle" label="我已阅读并同意" labelSize="14" name="true"></u-checkbox>
+				</u-checkbox-group>
+				<text class="a" @click="go('/pages/other/agreement?title=用户协议')">《用户协议》</text>
+				<text>和</text>
+				<text class="a" @click="go('/pages/other/agreement?title=隐私政策')">《隐私政策》</text>
+			</view>
+			<button class="btn" @click="getUserProfile()" :disabled="disabled">用户登录</button>
+		</view>
+		<view class="doctor" @click="go('/pages/user/loginDoctor')">
+			<view class="login">
+				<text class="icon">&#xe6a3;</text>
+				<text>医生登录</text>
+			</view>
 		</view>
-		<button class="btn" @click="getUserProfile()" :disabled="disabled">
-			<text class="icon">&#xe62d;</text>
-			<text>用户登录</text>
-		</button>
-		<button class="btn" style="background-color: #607d8b" @click="go('/pages/user/loginDoctor')">
-			<text class="icon">&#xe6a3;</text>
-			<text>医生登录</text>
-		</button>
 	</view>
 </template>
 <script>
@@ -78,13 +79,21 @@ export default {
 
 <style lang="scss">
 .bg {
-	padding: 30px;
-	margin-top: -20px;
+	padding: 0px 30px;
 	.pic {
 		width: 100%;
 		border-radius: 5px;
 	}
-	.btn {
+}
+.doctor {
+	position: fixed;
+	width: 100%;
+	bottom: 0px;
+	background-color: #e1e1e1;
+	.login {
+		text-align: center;
+		padding: 20px;
+		color: $font-c;
 		.icon {
 			padding-right: 5px;
 		}

+ 9 - 15
app/pages/user/loginDoctor.vue

@@ -1,13 +1,14 @@
 <template>
 	<view>
+		<back></back>
 		<view class="app_top">
-			<image src="https://axure-file.lanhuapp.com/md5__f93627286149f825890eb821ab5d5abb.png" mode="widthFix" class="img"></image>
+			<image src="../../static/my2.png" mode="widthFix" class="img"></image>
 		</view>
 		<view class="dk">
 			<view class="tx">
 				<image src="../../static/favicon.png" mode="aspectFill"></image>
 			</view>
-			<view class="unit">岑溪人民医院随访系统</view>
+			<view class="unit">医生登录</view>
 			<view class="bg">
 				<text class="icon">&#xe616;</text>
 				<input v-model="item.username" placeholder="请输入账号" class="input" />
@@ -74,6 +75,10 @@ export default {
 				data: this.item,
 				method: 'POST',
 				success: (res) => {
+					//提醒用户修改初始密码
+					if (this.item.password == '123456') {
+						uni.setStorageSync('easy', true);
+					}
 					let user = res.data.user.user;
 					user.doctor = true;
 					user.token = res.data.token;
@@ -101,14 +106,6 @@ export default {
 .app_top {
 	display: block;
 	position: relative;
-	.icon {
-		position: absolute;
-		color: white;
-		z-index: 2;
-		top: 18%;
-		left: 13px;
-		font-size: 25px;
-	}
 	.img {
 		width: 100%;
 	}
@@ -120,7 +117,7 @@ export default {
 	width: 75%;
 	background-color: white;
 	border-radius: 10px;
-	margin-top: -130px;
+	margin-top: -80px;
 	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
 	.tx {
 		text-align: center;
@@ -135,7 +132,7 @@ export default {
 	}
 	.unit {
 		text-align: center;
-		margin-bottom: 25px;
+		margin-bottom: 20px;
 		font-size: 20px;
 		color: #545555;
 		margin-top: 10px;
@@ -191,7 +188,4 @@ export default {
 		font-size: 14px;
 	}
 }
-.btn {
-	margin-top: 30px;
-}
 </style>

+ 88 - 0
app/pages/user/pass.vue

@@ -0,0 +1,88 @@
+<template>
+	<view class="main">
+		<view class="form">
+			<view class="form_group">
+				<view class="lable">旧密码</view>
+				<view class="icon" :class="{ active: !show }" @click="show = !show">&#xe6ef;</view>
+				<input :password="show" v-model="item.oldPassword" placeholder="请输入旧密码" />
+			</view>
+			<view class="form_group">
+				<view class="lable">新密码</view>
+				<view class="icon" :class="{ active: !show }" @click="show = !show">&#xe6ef;</view>
+				<input :password="show" v-model="item.newPassword" placeholder="请输入新密码" />
+			</view>
+			<view class="form_group">
+				<view class="lable">重复密码</view>
+				<view class="icon" :class="{ active: !show }" @click="show = !show">&#xe6ef;</view>
+				<input :password="show" v-model="item.again" placeholder="请再次输入新密码" />
+			</view>
+		</view>
+		<button class="btn" @click="save()">确认</button>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			item: {},
+			show: true
+		};
+	},
+	methods: {
+		save() {
+			let rule = [
+				{ name: 'oldPassword', checkType: 'notnull', errorMsg: '请输入旧密码' },
+				{ name: 'newPassword', checkType: 'notnull', errorMsg: '请输入新密码' },
+				{ name: 'again', checkType: 'same', checkRule: this.item.newPassword, errorMsg: '两次输入不一致' }
+			];
+			if (!this.verify.check(this.item, rule)) {
+				uni.showModal({ content: this.verify.error, showCancel: false });
+				return false;
+			}
+			this.http.request({
+				url: '/system/user/profile/updatePwd',
+				data: this.item,
+				method: 'PUT',
+				contentType: 'application/x-www-form-urlencoded',
+				success: (res) => {
+					this.http.request({
+						url: '/logout',
+						success: (res) => {
+							uni.showModal({
+								title: '提示',
+								content: '修改成功,请重新登录',
+								showCancel: false,
+								success: (res) => {
+									if (res.confirm) {
+										uni.removeStorageSync('user');
+										uni.redirectTo({ url: '/pages/user/loginDoctor' });
+									}
+								}
+							});
+						}
+					});
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.form_group {
+	input {
+		width: 60%;
+	}
+	.icon {
+		float: right;
+		margin-top: 12px;
+		font-size: 20px;
+		padding-left: 10px;
+		color: $font-c;
+		&.active {
+			color: $main-color;
+		}
+	}
+}
+</style>

BIN=BIN
app/static/my2.png


+ 5 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_HomeController.java

@@ -3,6 +3,7 @@ package com.ruoyi.web.work.api;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.web.work.service.IKnowledgeService;
+import com.ruoyi.web.work.service.INoticeService;
 import com.ruoyi.web.work.service.IUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -22,6 +23,9 @@ public class Api_HomeController extends BaseController {
     private IKnowledgeService knowledgeService;
 
     @Autowired
+    private INoticeService noticeService;
+
+    @Autowired
     IUserService userService;
 
     @GetMapping("/index")
@@ -29,6 +33,7 @@ public class Api_HomeController extends BaseController {
         AjaxResult result = new AjaxResult();
         result.put("knowledgeList", knowledgeService.indexList());
         result.put("expertList", userService.selectExpert());
+        result.put("noticeList", noticeService.indexList());
         return AjaxResult.success(result);
     }
 

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_MdmController.java

@@ -131,7 +131,7 @@ public class Api_MdmController {
         headData.put("MessageId",messageId);
         headData.put("Timestamp",timestamp);
         requestData.put("Head",headData);
-        ajax.put("Request",requestData);
+        ajax.put("Response",requestData);
         return ajax;
     }
 

+ 42 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_NoticeController.java

@@ -0,0 +1,42 @@
+package com.ruoyi.web.work.api;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.web.work.domain.Notice;
+import com.ruoyi.web.work.domain.vo.NoticeVoList;
+import com.ruoyi.web.work.service.INoticeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 通知公告
+ *
+ * @author lsw
+ * @date 2024-08-20
+ */
+@RestController
+@RequestMapping("/app/notice")
+public class Api_NoticeController extends BaseController {
+    @Autowired
+    private INoticeService noticeService;
+
+    @GetMapping("/list")
+    public TableDataInfo list(Notice notice) {
+        notice.setState(0);
+        startPage();
+        List<NoticeVoList> list = noticeService.selectList(notice);
+        return getDataTable(list);
+    }
+
+    @GetMapping(value = "/detail/{id}")
+    public AjaxResult detail(@PathVariable("id") Long id) {
+        return AjaxResult.success(noticeService.getById(id));
+    }
+
+}

+ 1 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/config/InterceptorConfig.java

@@ -22,6 +22,7 @@ public class InterceptorConfig implements WebMvcConfigurer {
         registration.addPathPatterns("/app/**");
         registration.excludePathPatterns("/app/user/login"); //排除
         registration.excludePathPatterns("/app/home/**"); //排除
+        registration.excludePathPatterns("/app/notice/**"); //排除
         registration.excludePathPatterns("/app/knowledge/**"); //排除
         registration.excludePathPatterns("/app/doctor/**"); //排除
         registration.excludePathPatterns("/app/department/**"); //排除

+ 63 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/controller/NoticeController.java

@@ -0,0 +1,63 @@
+package com.ruoyi.web.work.controller;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.web.work.domain.Notice;
+import com.ruoyi.web.work.domain.vo.NoticeVoList;
+import com.ruoyi.web.work.service.INoticeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 通知公告
+ * @author lsw
+ * @date 2024-08-20
+ */
+@RestController
+@RequestMapping("/work/notice")
+public class NoticeController extends BaseController {
+    @Autowired
+    private INoticeService noticeService;
+
+    @PreAuthorize("@ss.hasPermi('work:notice:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(Notice notice){
+        startPage();
+        List<NoticeVoList> list = noticeService.selectList(notice);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('work:notice:query')")
+    @GetMapping(value = "/detail/{id}")
+    public AjaxResult detail(@PathVariable("id") Long id){
+        return AjaxResult.success(noticeService.getById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('work:notice:add')")
+    @Log(title = "通知公告", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody Notice notice){
+        return toAjax(noticeService.save(notice));
+    }
+
+    @PreAuthorize("@ss.hasPermi('work:notice:edit')")
+    @Log(title = "通知公告", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody Notice notice){
+        return toAjax(noticeService.updateById(notice));
+    }
+
+    @PreAuthorize("@ss.hasPermi('work:notice:remove')")
+    @Log(title = "通知公告", businessType = BusinessType.DELETE)
+    @GetMapping("/remove/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids){
+        return toAjax(noticeService.removeByIds(Arrays.asList(ids)));
+    }
+}

+ 50 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/domain/Notice.java

@@ -0,0 +1,50 @@
+package com.ruoyi.web.work.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.experimental.Accessors;
+/**
+ * @author lsw
+ * @date 2024-08-20
+ */
+@Data
+@TableName(value = "tb_notice")
+@Accessors(chain = true)
+public class Notice{
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    @ApiModelProperty(value = "标题")
+    private String title;
+
+    @ApiModelProperty(value = "内容")
+    private String contents;
+
+    @ApiModelProperty(value = "是否置顶:1=是,0=否")
+    private Integer top;
+
+    @ApiModelProperty(value = "状态:1=正常,0=停用")
+    private Integer state;
+
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @TableField(fill = FieldFill.UPDATE)
+    private String updateBy;
+
+    @TableField(fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+
+}

+ 32 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/domain/vo/NoticeVoList.java

@@ -0,0 +1,32 @@
+package com.ruoyi.web.work.domain.vo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * @author lsw
+ * @date 2024-08-20
+ */
+@Data
+@Accessors(chain = true)
+public class NoticeVoList {
+
+    private Long id;
+
+    @ApiModelProperty(value = "标题")
+    private String title;
+
+    @ApiModelProperty(value = "是否置顶:1=是,0=否")
+    private Integer top;
+
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+}

+ 17 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/mapper/NoticeMapper.java

@@ -0,0 +1,17 @@
+package com.ruoyi.web.work.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.web.work.domain.Notice;
+import com.ruoyi.web.work.domain.vo.NoticeVoList;
+
+import java.util.List;
+
+/**
+ * @author lsw
+ * @date 2024-08-20
+ */
+public interface NoticeMapper extends BaseMapper<Notice> {
+    List<NoticeVoList> selectList(Notice notice);
+
+    List<Notice> indexList();
+}

+ 0 - 6
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/IKnowledgeService.java

@@ -18,11 +18,5 @@ public interface IKnowledgeService extends IService<Knowledge> {
 
     List<KnowledgeVoList> selectAppList(Knowledge knowledge);
 
-    /**
-     * 知识详情
-     *
-     * @param id
-     * @return
-     */
     AjaxResult detail(Long id);
 }

+ 17 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/INoticeService.java

@@ -0,0 +1,17 @@
+package com.ruoyi.web.work.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.web.work.domain.Notice;
+import com.ruoyi.web.work.domain.vo.NoticeVoList;
+
+import java.util.List;
+
+/**
+ * @author lsw
+ * @date 2024-08-20
+ */
+public interface INoticeService extends IService<Notice>{
+    List<NoticeVoList> selectList(Notice notice);
+
+    List<Notice> indexList();
+}

+ 31 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/impl/NoticeServiceImpl.java

@@ -0,0 +1,31 @@
+package com.ruoyi.web.work.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.web.work.domain.Notice;
+import com.ruoyi.web.work.domain.vo.NoticeVoList;
+import com.ruoyi.web.work.mapper.NoticeMapper;
+import com.ruoyi.web.work.service.INoticeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author lsw
+ * @date 2024-08-20
+ */
+@Service
+public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> implements INoticeService {
+    @Autowired
+    private NoticeMapper noticeMapper;
+
+    @Override
+    public List<NoticeVoList> selectList(Notice notice) {
+        return noticeMapper.selectList(notice);
+    }
+
+    @Override
+    public List<Notice> indexList() {
+        return noticeMapper.indexList();
+    }
+}

+ 6 - 1
ruoyi-admin/src/main/resources/mapper/work/KnowledgeMapper.xml

@@ -6,7 +6,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     
     <select id="selectList" resultType="com.ruoyi.web.work.domain.Knowledge">
         SELECT
-        k.*,
+        k.id,
+        k.title,
+        k.type,
+        k.create_time,
+        k.create_by,
+        k.state,
         d.dept_name AS deptName
         FROM
         tb_knowledge k

+ 20 - 0
ruoyi-admin/src/main/resources/mapper/work/NoticeMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.web.work.mapper.NoticeMapper">
+    
+    <select id="selectList" resultType="com.ruoyi.web.work.domain.Notice">
+        select id,title,top,state,create_time,create_by from tb_notice
+        <where>
+            <if test="title != null  and title != ''"> and title like concat('%', #{title}, '%')</if>
+            <if test="top != null "> and top = #{top}</if>
+            <if test="state != null "> and state = #{state}</if>
+        </where>
+    </select>
+
+    <select id="indexList" resultType="com.ruoyi.web.work.domain.vo.NoticeVoList">
+        SELECT id,top,title,create_time FROM tb_notice WHERE state=0 ORDER BY top DESC,id DESC LIMIT 5
+    </select>
+
+</mapper>

+ 4 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java

@@ -67,6 +67,10 @@ public class SysUser extends BaseEntity {
 
     private String password;
 
+    private Integer shows;
+
+    private Integer views;
+
     @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
     private String status;
 

+ 4 - 1
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -12,6 +12,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="email"        column="email"        />
         <result property="phonenumber"  column="phonenumber"  />
         <result property="sex"          column="sex"          />
+		<result property="shows"        column="shows"       />
+		<result property="views"        column="views"       />
+		<result property="sex"          column="sex"          />
         <result property="avatar"       column="avatar"       />
         <result property="password"     column="password"     />
         <result property="status"       column="status"       />
@@ -48,7 +51,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 	
 	<sql id="selectUserVo">
-        select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.introduce,
+        select u.user_id,u.views,u.shows, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.introduce,
         d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
         r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
         from sys_user u