index.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import { use, isObj } from '../utils';
  2. import { isMobile } from '../utils/validate/mobile';
  3. import Area from '../area';
  4. import Field from '../field';
  5. import Popup from '../popup';
  6. import Toast from '../toast';
  7. import Button from '../button';
  8. import Dialog from '../dialog';
  9. import Detail from './Detail';
  10. import SwitchCell from '../switch-cell';
  11. const [sfc, bem, t] = use('address-edit');
  12. const defaultData = {
  13. name: '',
  14. tel: '',
  15. country: '',
  16. province: '',
  17. city: '',
  18. county: '',
  19. areaCode: '',
  20. postalCode: '',
  21. addressDetail: '',
  22. isDefault: false
  23. };
  24. export default sfc({
  25. props: {
  26. areaList: Object,
  27. isSaving: Boolean,
  28. isDeleting: Boolean,
  29. validator: Function,
  30. showDelete: Boolean,
  31. showPostal: Boolean,
  32. searchResult: Array,
  33. showSetDefault: Boolean,
  34. showSearchResult: Boolean,
  35. saveButtonText: String,
  36. deleteButtonText: String,
  37. showArea: {
  38. type: Boolean,
  39. default: true
  40. },
  41. showDetail: {
  42. type: Boolean,
  43. default: true
  44. },
  45. detailRows: {
  46. type: Number,
  47. default: 1
  48. },
  49. addressInfo: {
  50. type: Object,
  51. default: () => ({ ...defaultData })
  52. },
  53. telValidator: {
  54. type: Function,
  55. default: isMobile
  56. }
  57. },
  58. data() {
  59. return {
  60. data: {},
  61. showAreaPopup: false,
  62. detailFocused: false,
  63. errorInfo: {
  64. tel: false,
  65. name: false,
  66. postalCode: false,
  67. addressDetail: false
  68. }
  69. };
  70. },
  71. computed: {
  72. areaListLoaded() {
  73. return isObj(this.areaList) && Object.keys(this.areaList).length;
  74. },
  75. areaText() {
  76. const { country, province, city, county, areaCode } = this.data;
  77. if (areaCode) {
  78. const arr = [country, province, city, county];
  79. if (province && province === city) {
  80. arr.splice(1, 1);
  81. }
  82. return arr.filter(text => text).join('/');
  83. }
  84. return '';
  85. }
  86. },
  87. watch: {
  88. addressInfo: {
  89. handler(val) {
  90. this.data = {
  91. ...defaultData,
  92. ...val
  93. };
  94. this.setAreaCode(val.areaCode);
  95. },
  96. deep: true,
  97. immediate: true
  98. },
  99. areaList() {
  100. this.setAreaCode(this.data.areaCode);
  101. }
  102. },
  103. methods: {
  104. onFocus(key) {
  105. this.errorInfo[key] = false;
  106. this.detailFocused = key === 'addressDetail';
  107. this.$emit('focus', key);
  108. },
  109. onChangeDetail(val) {
  110. this.data.addressDetail = val;
  111. this.$emit('change-detail', val);
  112. },
  113. onAreaConfirm(values) {
  114. this.showAreaPopup = false;
  115. this.assignAreaValues();
  116. this.$emit('change-area', values);
  117. },
  118. assignAreaValues() {
  119. const { area } = this.$refs;
  120. if (area) {
  121. const detail = area.getArea();
  122. detail.areaCode = detail.code;
  123. delete detail.code;
  124. Object.assign(this.data, detail);
  125. }
  126. },
  127. onSave() {
  128. const items = ['name', 'tel', 'areaCode', 'addressDetail'];
  129. if (this.showPostal) {
  130. items.push('postalCode');
  131. }
  132. const isValid = items.every(item => {
  133. const msg = this.getErrorMessage(item);
  134. if (msg) {
  135. this.errorInfo[item] = true;
  136. Toast(msg);
  137. }
  138. return !msg;
  139. });
  140. if (isValid && !this.isSaving) {
  141. this.$emit('save', this.data);
  142. }
  143. },
  144. getErrorMessage(key) {
  145. const value = String(this.data[key] || '').trim();
  146. if (this.validator) {
  147. const message = this.validator(key, value);
  148. if (message) {
  149. return message;
  150. }
  151. }
  152. switch (key) {
  153. case 'name':
  154. return value ? '' : t('nameEmpty');
  155. case 'tel':
  156. return this.telValidator(value) ? '' : t('telInvalid');
  157. case 'areaCode':
  158. return value ? '' : t('areaEmpty');
  159. case 'addressDetail':
  160. return value ? '' : t('addressEmpty');
  161. case 'postalCode':
  162. return value && !/^\d{6}$/.test(value) ? t('postalEmpty') : '';
  163. }
  164. },
  165. onDelete() {
  166. Dialog.confirm({
  167. title: t('confirmDelete')
  168. })
  169. .then(() => {
  170. this.$emit('delete', this.data);
  171. })
  172. .catch(() => {
  173. this.$emit('cancel-delete', this.data);
  174. });
  175. },
  176. // get values of area component
  177. getArea() {
  178. return this.$refs.area ? this.$refs.area.getValues() : [];
  179. },
  180. // set area code to area component
  181. setAreaCode(code) {
  182. this.data.areaCode = code || '';
  183. if (code) {
  184. this.$nextTick(this.assignAreaValues);
  185. }
  186. },
  187. setAddressDetail(value) {
  188. this.data.addressDetail = value;
  189. },
  190. onDetailBlur() {
  191. // await for click search event
  192. setTimeout(() => {
  193. this.detailFocused = false;
  194. });
  195. }
  196. },
  197. render(h) {
  198. const { data, errorInfo } = this;
  199. const onFocus = name => () => this.onFocus(name);
  200. // hide bottom field when use search && detail get focused
  201. const hideBottomFields = this.searchResult.length && this.detailFocused;
  202. return (
  203. <div class={bem()}>
  204. <Field
  205. vModel={data.name}
  206. clearable
  207. label={t('name')}
  208. placeholder={t('namePlaceholder')}
  209. error={errorInfo.name}
  210. onFocus={onFocus('name')}
  211. />
  212. <Field
  213. vModel={data.tel}
  214. clearable
  215. type="tel"
  216. label={t('tel')}
  217. placeholder={t('telPlaceholder')}
  218. error={errorInfo.tel}
  219. onFocus={onFocus('tel')}
  220. />
  221. <Field
  222. vShow={this.showArea}
  223. readonly
  224. label={t('area')}
  225. placeholder={t('areaPlaceholder')}
  226. value={this.areaText}
  227. onClick={() => {
  228. this.showAreaPopup = true;
  229. }}
  230. />
  231. <Detail
  232. vShow={this.showDetail}
  233. focused={this.detailFocused}
  234. value={data.addressDetail}
  235. error={errorInfo.addressDetail}
  236. detailRows={this.detailRows}
  237. searchResult={this.searchResult}
  238. showSearchResult={this.showSearchResult}
  239. onFocus={onFocus('addressDetail')}
  240. onBlur={this.onDetailBlur}
  241. onInput={this.onChangeDetail}
  242. onSelect-search={event => {
  243. this.$emit('select-search', event);
  244. }}
  245. />
  246. {this.showPostal && (
  247. <Field
  248. vShow={!hideBottomFields}
  249. vModel={data.postalCode}
  250. type="tel"
  251. maxlength="6"
  252. label={t('postal')}
  253. placeholder={t('postal')}
  254. error={errorInfo.postalCode}
  255. onFocus={onFocus('postalCode')}
  256. />
  257. )}
  258. {this.slots()}
  259. {this.showSetDefault && (
  260. <SwitchCell
  261. vModel={data.isDefault}
  262. vShow={!hideBottomFields}
  263. title={t('defaultAddress')}
  264. onChange={event => {
  265. this.$emit('change-default', event);
  266. }}
  267. />
  268. )}
  269. <div vShow={!hideBottomFields} class={bem('buttons')}>
  270. <Button
  271. block
  272. loading={this.isSaving}
  273. type="danger"
  274. text={this.saveButtonText || t('save')}
  275. onClick={this.onSave}
  276. />
  277. {this.showDelete && (
  278. <Button
  279. block
  280. loading={this.isDeleting}
  281. text={this.deleteButtonText || t('delete')}
  282. onClick={this.onDelete}
  283. />
  284. )}
  285. </div>
  286. <Popup
  287. vModel={this.showAreaPopup}
  288. position="bottom"
  289. lazyRender={false}
  290. getContainer="body"
  291. >
  292. <Area
  293. ref="area"
  294. loading={!this.areaListLoaded}
  295. value={data.areaCode}
  296. areaList={this.areaList}
  297. onConfirm={this.onAreaConfirm}
  298. onCancel={() => {
  299. this.showAreaPopup = false;
  300. }}
  301. />
  302. </Popup>
  303. </div>
  304. );
  305. }
  306. });