Browse Source

Merge branch 'master' of https://git.citupro.com/zhengnaiwen_citu/menduner-uniapp

lifanagju_citu 8 months ago
parent
commit
d5f035693e

+ 3 - 3
api/position.js

@@ -8,12 +8,12 @@ export const getJobAdvertisedSearch = (params) => {
     params,
     custom: {
       showLoading: false,
-      auth: true
+      auth: false
     }
   })
 }
 
-// 根据条件搜索招聘职位
+// 职位详情
 export const getPositionDetails = (params) => {
   return request({
     url: '/app-api/menduner/system/job/advertised/get/detail',
@@ -21,7 +21,7 @@ export const getPositionDetails = (params) => {
     params,
     custom: {
       showLoading: false,
-      auth: true
+      auth: false
     }
   })
 }

+ 43 - 2
api/user.js

@@ -38,6 +38,19 @@ export const getJobFavoriteList = (params) => {
   })
 }
 
+// 获取关注的企业列表
+export const getSubscribeEnterprise = (params) => {
+  return request({
+    url: '/app-api/menduner/system/person/get/enterprise/subscribe/page',
+    method: 'GET',
+    params,
+    custom: {
+      showLoading: false,
+      auth: true
+    }
+  })
+}
+
 // 谁看过我
 export const getInterestedMePage = (params) => {
   return request({
@@ -94,7 +107,7 @@ export const deleteResume = (id) => {
 // 获取已投递的职位列表
 export const getJobDeliveryList = (params) => {
   return request({
-    url: '/menduner/system/job-cv-rel/page',
+    url: '/app-api/menduner/system/job-cv-rel/page',
     method: 'GET',
     params,
     custom: {
@@ -107,7 +120,7 @@ export const getJobDeliveryList = (params) => {
 // 获取面试日程分页
 export const getUserInterviewInvitePage = (params) => {
   return request({
-    url: '/menduner/system/interview-invite/page',
+    url: '/app-api/menduner/system/interview-invite/page',
     method: 'GET',
     params,
     custom: {
@@ -115,4 +128,32 @@ export const getUserInterviewInvitePage = (params) => {
       auth: true
     }
   })
+}
+
+// 同意邀约面试
+export const userInterviewInviteConsent = (data) => {
+  return request({
+    url: '/app-api/menduner/system/interview-invite/consent',
+    method: 'POST',
+    data,
+    custom: {
+      auth: true,
+      showLoading: false
+    }
+  })
+}
+
+// 拒绝邀约面试
+export const userInterviewInviteReject = (id) => {
+  return request({
+    url: '/app-api/menduner/system/interview-invite/reject',
+    method: 'POST',
+    params: {
+      id
+    },
+    custom: {
+      auth: true,
+      showLoading: false
+    }
+  })
 }

File diff suppressed because it is too large
+ 0 - 3
components/PositionList/index.vue


+ 2 - 2
pages.json

@@ -43,9 +43,9 @@
 					}
 				},
 				{
-					"path": "collect/position",
+					"path": "collect/index",
 					"style": {
-						"navigationBarTitleText": "职位收藏"
+						"navigationBarTitleText": "我的收藏"
 					}
 				},
 				{

+ 2 - 2
pages/index/my.vue

@@ -46,7 +46,7 @@ import { getUserAvatar } from '@/utils/avatar'
 import { getAccessToken } from '@/utils/request'
 
 const useUserStore = userStore()
-const userInfo = computed(() => useUserStore.userInfo)
+const userInfo = computed(() => useUserStore?.userInfo)
 const popup = ref()
 const itemList = [
 	{ title: "面试管理", path: "/pagesA/interview/index", icon: "list" },
@@ -55,7 +55,7 @@ const itemList = [
 
 const list = [
 	{	title:'附件简历',	path:'/pagesA/resume/index'	},					
-	{ title:'职位收藏', path:'/pagesA/collect/position' },
+	{ title:'我的收藏', path:'/pagesA/collect/index' },
 	{ title:'切换为招聘者', rightTex: '我要招人' }
 ]
 

+ 2 - 2
pages/index/position.vue

@@ -22,8 +22,8 @@
 import SwiperAd from '@/components/SwiperAd'
 // import SearchBar from '@/components/SearchBar'
 import FilterList from '@/components/FilterList'
-import PositionList from '@/pagesB/PositionList'
-import { positionList, swiperAdListTest } from '@/utils/testData'
+import PositionList from '@/components/PositionList'
+import { swiperAdListTest } from '@/utils/testData'
 import { dealDictObjData } from '@/utils/position'
 import { getJobAdvertisedSearch } from '@/api/position';
 import { ref, reactive } from 'vue'

+ 90 - 0
pagesA/collect/company.vue

@@ -0,0 +1,90 @@
+<template>
+  <view class="defaultBgc">
+    <scroll-view class="scrollBox" scroll-y="true" @scrolltolower="loadingMore" style="height: 100vh;">
+      <view v-if="items.length">
+        <view v-for="(item, index) in items" :key="index" class="ss-m-t-20" @click="toDetail(item)">
+          <view style="background-color: #fff;" class="ss-p-30">
+            <view class="d-flex align-center">
+              <image :src="item.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" class="avatar" style="width: 60px; height: 60px;"></image>
+              <view style="flex: 1;" class="ss-m-l-30">
+                <view class="enterprise-name ellipsis">{{ item.name }}</view>
+                <view class="ss-m-y-15 font-size-12">
+                  <span class="tag-gap color-666">
+                    <span>{{ item.financingName }}</span>
+                    <span class="ss-m-x-10" v-if="item.financingName && item.industryName">|</span>
+                    <span>{{item.industryName }}</span>
+                    <span class="ss-m-x-10" v-if="item.scaleName">|</span>
+                    <span>{{item.scaleName }}</span>
+                  </span>
+                </view>
+                <view>
+                  <uni-tag 
+                    v-for="(tag, i) in item.tagList || []"
+                    :key="i"
+                    class="ss-m-r-10"
+                    :text="tag"
+                    inverted="false"
+                    size="mini"
+                    custom-style="background-color: #eef1f7;color:#7f828b;border-color:#eef1f7;"
+                  />
+                </view>
+              </view>
+            </view>
+          </view>
+        </view>
+        <uni-load-more :status="status" />
+      </view>
+      <view v-else class="nodata-img-parent">
+        <image src="https://minio.citupro.com/dev/static/nodata.png" mode="widthFix" style="width: 100vw;height: 100vh;"></image>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { getSubscribeEnterprise } from '@/api/user'
+import { dealDictArrayData } from '@/utils/position'
+
+const items = ref([])
+const status = ref('more')
+const queryParams = ref({
+  pageSize: 10,
+  pageNo: 1
+})
+
+const getList = async () => {
+  const { data } = await getSubscribeEnterprise(queryParams.value)
+  let list = data?.list || []
+  if (list?.length) {
+    list = dealDictArrayData([], list)
+    items.value = items.value.concat(list)
+  }
+  status.value = list?.length < queryParams.value.pageSize ? 'noMore' : 'more'
+}
+getList()
+
+// 加载更多
+const loadingMore = () => {
+  status.value = 'loading'
+  queryParams.value.pageNo++
+  getList()
+}
+
+// 企业详情
+const toDetail = (item) => {
+  uni.navigateTo({
+    url: `/pagesB/companyDetail/index?id=${item.id}`
+  })
+}
+</script>
+
+<style scoped lang="scss">
+.enterprise-name {
+  color: #333;
+  font-weight: bold;
+  font-size: 16px;
+  width: 70vw;
+  max-width: 70vw;
+}
+</style>

+ 26 - 0
pagesA/collect/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <view>
+    <uni-segmented-control :current="current" :values="controlList" @clickItem="handleChange" styleType="text" activeColor="#00897B"></uni-segmented-control>
+      <Position v-if="current === 0"></Position>
+      <Company v-else></Company>
+  </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import Position from './position.vue'
+import Company from './company.vue'
+
+const current = ref(0)
+const controlList = ['职位', '企业']
+
+const handleChange = (e) => {
+  current.value = e.currentIndex
+}
+
+const loadingMore = () => {}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 1 - 1
pagesA/collect/position.vue

@@ -16,7 +16,7 @@
 import { ref } from 'vue'
 import { getJobFavoriteList } from '@/api/user'
 import { dealDictObjData } from '@/utils/position'
-import PositionList from '@/pagesB/PositionList'
+import PositionList from '@/components/PositionList'
 
 const status = ref('more')
 const queryParams = ref({

+ 55 - 48
pagesA/interview/index.vue

@@ -1,69 +1,37 @@
 <template>
   <view>
     <uni-segmented-control :current="current" :values="controlList" @clickItem="handleChange" styleType="text" activeColor="#00897B"></uni-segmented-control>
-    <scroll-view class="scrollBox" scroll-y="true" @scrolltolower="loadingMore" style="height: calc(100vh - 36px);">
+    <scroll-view class="scrollBox defaultBgc" scroll-y="true" @scrolltolower="loadingMore" style="height: calc(100vh - 36px);">
       <view v-if="items.length">
-        <uni-card v-for="(item,index) in items" :key="index" :is-shadow="true" :border='false' shadow="0px 0px 3px 1px rgba(0,0,0,0.1)" >
-          <view>
-            <view class="mar">
-              <span style="font-size: 16px;font-weight: 700;color: black;">{{ item.job.name }}</span>
-              <span class="mar" v-if="item.job.hire">
-                <uni-icons type="fire-filled" size="25" color="#ff770d"></uni-icons>
-              </span>
-              <span class="salary-text">{{ item.job.payTo }}-{{ item.job.payFrom }}/{{ item.job.payName }}</span>
-            </view>
-            <view style="font-size: 13px;">
-              <span class="tag-gap">
-                <span>{{ item.job.areaName }}</span>
-                <span class="viewider">|</span>
-                <span>{{ item.job.eduName }}</span>
-                <span class="viewider">|</span>
-                <span>{{ item.job.expName }}</span>
-              </span>
-            </view>
-            <view class="mar">
-              <view style="margin:10px 0px;font-size:13px;" class="f-horizon">
-                <span style="display: flex;">
-                  <image  class="r-avatar" :src="item.enterprise.logoUrl || ''" style="width: 50px; height: 50px;"></image>
-                  <span class="ml" style="width:50vw;">
-                    <view style="width:50vw;font-weight: bold;">{{ item.enterprise.name }} </view>
-                    <view class="dis">
-                      <view class="show-more" :style="{'width': item.enterprise.industryName == '' ? '14vw' : '28vw'}">{{!!item.enterprise.industryName ? item.enterprise.industryName : '行业未知'}}</view>
-                      <span class="divider ss-m-10"> | </span>
-                      <span>{{ !!item.enterprise.scaleName ? item.enterprise.scaleName : '规模未知' }}</span>
-                    </view>
-                  </span>
-                </span>
-              </view>
-            </view>
-            <view v-if="current === 4">
-              <view>拒绝原因: {{ item.refuseMsg }}</view>
-            </view>
-          </view>
-          <view v-if="item.status == '0'">
-            <view class="divided-line"></view>
-            <view style="display: flex;justify-content: flex-end;">
-              <span style="color: #dd524d;text-decoration: underline;">拒绝</span>
-              <span style="color: #2991de;margin-left: 65rpx;text-decoration: underline;">同意</span>
-            </view>
-          </view>
-        </uni-card>
+        <PositionList v-if="current === 0" class="pb-10" :list="items" :noMore="false"></PositionList>
+        <Items v-else class="pb-10" :list="items" @action="handleAction"></Items>
         <uni-load-more :status="more" />
       </view>
       <view v-else class="nodata-img-parent">
         <image src="https://minio.citupro.com/dev/static/nodata.png" mode="widthFix" style="width: 100vw;height: 100vh;"></image>
       </view>
     </scroll-view>
+
+    <!-- 同意、拒绝面试 -->
+    <uni-popup ref="popup" type="dialog">
+      <uni-popup-dialog :type="type === 'agree' ? 'success' : 'warn'" cancelText="取消" confirmText="确认" 
+        title="系统提示" :content="type === 'agree' ? '确认接受面试吗?' : '确认拒绝面试吗?'" @confirm="handleConfirm" @close="handleClose"
+      ></uni-popup-dialog>
+    </uni-popup>
   </view>
 </template>
 
 <script setup>
 import { ref } from 'vue'
-import { getJobDeliveryList, getUserInterviewInvitePage } from '@/api/user'
+import { getJobDeliveryList, getUserInterviewInvitePage, userInterviewInviteConsent, userInterviewInviteReject } from '@/api/user'
 import { dealDictObjData } from '@/utils/position'
+import PositionList from '@/components/PositionList'
+import Items from './item.vue'
+import { userStore } from '@/store/user'
 
+const useUserStore = userStore()
 const current = ref(0)
-const controlList = ['已投递', '待接受', '待面试', '已完成', '已拒绝']
+const controlList = ['已投递', '待同意', '待面试', '已完成', '已拒绝']
 const statusList = [0, 1, 3, 98]
 
 const more = ref('more')
@@ -72,12 +40,19 @@ const queryParams = ref({
   pageNo: 1,
   pageSize: 10
 })
+const popup = ref()
+const type = ref('')
+const id = ref(null)
 
 const getList = async () => {
   const api = current.value === 0 ? getJobDeliveryList : getUserInterviewInvitePage
   if (current.value !== 0) queryParams.value.status = statusList[current.value - 1]
   const { data } = await api(queryParams.value)
   const list = data?.list || []
+  if (!list.length && queryParams.value.pageNo === 1) {
+    items.value = []
+    return
+  }
   if (list?.length) {
     list.forEach(e => {
       e.job = { ...e.job, ...dealDictObjData({}, e.job) }
@@ -90,16 +65,48 @@ const getList = async () => {
 getList()
 
 const handleChange = (e) => {
+  items.value = []
   queryParams.value.pageNo = 1
   current.value = e.currentIndex
   getList()
 }
 
+// 加载更多
 const loadingMore = () => {
   more.value = 'loading'
   queryParams.value.pageNo++
   getList()
 }
+
+// 同意、拒绝
+const handleAction = (item, typeVal) => {
+  id.value = item.id
+  type.value = typeVal
+  popup.value.open()
+}
+
+const handleClose = () => {
+  popup.value.close()
+  type.value = ''
+  id.value = null
+}
+
+const handleConfirm = async () => {
+  if (!id.value) return
+  const api = type.value === 'agree' ? userInterviewInviteConsent : userInterviewInviteReject
+  // 同意需提交手机号
+  let phone = ''
+  if (useUserStore?.userInfo?.phone) phone = useUserStore?.userInfo?.phone
+  await api(type.value === 'agree' ? { id: id.value, phone } : id.value)
+  handleClose()
+  uni.showToast({
+    title: '操作成功',
+    icon: 'success'
+  })
+  queryParams.value.pageNo = 1
+  items.value = []
+  getList()
+}
 </script>
 
 <style scoped lang="scss">

+ 153 - 0
pagesA/interview/item.vue

@@ -0,0 +1,153 @@
+<template>
+  <view v-if="list.length > 0">
+    <view v-for="(item, index) in list" :key="index">
+      <view class="sub-li-bottom" @click.stop="toDetail(0, item)">
+        <view class="avatarBox">
+          <image class="enterAvatar" :src="item.enterprise?.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'"></image>
+        </view>
+        <view>
+          <span class="ss-m-x-20" style="font-weight: bold;">{{ item.contact?.name || ' -- ' }}</span>
+          <span>{{ item.contact?.postNameCn }}</span>
+          <span class="divider tag-gap1 ss-m-x-10" v-if="item.contact?.postNameCn && item.invitePhone"> | </span>
+          <span class="mr">{{ item.invitePhone }}</span>
+        </view>
+      </view>
+      <!-- 职位信息 -->
+      <view class="list-shape">
+        <view class="titleBox my-5" @click="toDetail(1, item)">
+          <span style="font-size: 16px;font-weight: 700;color: black;">{{item.job?.name}}</span>
+          <span class="salary-text">{{ item.job?.payFrom }}-{{ item.job?.payTo }}/{{ item.job?.payName }}</span>
+        </view>
+        <!-- 面试时间、地点 -->
+        <view class="color-666 font-size-14 ss-m-t-20" @click="toDetail(1, item)">
+          <view>面试时间:{{ timesTampChange(item.time, 'Y-M-D h:m') }}</view>
+          <view class="ss-m-t-20">面试地点:{{ item.address }}</view>
+        </view>
+
+        <view v-if="item.status === '0'">
+					<view class="divided-line"></view>
+					<view class="d-flex justify-end">
+						<span style="color: #dd524d;text-decoration: underline;" @click="handleAction(item, 'refuse')">拒绝</span>
+						<span style="color: #00897B;margin-left: 65rpx;text-decoration: underline;" @click="handleAction(item, 'agree')">同意</span>
+					</view>
+				</view>
+      </view>
+    </view>
+  </view>
+
+</template>
+
+<script setup>
+import { timesTampChange } from '@/utils/date'
+
+const emits = defineEmits(['action'])
+const props = defineProps({
+  list: { type: Array, default: () => [] }
+})
+
+//岗位详情
+const toDetail = (isPosition, item) =>{
+  const url = isPosition ? `/pagesB/positionDetail/index?id=${item.job?.id}` : `/pagesB/companyDetail/index?id=${item.enterprise?.id}`
+  uni.navigateTo({ url })
+}
+
+const handleAction = (item, type) => {
+  emits('action', item, type)
+}
+
+</script>
+
+<style scoped lang="scss">
+.noMore{
+  margin: 20px 0;
+}
+
+.date-time{
+  color:#d9d0d2;
+  float: right;
+}
+
+.divided-line {
+  width: 100%;
+  height: 1px;
+  background-color: #f0f2f7;
+  margin: 20px 0;
+
+}
+.enterAvatar{
+	width: 40px;
+	height: 40px;
+	border-radius: 50%;
+	margin: auto;
+}
+
+.sub-li-bottom {
+  margin-top: 10px;
+  display: flex;
+  // justify-content:space-between;
+  align-items: center;
+  background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+  font-size: 13px;
+  padding: 5px 30rpx;
+  .avatarBox {
+    max-width: 40px;
+    max-height: 40px;
+  }
+}
+
+.salary-text {
+	float: right;
+	color: #fe574a;
+}
+.list-shape {
+	padding: 10px 30rpx 10px;
+  background-color: #fff;
+  .titleBox {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+}
+.tag-gap{
+	margin: 10rpx 10rpx 10rpx 0;
+}
+.tag-gap1{
+  margin-bottom: 20px;
+}
+.divider-mx{
+	margin: 0 10rpx;
+}
+.divider {
+	color:#e4d4d2;
+}
+
+//公司名称
+.cer-end{
+  position: absolute;
+  top: 85%;
+  right: 16%;
+}
+.cer-text{
+  text-decoration: underline;
+  margin: 0 5rpx;
+}
+//一行展示不全...
+.dis{
+	display: flex;
+	align-items: center;
+}
+.show-more{
+	width: 26vw;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+/* 列表触底暂无更多 */
+.noMore{ text-align:center; color:grey; }
+.mt { margin-top: 10rpx; }
+.mb { margin-bottom: 10rpx; }
+.ml { margin-left: 20rpx; }
+.mr { margin-right: 20rpx; }
+.mr-10{ margin-right: 10rpx; }
+.my-5{ margin: 5px 0; }
+</style>

+ 59 - 22
pagesA/seenMe/index.vue

@@ -2,30 +2,49 @@
   <view class="defaultBgc" style="height: 100vh;">
     <scroll-view class="scrollBox" scroll-y="true" @scrolltolower="loadingMore">
       <view v-if="list.length > 0">
-        <uni-card v-for="(item,index) in list" :key="index" :is-shadow="true" :border='false' shadow="0px 0px 3px 1px rgba(0,0,0,0.1)" >
-          <view class="f-horizon">
-            <image class="avatar" :src="item.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'"></image>
-            <view class="f-straight" style="width:60vw;">
-              <view class="title-des">{{ item.enterprise.name }}</view>
-              <view class="s-word">
-                <span class="dis">
-                  <view class="show-more" :style="{'width': item.enterprise.industryName == '' ? '15vw' : '30vw'}">
-                    {{ item.enterprise.industryName ? item.enterprise.industryName : '行业未知' }}
-                  </view>
-                  <span class="divider ss-m-10"> | </span>
-                  <span>{{ item.enterprise.scaleName || '规模未知' }}</span>
-                </span>
-              </view>
+        <view v-for="(item, index) in list" :key="index" class="ss-m-t-20">
+          <view class="sub-li-bottom">
+            <view class="avatarBox">
+              <image class="r-avatar" :src="getUserAvatar(item.contact.avatar, item.contact.sex)"></image>
+            </view>
+            <view class="ss-m-l-30">
+              <span>{{ item.contact?.name || ' -- ' }}</span>
+              <span class="ss-m-x-10"> | </span>
+              <span>{{ item.post?.nameCn || '--' }}</span>
             </view>
           </view>
-          <view style="border-bottom: 1px dashed #ccc;"></view>
-          <view class="ss-m-t-20 d-flex align-center justify-end">
-            <image class="r-avatar" :src="getUserAvatar(item.contact.avatar, item.contact.sex)"></image>
-            <text class="ss-m-l-20">
-              {{ item.contact.name }} | {{ item.post.nameCn }}
-            </text>
+          <view style="background-color: #fff;" class="ss-p-30">
+            <view class="d-flex align-center">
+              <image :src="item.enterprise.logoUrl" class="avatar" style="width: 60px; height: 60px;"></image>
+              <view style="flex: 1;" class="ss-m-l-30">
+                <view class="enterprise-name ellipsis">{{ item.enterprise.name }}</view>
+                <!-- 行业规模 -->
+                <view class="ss-m-y-15 font-size-12">
+                  <span class="tag-gap color-666">
+                    <span>{{ item.enterprise.financingName }}</span>
+                    <span class="ss-m-x-10" v-if="item.enterprise.financingName && item.enterprise.industryName">|</span>
+                    <span>{{item.enterprise.industryName }}</span>
+                    <span class="ss-m-x-10" v-if="item.enterprise.industryName && item.enterprise.scaleName">|</span>
+                    <span>{{item.enterprise.scaleName }}</span>
+                  </span>
+                </view>
+                <!-- 标签 -->
+                <view>
+                  <uni-tag 
+                    v-for="(tag, i) in item.enterprise.tagList || []"
+                    :key="i"
+                    class="ss-m-r-10"
+                    :text="tag"
+                    inverted="false"
+                    size="mini"
+                    custom-style="background-color: #eef1f7;color:#7f828b;border-color:#eef1f7;"
+                  />
+                </view>
+                <view class="color-666 font-size-13 ss-m-t-20">查看时间:{{ timesTampChange(item.updateTime) }}</view>
+              </view>
+            </view>
           </view>
-        </uni-card>
+        </view>
         <uni-load-more :status="status" />
       </view>
       <view v-else class="nodata-img-parent">
@@ -40,6 +59,7 @@ import { ref } from 'vue'
 import { getInterestedMePage } from '@/api/user'
 import { dealDictObjData } from '@/utils/position'
 import { getUserAvatar } from '@/utils/avatar'
+import { timesTampChange } from '@/utils/date'
 
 const status = ref('more')
 const queryParams = ref({
@@ -71,5 +91,22 @@ const loadingMore = () => {
 </script>
 
 <style scoped lang="scss">
-
+.sub-li-bottom {
+  display: flex;
+  align-items: center;
+  background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+  font-size: 13px;
+  padding: 5px 30rpx;
+  .avatarBox {
+    max-width: 40px;
+    max-height: 40px;
+  }
+}
+.enterprise-name {
+  color: #333;
+  font-weight: bold;
+  font-size: 16px;
+  width: 70vw;
+  max-width: 70vw;
+}
 </style>

+ 1 - 1
static/style/index.css

@@ -192,7 +192,7 @@
 
 .salary-text {
   float: right;
-  color: #ff770d;
+  color: #fe574a;
 }
 
 .list-shape {

Some files were not shown because too many files changed in this diff