综合办公系统
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

instrumentsList.vue 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. <!--
  2. * @Author: ysh
  3. * @Date: 2025-07-08 14:00:47
  4. * @LastEditors: Please set LastEditors
  5. * @LastEditTime: 2025-08-12 09:32:32
  6. -->
  7. <template>
  8. <view class="device-container">
  9. <!-- 顶部搜索栏 -->
  10. <view class="search-section">
  11. <view class="search-bar">
  12. <view class="search-input-wrapper">
  13. <uni-icons type="search" size="16" color="#999"></uni-icons>
  14. <input class="search-input" v-model="queryParams.name" placeholder="搜索设备名称" @confirm="handleQuery"
  15. @input="handleSearchInput" />
  16. <uni-icons v-if="queryParams.name" type="clear" size="16" color="#999" @click="clearSearch"></uni-icons>
  17. </view>
  18. </view>
  19. <!-- 筛选条件 -->
  20. <view class="filter-section" v-if="showFilter">
  21. <view class="filter-row">
  22. <view class="filter-item">
  23. <text class="filter-label">设备状态</text>
  24. <uni-data-select v-model="queryParams.status" :localdata="statusOptions" placeholder="全部状态"
  25. @change="handleQuery" />
  26. </view>
  27. <view class="filter-item">
  28. <text class="filter-label">设备品牌</text>
  29. <uni-easyinput v-model="queryParams.brand" placeholder="品牌筛选" @confirm="handleQuery" @blur="handleQuery"
  30. :inputBorder="false" :styles="{
  31. backgroundColor: '#fff',
  32. borderRadius: '8px',
  33. border: '1px solid #e0e0e0'
  34. }" />
  35. </view>
  36. </view>
  37. <view class="filter-row">
  38. <view class="filter-item">
  39. <text class="filter-label">出厂编号</text>
  40. <uni-easyinput v-model="queryParams.code" placeholder="编号筛选" @confirm="handleQuery" @blur="handleQuery"
  41. :inputBorder="false" :styles="{
  42. backgroundColor: '#fff',
  43. borderRadius: '8px',
  44. border: '1px solid #e0e0e0'
  45. }" />
  46. </view>
  47. <view class="filter-item">
  48. <text class="filter-label">规格型号</text>
  49. <uni-easyinput v-model="queryParams.series" placeholder="型号筛选" @confirm="handleQuery" @blur="handleQuery"
  50. :inputBorder="false" :styles="{
  51. backgroundColor: '#fff',
  52. borderRadius: '8px',
  53. border: '1px solid #e0e0e0'
  54. }" />
  55. </view>
  56. </view>
  57. </view>
  58. <!-- 筛选按钮 -->
  59. <view class="filter-toggle" @click="toggleFilter">
  60. <text>筛选</text>
  61. <uni-icons :type="showFilter ? 'up' : 'down'" size="14" color="#666"></uni-icons>
  62. </view>
  63. </view>
  64. <!-- 设备列表 -->
  65. <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :down="downOption"
  66. :up="upOption" :fixed="false" :top="10" style="padding: 30rpx;">
  67. <view>
  68. <view v-for="(item, index) in deviceList" :key="index" class="device-card" @click="handleViewDetail(item)">
  69. <view class="device-header">
  70. <view class="device-info">
  71. <text class="device-name">{{ item.name }}</text>
  72. <text class="device-brand">{{ item.brand }}</text>
  73. </view>
  74. <view class="device-status">
  75. <uni-tag :text="statusTypeText(item.status)" :type="statusTypeStyle(item.status)" size="small" />
  76. </view>
  77. </view>
  78. <view class="device-content">
  79. <view class="info-row">
  80. <text class="info-label">设备编号:</text>
  81. <text class="info-value">{{ item.deviceNumber || '暂无' }}</text>
  82. </view>
  83. <view class="info-row">
  84. <text class="info-label">规格型号:</text>
  85. <text class="info-value">{{ item.series || '暂无' }}</text>
  86. </view>
  87. <view class="info-row">
  88. <text class="info-label">出厂编号:</text>
  89. <text class="info-value">{{ item.code || '暂无' }}</text>
  90. </view>
  91. <view class="info-row">
  92. <text class="info-label">购置时间:</text>
  93. <text class="info-value">{{ formatDate(item.acquisitionTime) }}</text>
  94. </view>
  95. <view class="info-row">
  96. <text class="info-label">购买价格:</text>
  97. <text class="info-value price">¥{{ item.cost || '0' }}</text>
  98. </view>
  99. <view class="info-row">
  100. <text class="info-label">单日成本:</text>
  101. <text class="info-value cost">¥{{ item.dayCost || '0' }}</text>
  102. </view>
  103. </view>
  104. <view class="device-actions">
  105. <button class="action-btn view" @click.stop="handleViewDetail(item)">
  106. <uni-icons type="eye" size="14" color="#007AFF"></uni-icons>
  107. <text>查看</text>
  108. </button>
  109. <button class="action-btn edit" @click.stop="handleUpdate(item)" v-show="checkPermi(['oa:device:edit'])">
  110. <uni-icons type="compose" size="14" color="#FF9500"></uni-icons>
  111. <text>编辑</text>
  112. </button>
  113. <button class="action-btn delete" @click.stop="handleDelete(item)" v-show="checkPermi(['oa:device:remove'])">
  114. <uni-icons type="trash" size="14" color="#FF3B30"></uni-icons>
  115. <text>删除</text>
  116. </button>
  117. </view>
  118. </view>
  119. </view>
  120. </mescroll-uni>
  121. <!-- 悬浮按钮 -->
  122. <view class="fab-button" @click="handleAdd" v-if="checkPermi(['oa:device:add'])">
  123. <uni-icons type="plusempty" size="24" color="#fff"></uni-icons>
  124. </view>
  125. <!-- 设备详情弹窗 -->
  126. <uni-popup ref="detailPopup" type="center">
  127. <view class="detail-popup">
  128. <view class="popup-header">
  129. <text class="title">设备详情</text>
  130. <uni-icons type="close" size="20" @click="closeDetailPopup"></uni-icons>
  131. </view>
  132. <view class="popup-body">
  133. <view class="detail-item">
  134. <text class="detail-label">设备名称</text>
  135. <text class="detail-value">{{ currentDevice.name }}</text>
  136. </view>
  137. <view class="detail-item">
  138. <text class="detail-label">设备品牌</text>
  139. <text class="detail-value">{{ currentDevice.brand }}</text>
  140. </view>
  141. <view class="detail-item">
  142. <text class="detail-label">设备编号</text>
  143. <text class="detail-value">{{ currentDevice.deviceNumber }}</text>
  144. </view>
  145. <view class="detail-item">
  146. <text class="detail-label">规格型号</text>
  147. <text class="detail-value">{{ currentDevice.series }}</text>
  148. </view>
  149. <view class="detail-item">
  150. <text class="detail-label">出厂编号</text>
  151. <text class="detail-value">{{ currentDevice.code }}</text>
  152. </view>
  153. <view class="detail-item">
  154. <text class="detail-label">设备状态</text>
  155. <text class="detail-value">
  156. <uni-tag :text="statusTypeText(currentDevice.status)" :type="statusTypeStyle(currentDevice.status)"
  157. size="small" />
  158. </text>
  159. </view>
  160. <view class="detail-item">
  161. <text class="detail-label">购置时间</text>
  162. <text class="detail-value">{{ formatDate(currentDevice.acquisitionTime) }}</text>
  163. </view>
  164. <view class="detail-item">
  165. <text class="detail-label">购买价格</text>
  166. <text class="detail-value price">¥{{ currentDevice.cost }}</text>
  167. </view>
  168. <view class="detail-item">
  169. <text class="detail-label">预计使用年限</text>
  170. <text class="detail-value">{{ currentDevice.expectLife }}年</text>
  171. </view>
  172. <view class="detail-item">
  173. <text class="detail-label">单日成本</text>
  174. <text class="detail-value cost">¥{{ currentDevice.dayCost }}</text>
  175. </view>
  176. <view class="detail-item">
  177. <text class="detail-label">存放地点</text>
  178. <text class="detail-value">{{ currentDevice.place || '暂无' }}</text>
  179. </view>
  180. <view class="detail-item">
  181. <text class="detail-label">备注</text>
  182. <text class="detail-value">{{ currentDevice.remark || '暂无' }}</text>
  183. </view>
  184. </view>
  185. <view class="popup-footer">
  186. <button class="popup-btn cancel" @click="closeDetailPopup">关闭</button>
  187. <button class="popup-btn primary" @click="handleViewLogs">查看记录</button>
  188. </view>
  189. </view>
  190. </uni-popup>
  191. <!-- 新增/编辑设备弹窗 -->
  192. <uni-popup ref="formPopup" type="center">
  193. <view class="form-popup">
  194. <view class="popup-header">
  195. <text class="title">{{ isEdit ? '编辑设备' : '新增设备' }}</text>
  196. <uni-icons type="close" size="20" @click="closeFormPopup"></uni-icons>
  197. </view>
  198. <scroll-view scroll-y="true" class="popup-body">
  199. <view class="form-item">
  200. <text class="label">设备品牌 *</text>
  201. <uni-easyinput v-model="form.brand" placeholder="请输入设备品牌" />
  202. </view>
  203. <view class="form-item">
  204. <text class="label">设备名称 *</text>
  205. <uni-easyinput v-model="form.name" placeholder="请输入设备名称" />
  206. </view>
  207. <view class="form-item">
  208. <text class="label">设备类别</text>
  209. <uni-data-select v-model="form.type" :localdata="typeOptions" placeholder="请选择设备类别" />
  210. </view>
  211. <view class="form-item">
  212. <text class="label">规格型号</text>
  213. <uni-easyinput v-model="form.series" placeholder="请输入规格型号" />
  214. </view>
  215. <view class="form-item">
  216. <text class="label">购置时间</text>
  217. <view class="date-picker" @click="openDatePicker">
  218. <text v-if="form.acquisitionTime">{{ form.acquisitionTime }}</text>
  219. <text v-else class="placeholder">请选择购置时间</text>
  220. </view>
  221. </view>
  222. <view class="form-item">
  223. <text class="label">存放地点</text>
  224. <uni-easyinput v-model="form.place" placeholder="请输入存放地点" />
  225. </view>
  226. <view class="form-item">
  227. <text class="label">购买价格(元)</text>
  228. <uni-easyinput v-model="form.cost" type="number" placeholder="请输入购买价格" />
  229. </view>
  230. <view class="form-item">
  231. <text class="label">预计使用年限(年)</text>
  232. <uni-easyinput v-model="form.expectLife" type="number" placeholder="请输入使用年限" />
  233. </view>
  234. <view class="form-item">
  235. <text class="label">出厂编号</text>
  236. <uni-easyinput v-model="form.code" type="textarea" placeholder="请输入出厂编号" :autoHeight="true" />
  237. </view>
  238. <view class="form-item">
  239. <text class="label">单日成本(元)</text>
  240. <uni-easyinput v-model="form.dayCost" type="number" placeholder="请输入单日成本" />
  241. </view>
  242. <view class="form-item">
  243. <text class="label">管理部门</text>
  244. <uni-data-select v-model="form.manageDept" :localdata="deptOptions" placeholder="请选择管理部门" />
  245. </view>
  246. <view class="form-item">
  247. <text class="label">设备状态</text>
  248. <uni-data-select v-model="form.status" :localdata="statusOptions" placeholder="请选择设备状态" />
  249. </view>
  250. <view class="form-item">
  251. <text class="label">设备编号</text>
  252. <uni-easyinput v-model="form.deviceNumber" placeholder="请输入设备编号" />
  253. </view>
  254. <view class="form-item">
  255. <text class="label">备注</text>
  256. <uni-easyinput v-model="form.remark" type="textarea" placeholder="请输入备注信息" :autoHeight="true" />
  257. </view>
  258. </scroll-view>
  259. <view class="popup-footer">
  260. <button class="popup-btn cancel" @click="closeFormPopup">取消</button>
  261. <button class="popup-btn primary" @click="submitForm">确定</button>
  262. </view>
  263. </view>
  264. </uni-popup>
  265. <!-- 日期选择器 -->
  266. <uv-calendar ref="calendar" mode="single" @confirm="confirmDate"></uv-calendar>
  267. </view>
  268. </template>
  269. <script>
  270. import { listDevice, getDevice, delDevice, addDevice, updateDevice } from "@/api/oa/device/device";
  271. import { checkPermi, checkRole } from "@/utils/permission";
  272. import MescrollMixin from '@/uni_modules/mescroll/components/mescroll-uni/mescroll-mixins.js'
  273. export default {
  274. name: "InstrumentsList",
  275. mixins: [MescrollMixin],
  276. data() {
  277. return {
  278. // mescroll相关
  279. mescroll: null, // mescroll实例
  280. downOption: {
  281. auto: true,
  282. textOutOffset: '下拉刷新',
  283. textLoading: '加载中...'
  284. },
  285. upOption: {
  286. auto: false,
  287. page: { num: 1, size: 20 },
  288. noMoreSize: 5,
  289. empty: { tip: '暂无设备数据' },
  290. textNoMore: '~ 没有更多数据了 ~'
  291. },
  292. // 筛选相关
  293. showFilter: false,
  294. // 列表数据
  295. deviceList: [],
  296. total: 0,
  297. // 查询参数
  298. queryParams: {
  299. pageNum: 1,
  300. pageSize: 20,
  301. name: '',
  302. status: '',
  303. brand: '',
  304. code: '',
  305. series: ''
  306. },
  307. // 表单数据
  308. form: {},
  309. isEdit: false,
  310. // 当前查看的设备
  311. currentDevice: {},
  312. // 选项数据
  313. statusOptions: [
  314. { value: '0', text: '被领用' },
  315. { value: '1', text: '可领用' },
  316. { value: '2', text: '维修中' },
  317. { value: '3', text: '已停用' },
  318. { value: '4', text: '已报废' }
  319. ],
  320. typeOptions: [
  321. { value: '仪器设备', text: '仪器设备' },
  322. { value: '办公设备', text: '办公设备' }
  323. ],
  324. deptOptions: []
  325. };
  326. },
  327. onLoad() {
  328. this.getDeptOptions();
  329. },
  330. methods: {
  331. checkPermi,
  332. checkRole,
  333. // 加载数据
  334. async loadData() {
  335. try {
  336. const response = await listDevice(this.queryParams);
  337. return { data: response.rows || [], total: response.total || 0 };
  338. } catch (error) {
  339. console.error('获取设备列表失败:', error);
  340. return { data: [], error: true };
  341. }
  342. },
  343. mescrollInit(mescroll) {
  344. this.mescroll = mescroll;
  345. },
  346. // mescroll下拉刷新回调
  347. async downCallback() {
  348. this.deviceList = []; // 清空列表
  349. this.queryParams.pageNum = 1;
  350. const res = await this.loadData();
  351. if (res.error) {
  352. this.mescroll.endErr();
  353. } else {
  354. this.deviceList = res.data;
  355. this.mescroll.endSuccess(res.data.length, res.total);
  356. }
  357. uni.stopPullDownRefresh();
  358. },
  359. // mescroll上拉加载回调
  360. async upCallback(page) {
  361. this.queryParams.pageNum = page.num;
  362. const res = await this.loadData();
  363. if (res.error) {
  364. this.mescroll.endErr();
  365. } else {
  366. this.deviceList = this.deviceList.concat(res.data);
  367. this.mescroll.endSuccess(res.data.length, res.total);
  368. }
  369. },
  370. // 获取设备列表(保留原有方法,用于其他操作后刷新)
  371. async getList() {
  372. try {
  373. const response = await listDevice(this.queryParams);
  374. if (this.queryParams.pageNum === 1) {
  375. this.deviceList = response.rows || [];
  376. } else {
  377. this.deviceList = [...this.deviceList, ...(response.rows || [])];
  378. }
  379. this.total = response.total || 0;
  380. } catch (error) {
  381. console.error('获取设备列表失败:', error);
  382. uni.showToast({
  383. title: '获取数据失败',
  384. icon: 'none'
  385. });
  386. }
  387. },
  388. // 获取部门选项
  389. async getDeptOptions() {
  390. try {
  391. // 这里需要根据实际的部门API进行调整
  392. const deptList = this.$store.state.user.deptList || [];
  393. this.deptOptions = deptList.map(dept => ({
  394. value: dept.deptId,
  395. text: dept.deptName
  396. }));
  397. } catch (error) {
  398. console.error('获取部门列表失败:', error);
  399. }
  400. },
  401. // 搜索相关方法
  402. handleSearchInput() {
  403. // 可以在这里添加防抖搜索
  404. },
  405. handleQuery() {
  406. this.queryParams.pageNum = 1;
  407. this.downCallback();
  408. },
  409. clearSearch() {
  410. this.queryParams.name = '';
  411. this.handleQuery();
  412. },
  413. toggleFilter() {
  414. this.showFilter = !this.showFilter;
  415. },
  416. // 设备操作方法
  417. handleViewDetail(item) {
  418. this.currentDevice = item;
  419. this.$refs.detailPopup.open();
  420. },
  421. closeDetailPopup() {
  422. this.$refs.detailPopup.close();
  423. },
  424. handleViewLogs() {
  425. this.closeDetailPopup();
  426. },
  427. handleAdd() {
  428. this.isEdit = false;
  429. this.resetForm();
  430. this.$refs.formPopup.open();
  431. },
  432. async handleUpdate(item) {
  433. this.isEdit = true;
  434. try {
  435. const response = await getDevice(item.deviceId);
  436. this.form = response.data || {};
  437. this.$refs.formPopup.open();
  438. } catch (error) {
  439. console.error('获取设备详情失败:', error);
  440. uni.showToast({
  441. title: '获取设备详情失败',
  442. icon: 'none'
  443. });
  444. }
  445. },
  446. closeFormPopup() {
  447. this.$refs.formPopup.close();
  448. },
  449. async handleDelete(item) {
  450. uni.showModal({
  451. title: '确认删除',
  452. content: `是否确认删除设备"${item.name}"?`,
  453. success: async (res) => {
  454. if (res.confirm) {
  455. try {
  456. await delDevice(item.deviceId);
  457. uni.showToast({
  458. title: '删除成功',
  459. icon: 'success'
  460. });
  461. this.downCallback(); // 使用mescroll刷新
  462. } catch (error) {
  463. console.error('删除设备失败:', error);
  464. uni.showToast({
  465. title: '删除失败',
  466. icon: 'none'
  467. });
  468. }
  469. }
  470. }
  471. });
  472. },
  473. // 表单相关方法
  474. resetForm() {
  475. this.form = {
  476. deviceId: null,
  477. brand: '',
  478. name: '',
  479. type: '仪器设备',
  480. series: '',
  481. acquisitionTime: '',
  482. place: '',
  483. cost: '',
  484. expectLife: '',
  485. code: '',
  486. dayCost: '',
  487. manageDept: '',
  488. status: '1',
  489. deviceNumber: '',
  490. remark: ''
  491. };
  492. },
  493. openDatePicker() {
  494. this.$refs.calendar.open();
  495. },
  496. confirmDate(e) {
  497. this.form.acquisitionTime = e.result;
  498. },
  499. async submitForm() {
  500. // 表单验证
  501. if (!this.form.name) {
  502. uni.showToast({
  503. title: '请输入设备名称',
  504. icon: 'none'
  505. });
  506. return;
  507. }
  508. try {
  509. if (this.isEdit) {
  510. await updateDevice(this.form);
  511. uni.showToast({
  512. title: '修改成功',
  513. icon: 'success'
  514. });
  515. } else {
  516. await addDevice(this.form);
  517. uni.showToast({
  518. title: '新增成功',
  519. icon: 'success'
  520. });
  521. }
  522. this.closeFormPopup();
  523. this.downCallback(); // 使用mescroll刷新
  524. } catch (error) {
  525. console.error('保存设备失败:', error);
  526. uni.showToast({
  527. title: '保存失败',
  528. icon: 'none'
  529. });
  530. }
  531. },
  532. // 工具方法
  533. formatDate(date) {
  534. if (!date) return '暂无';
  535. return new Date(date).toLocaleDateString();
  536. },
  537. statusTypeText(status) {
  538. const statusMap = {
  539. '0': '被领用',
  540. '1': '可领用',
  541. '2': '维修中',
  542. '3': '已停用',
  543. '4': '已报废'
  544. };
  545. return statusMap[status] || '未知';
  546. },
  547. statusTypeStyle(status) {
  548. const styleMap = {
  549. '0': 'warning',
  550. '1': 'success',
  551. '2': 'primary',
  552. '3': 'error',
  553. '4': 'info'
  554. };
  555. return styleMap[status] || 'default';
  556. },
  557. hasPermission(permission) {
  558. // 这里需要根据实际的权限系统进行调整
  559. return true;
  560. }
  561. }
  562. };
  563. </script>
  564. <style lang="scss" scoped>
  565. .device-container {
  566. height: 100vh;
  567. background-color: #f5f5f5;
  568. display: flex;
  569. flex-direction: column;
  570. }
  571. .search-section {
  572. background: #fff;
  573. padding: 10px 15px;
  574. border-bottom: 1px solid #eee;
  575. }
  576. .search-bar {
  577. margin-bottom: 10px;
  578. }
  579. .search-input-wrapper {
  580. display: flex;
  581. align-items: center;
  582. background: #f8f8f8;
  583. border-radius: 20px;
  584. padding: 8px 15px;
  585. .search-input {
  586. flex: 1;
  587. margin-left: 8px;
  588. font-size: 14px;
  589. }
  590. }
  591. .filter-section {
  592. background: #f8f8f8;
  593. border-radius: 8px;
  594. padding: 10px;
  595. margin-bottom: 10px;
  596. }
  597. .filter-row {
  598. display: flex;
  599. gap: 10px;
  600. margin-bottom: 10px;
  601. &:last-child {
  602. margin-bottom: 0;
  603. }
  604. }
  605. .filter-item {
  606. flex: 1;
  607. .filter-label {
  608. font-size: 13px;
  609. color: #666;
  610. margin-bottom: 8px;
  611. display: block;
  612. font-weight: 500;
  613. }
  614. // uni-easyinput 样式
  615. :deep(.uni-easyinput) {
  616. height: 40px !important;
  617. border-radius: 8px !important;
  618. .uni-easyinput__content {
  619. height: 100% !important;
  620. border-radius: 8px !important;
  621. border: 1px solid #e0e0e0 !important;
  622. background: #fff !important;
  623. transition: all 0.3s ease;
  624. &:focus-within {
  625. border-color: #6366f1 !important;
  626. box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1) !important;
  627. }
  628. }
  629. .uni-easyinput__content-input {
  630. padding: 0 12px !important;
  631. font-size: 14px !important;
  632. color: #333 !important;
  633. &::placeholder {
  634. color: #a0a0a0 !important;
  635. font-size: 13px !important;
  636. }
  637. }
  638. }
  639. }
  640. .filter-toggle {
  641. display: flex;
  642. align-items: center;
  643. justify-content: center;
  644. gap: 5px;
  645. font-size: 14px;
  646. color: #666;
  647. padding: 8px;
  648. }
  649. .device-card {
  650. background: #fff;
  651. border-radius: 12px;
  652. margin-bottom: 15px;
  653. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  654. overflow: hidden;
  655. }
  656. .device-header {
  657. display: flex;
  658. justify-content: space-between;
  659. align-items: center;
  660. padding: 15px;
  661. border-bottom: 1px solid #f0f0f0;
  662. background: linear-gradient(90deg, #e0e7ff 0%, #f8fafc 100%);
  663. }
  664. .device-info {
  665. flex: 1;
  666. .device-name {
  667. font-size: 16px;
  668. font-weight: 600;
  669. color: #333;
  670. display: block;
  671. margin-bottom: 4px;
  672. }
  673. .device-brand {
  674. font-size: 12px;
  675. color: #666;
  676. }
  677. }
  678. .device-content {
  679. padding: 15px;
  680. }
  681. .info-row {
  682. display: flex;
  683. margin-bottom: 8px;
  684. &:last-child {
  685. margin-bottom: 0;
  686. }
  687. .info-label {
  688. width: 80px;
  689. font-size: 13px;
  690. color: #666;
  691. flex-shrink: 0;
  692. }
  693. .info-value {
  694. flex: 1;
  695. font-size: 13px;
  696. color: #333;
  697. &.price,
  698. &.cost {
  699. color: #ff6b35;
  700. font-weight: 600;
  701. }
  702. }
  703. }
  704. .device-actions {
  705. display: flex;
  706. border-top: 1px solid #f0f0f0;
  707. .action-btn {
  708. flex: 1;
  709. display: flex;
  710. align-items: center;
  711. justify-content: center;
  712. gap: 4px;
  713. padding: 5px;
  714. background: none;
  715. border: none;
  716. font-size: 13px;
  717. &.view {
  718. color: #007AFF;
  719. }
  720. &.edit {
  721. color: #FF9500;
  722. border-left: 1px solid #f0f0f0;
  723. }
  724. &.delete {
  725. color: #FF3B30;
  726. border-left: 1px solid #f0f0f0;
  727. }
  728. }
  729. }
  730. .fab-button {
  731. position: fixed;
  732. right: 20px;
  733. bottom: 30px;
  734. width: 56px;
  735. height: 56px;
  736. background: #6366f1;
  737. border-radius: 50%;
  738. display: flex;
  739. align-items: center;
  740. justify-content: center;
  741. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
  742. z-index: 999;
  743. }
  744. // 弹窗样式
  745. .detail-popup,
  746. .form-popup {
  747. background: #fff;
  748. border-radius: 12px;
  749. width: 90vw;
  750. max-height: 80vh;
  751. display: flex;
  752. flex-direction: column;
  753. }
  754. .popup-header {
  755. display: flex;
  756. justify-content: space-between;
  757. align-items: center;
  758. padding: 20px;
  759. border-bottom: 1px solid #f0f0f0;
  760. .title {
  761. font-size: 18px;
  762. font-weight: 600;
  763. color: #333;
  764. }
  765. }
  766. .popup-body {
  767. flex: 1;
  768. padding: 20px;
  769. max-height: 60vh;
  770. }
  771. .detail-item {
  772. display: flex;
  773. margin-bottom: 15px;
  774. .detail-label {
  775. width: 80px;
  776. font-size: 14px;
  777. color: #666;
  778. flex-shrink: 0;
  779. }
  780. .detail-value {
  781. flex: 1;
  782. font-size: 14px;
  783. color: #333;
  784. &.price,
  785. &.cost {
  786. color: #ff6b35;
  787. font-weight: 600;
  788. }
  789. }
  790. }
  791. .form-item {
  792. margin-bottom: 20px;
  793. .label {
  794. font-size: 14px;
  795. color: #333;
  796. margin-bottom: 8px;
  797. display: block;
  798. }
  799. // uni-easyinput 样式
  800. :deep(.uni-easyinput) {
  801. .uni-easyinput__content {
  802. height: 45px !important;
  803. border: 1px solid #ddd !important;
  804. border-radius: 8px !important;
  805. background: #f8f8f8 !important;
  806. transition: all 0.3s ease;
  807. &:focus-within {
  808. border-color: #667eea !important;
  809. background: #fff !important;
  810. box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1) !important;
  811. }
  812. }
  813. .uni-easyinput__content-input {
  814. padding: 0 12px !important;
  815. font-size: 14px !important;
  816. color: #333 !important;
  817. &::placeholder {
  818. color: #999 !important;
  819. }
  820. }
  821. // textarea 样式
  822. &.uni-easyinput--textarea {
  823. .uni-easyinput__content {
  824. height: auto !important;
  825. min-height: 80px !important;
  826. }
  827. .uni-easyinput__content-textarea {
  828. padding: 12px !important;
  829. font-size: 14px !important;
  830. color: #333 !important;
  831. min-height: 60px !important;
  832. &::placeholder {
  833. color: #999 !important;
  834. }
  835. }
  836. }
  837. }
  838. .date-picker {
  839. padding: 12px;
  840. border: 1px solid #ddd;
  841. border-radius: 8px;
  842. background: #f8f8f8;
  843. font-size: 14px;
  844. .placeholder {
  845. color: #999;
  846. }
  847. }
  848. }
  849. .popup-footer {
  850. display: flex;
  851. gap: 15px;
  852. padding: 10px;
  853. border-top: 1px solid #f0f0f0;
  854. .popup-btn {
  855. flex: 1;
  856. padding: 1px;
  857. border-radius: 8px;
  858. font-size: 16px;
  859. border: none;
  860. &.cancel {
  861. background: #f5f5f5;
  862. color: #666;
  863. }
  864. &.primary {
  865. background: #6366f1;
  866. color: #fff;
  867. }
  868. }
  869. }
  870. </style>