sh пре 4 година
родитељ
комит
bc7e5339bc
12 измењених фајлова са 1108 додато и 178 уклоњено
  1. 0 1
      .env.production
  2. 2 0
      .env.staging
  3. 1 1
      .env.test
  4. 81 0
      ci/deploy/deploy.sh
  5. 67 0
      ci/nginx/pre.conf
  6. 49 0
      public/index.html
  7. 352 0
      src/api/op.js
  8. 78 0
      src/api/rest.js
  9. 1 1
      src/components/Pagination/index.vue
  10. 83 0
      src/utils/request2.js
  11. 256 0
      src/views/unit/index-bak.vue
  12. 138 175
      src/views/unit/index.vue

+ 0 - 1
.env.production

@@ -3,7 +3,6 @@ ENV = 'production'
 PORT = '9531'
 
 # base api
-# VUE_APP_BASE_API = '//feuc.liveplus.online'
 VUE_APP_BASE_API = ''
 
 # h5 url

+ 2 - 0
.env.staging

@@ -3,6 +3,8 @@ NODE_ENV = production
 # just a flag
 ENV = 'staging'
 
+VUE_APP_BASE_API = ''
+
 # base api
 VUE_APP_BASE_API = '/stage-api'
 

+ 1 - 1
.env.test

@@ -3,7 +3,7 @@ ENV = 'production'
 PORT = '9532'
 
 # base api
-VUE_APP_BASE_API = '//feuc-test.liveplus.online'
+VUE_APP_BASE_API = ''
 
 # h5 url
 VUE_APP_H5_URL = 'http://h5.liveplus.online/static-web/feuc-test'

+ 81 - 0
ci/deploy/deploy.sh

@@ -0,0 +1,81 @@
+
+# if [ $# != 3 ] ; then
+#   echo "usage: $0 app env host"
+#   echo " e.g.: $0 yywy test root@59.110.65.177"
+#   exit 1;
+# fi
+
+workdir=$(pwd)
+
+app=$1
+env=$2
+host=$3
+
+app="feuc"
+
+echo "运行服务器:
+1) 测试[59.110.65.177]
+2) 生产[47.93.11.204]"
+read -p "请选择运行环境对应的数字: " n
+case $n in
+  1)
+    host="root@59.110.65.177"
+  ;;
+  2)
+    host="root@47.93.11.204"
+  ;;
+  *)
+    echo "请选择正确的运行服务器"
+    exit 1;
+  ;;
+esac
+
+echo "运行环境:
+1) dev
+2) test
+3) pre
+4) prod"
+read -p "请输入运行环境对应的数字: " n
+case $n in
+  1)
+    env="dev"
+  ;;
+  2)
+    env="test"
+  ;;
+  3)
+    env="pre"
+  ;;
+  4)
+    env="prod"
+  ;;
+  *)
+    echo "请选择正确的运行环境"
+    exit 1;
+  ;;
+esac
+
+echo "\033[31m 开始部署 ${app}-${env} 到 ${host}\033[0m"
+
+ssh ${host} <<- EOF
+  rm -rf /opt/${app}/temp/admin;
+  mkdir -p /opt/${app}/temp/admin;
+EOF
+scp -r ${workdir}/dist/* ${host}:/opt/${app}/temp/admin
+ssh ${host} <<- EOF
+  rm -rf /opt/${app}/${env}/admin;
+  mkdir -p /opt/${app}/${env}/admin;
+  cp -r /opt/${app}/temp/admin/* /opt/${app}/${env}/admin;
+EOF
+
+# nginx
+
+ssh ${host} <<- EOF
+  mkdir -p /opt/nginx/ssl;
+EOF
+scp ${workdir}/ci/nginx/${env}.conf ${host}:/opt/nginx/${app}-${env}.conf
+ssh ${host} <<- EOF
+  /usr/sbin/nginx -s reload;
+EOF
+
+echo "\033[32m 部署完成 \033[0m"

+ 67 - 0
ci/nginx/pre.conf

@@ -0,0 +1,67 @@
+upstream feuc_pre_api_server {
+    ip_hash;
+    server 127.0.0.1:17001;
+}
+
+server {
+  listen 80;
+  server_name admin.feuc.pre.liveplus.online;
+  # rewrite ^(.*)$ https://${server_name}$1 permanent; 
+
+  server_tokens        off;
+
+    client_max_body_size 100m;
+
+    location = /robots.txt {
+      add_header Content-Type text/plain;
+      return 200 "User-agent: *\nDisallow: /\n";
+    }
+
+    location /api {
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+      proxy_set_header Host $http_host;
+      proxy_redirect off;
+      proxy_pass http://feuc_pre_api_server;
+    }
+
+    location / {
+      root /opt/feuc/pre/admin;
+      index index.html index.htm;
+
+      try_files $uri $uri/ /index.html;
+    }
+}
+
+# server {
+#     listen 443;
+#     server_name admin.feuc.pre.liveplus.online;
+
+#     ssl on;
+#     ssl_certificate      /opt/nginx/ssl/feuc_pre_admin.pem;
+#     ssl_certificate_key  /opt/nginx/ssl/feuc_pre_admin.key;
+#     ssl_session_cache    shared:SSL:1m;
+#     ssl_session_timeout  5m;
+    
+#     server_tokens        off;
+
+#     client_max_body_size 100m;
+
+#     location = /robots.txt {
+#       add_header Content-Type text/plain;
+#       return 200 "User-agent: *\nDisallow: /\n";
+#     }
+
+#     location /api {
+#       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+#       proxy_set_header Host $http_host;
+#       proxy_redirect off;
+#       proxy_pass http://feuc_pre_api_server;
+#     }
+
+#     location / {
+#       root /opt/feuc/pre/admin;
+#       index index.html index.htm;
+
+#       try_files $uri $uri/ /index.html;
+#     }
+# }

+ 49 - 0
public/index.html

@@ -14,4 +14,53 @@
     <div id="app"></div>
     <!-- built files will be auto injected -->
   </body>
+
+
+<style>
+.w-50 {width: 50%;}
+.w-60 {width: 60%;}
+.w-70 {width: 70%;}
+.w-80 {width: 80%;}
+.w-90 {width: 90%;}
+.w-100 {width: 100%;}
+.top-0 {margin:0;vertical-align:top;}
+.top-1 {margin:0 auto;margin-top: 1vh;vertical-align:top;}
+.top-2 {margin:0 auto;margin-top: 2vh;vertical-align:top;}
+.top-3 {margin:0 auto;margin-top: 3vh;vertical-align:top;}
+.top-4 {margin:0 auto;margin-top: 4vh;vertical-align:top;}
+.top-5 {margin:0 auto;margin-top: 5vh;vertical-align:top;}
+.top-10 {margin:0 auto;margin-top: 10vh;vertical-align:top;}
+.z-10000 {z-index:10000}
+
+.pointer {
+  cursor: pointer
+}
+.el-table th {
+  padding: 3px 0; background-color: #DCDCDC;
+}
+.pagination-container {
+  margin-top: 5px; margin-left: 0px;
+}
+.el-dialog__body {
+  padding: 10px 15px 0px 15px;
+}
+.el-table td {
+    padding: 1px 1px;
+}
+.app-container {
+  padding: 5px;
+}
+.filter-container {
+  padding-bottom: 5px;
+}
+.filter-container .filter-item {
+  margin-bottom: 0px;
+}
+.el-tabs__header {
+  margin: 0 0 10px;
+}
+.el-card__body {
+  padding: 15px 5px 5px 5px;
+}
+</style>
 </html>

+ 352 - 0
src/api/op.js

@@ -0,0 +1,352 @@
+import { fGet, fSave, fPut, fDelete } from '@/api/rest'
+
+function hasArea(obj, key) {
+  const k = key || 'area'
+  return obj[k] instanceof Array
+}
+
+function Options(options, defaultOptions) {
+  return Object.assign(defaultOptions || {}, options || {})
+}
+
+function fnList(options) {
+  const opt = typeof options === 'string' ? { list: options } : Options(options, { list: 'main' })
+
+  const listObj = this.self.list[opt.list]
+  if (listObj === undefined) {
+    return
+  }
+
+  // deal area query parameter
+  if (hasArea(listObj)) {
+    if (listObj.area.length > 0) {
+      listObj.query.province_id = listObj.area[0]
+    }
+    if (listObj.area.length > 1) {
+      listObj.query.city_id = listObj.area[1]
+    }
+    if (listObj.area.length > 2) {
+      listObj.query.area_id = listObj.area[2]
+    }
+  }
+
+  opt.before = opt.before || listObj.before
+  opt.after = opt.after || listObj.after
+  opt.error = opt.error || listObj.error
+
+  listObj.loading = true
+  const url = opt.url || listObj.url || this.self.URI
+  const params = opt.before instanceof Function ? opt.before({ ...listObj.query }) : { ...listObj.query }
+  fGet(url, params).then(res => {
+    const { total, data } = res
+    listObj.total = total
+    listObj.list = data.map(v => {
+      if (listObj.render instanceof Function) {
+        return listObj.render(v)
+      }
+      return v
+    })
+    opt.after instanceof Function && opt.after(res)
+  }).finally(() => {
+    listObj.loading = false
+  })
+}
+
+function fnFilter(options) {
+  const opt = Options(options, { list: 'main' })
+  this.self.list[opt.list].query ? this.self.list[opt.list].query.page = 1 : undefined
+  this.list(opt)
+}
+
+function fnReset(options) {
+  const opt = Options(options, { list: 'main' })
+
+  const listObj = this.self.list[opt.list]
+  listObj.query = listObj.query || {}
+  listObj.query = Object.assign({ page: 1, limit: listObj.query.limit || 20 }, opt.data || {})
+
+  // deal area query parameter
+  if (hasArea(listObj)) {
+    listObj.area = []
+  }
+
+  this.list(opt)
+}
+
+function fnCreate(options) {
+  const opt = Options(options, { form: 'form', title: '新增' })
+
+  opt.before instanceof Function && opt.before()
+  const formObj = this.self.dlg[opt.form]
+
+  // deal area parameter
+  if (hasArea(formObj)) {
+    formObj.area = []
+  }
+
+  formObj.title = opt.title || '新增'
+  formObj.data = opt.data || {}
+  formObj.visible = true
+  this.self.$nextTick(() => {
+    this.self.$refs[opt.form].clearValidate()
+  })
+}
+
+function fnEdit(options) {
+  const opt = Options(options, { form: 'form' })
+  const formObj = this.self.dlg[opt.form]
+  // deal area parameter
+  if (hasArea(formObj)) {
+    formObj.area = [opt.row.province_id, opt.row.city_id, opt.row.area_id]
+  }
+  formObj.title = opt.title === undefined || opt.row[opt.title] === undefined ? (opt.title || '编辑') : `编辑 - ${opt.row[opt.title]}`
+  formObj.data = Object.assign({}, opt.row)
+  formObj.data = Object.assign(formObj.data, opt.data || {})
+  opt.before instanceof Function && opt.before(formObj.data)
+  formObj.visible = true
+  this.self.$nextTick(() => {
+    this.self.$refs[opt.form].clearValidate()
+  })
+}
+
+function fnDelete(options) {
+  const opt = Options(options, { list: 'main', form: 'form' })
+  const title = opt.title === undefined || opt.row[opt.title] === undefined ? (opt.title || '') : ` - ${opt.row[opt.title]}`
+  const listObj = this.self.list[opt.list] || {}
+  this.self.$confirm(`您是否确认要删除${title}`, '删除确认').then(() => {
+    const url = opt.url || listObj.url || this.self.URI
+    fDelete(url, opt.row).then(res => {
+      this.list(opt.reload)
+    })
+  }).catch(() => {})
+}
+
+function fnSave(options) {
+  const opt = Options(options, { form: 'form', data: {}, reload: 'main' })
+  const thiz = this
+  const self = this.self
+  const formObj = self.dlg[opt.form] || {}
+
+  function innerSave(data) {
+    if (typeof opt.before === 'function') {
+      const beforeResult = opt.before(data)
+      if (typeof beforeResult === 'boolean' && !beforeResult) {
+        return
+      }
+    }
+    formObj.saveLoading = true
+    const url = opt.url || self.URI
+    fSave(url, data).then(res => {
+      self.$message.success('保存成功')
+      typeof opt.reload === 'string' && thiz.list(opt.reload)
+      typeof opt.success === 'function' && opt.success(res)
+      formObj.visible = false
+    }).catch(err => {
+      typeof opt.error === 'function' && opt.error(err)
+    }).finally(() => {
+      formObj.saveLoading = false
+    })
+  }
+
+  if (typeof opt.form === 'string' && opt.form.length > 0) {
+    if (formObj.data === undefined) {
+      const err = `form ${opt.form} data not exist.`
+      self.$message.error(err)
+      typeof opt.error === 'function' && opt.error(err)
+      return
+    }
+    const formRef = self.$refs[opt.form]
+    if (formRef === undefined || formRef.validate === undefined) {
+      const err = `form ${opt.form} element not exist.`
+      self.$message.error(err)
+      typeof opt.error === 'function' && opt.error(err)
+      return
+    }
+    formRef.validate((valid) => {
+      if (valid) {
+        const otherData = opt.data || {}
+        const params = { ...formObj.data, ...otherData }
+        // deal area parameter
+        if (hasArea(formObj) && formObj.area.length > 2) {
+          params.province_id = formObj.area[0]
+          params.city_id = formObj.area[1]
+          params.area_id = formObj.area[2]
+        }
+        innerSave(params)
+      }
+    })
+  } else {
+    const params = opt.data || {}
+    innerSave(params)
+  }
+}
+
+function fnUpdate(options) {
+  const opt = Options(options, { row: {}, data: {}})
+  const params = { ...opt.row, ...opt.data }
+  const url = opt.url || this.self.URI
+  fSave(url, params).then(res => {
+    typeof opt.success === 'function' ? opt.success(res) : this.list(opt.reload)
+  })
+}
+
+function fnMultiSelect(options) {
+  const opt = options || {}
+  const listObj = this.self.list[opt.list || 'main']
+  if (listObj === undefined) {
+    return
+  }
+  listObj.selectedRows = opt.rows || []
+}
+
+// 批量操作
+function fnBatchOp(options) {
+  const opt = Options(options, { params: {}})
+  const list = opt.list || 'main'
+  const listObj = this.self.list[list]
+  if (listObj === undefined) {
+    return
+  }
+  const selectedRows = listObj.selectedRows || []
+  const ids = selectedRows.map(v => {
+    return v.id
+  })
+  if (ids.length === 0) {
+    this.self.$message.error('请先选择要操作的记录')
+    return
+  }
+  const op = opt.op || '操作'
+  this.self.$confirm(`您是否确认对选择的${ids.length}个记录进行${op}`, '提示').then(() => {
+    const url = opt.url || listObj.url || this.self.URI
+    const params = { ...opt.params }
+    if (typeof opt.ids !== 'boolean' || !opt.ids) {
+      params.ids = ids
+    }
+    fPut(url, params).then(res => {
+      if (typeof opt.success === 'function') {
+        opt.success(res)
+      } else {
+        this.self.$message.success('操作成功')
+        const reload = opt.reload || list
+        this.list({ list: reload })
+      }
+    })
+  })
+}
+
+function fnGetSelected(options) {
+  const opt = Options(options, {})
+  const list = opt.list || 'main'
+  const listObj = this.self.list[list]
+  if (listObj === undefined) {
+    return []
+  }
+  const selectedRows = listObj.selectedRows || []
+  if (typeof opt.render === 'function') {
+    return selectedRows.map(v => {
+      return opt.render(v)
+    })
+  }
+  return selectedRows
+}
+
+function fnLoadAreaList(node, resolve) {
+  const { level, value } = node
+  fGet('/manager/areas', { pid: level === 0 ? 1 : value }).then(res => {
+    const { data } = res
+    const nodes = data.map(v => {
+      return {
+        value: v.id,
+        label: v.name,
+        leaf: level + 1 >= 3
+      }
+    })
+    resolve(nodes)
+  })
+}
+
+function fnGetUploadProps(options) {
+  const self = this.self
+  const params = Object.assign({
+    uploadUrl: '',
+    uploadParams: {},
+    viewUrl: '',
+    default: 'https://dummyimage.com/500x500/EE82EE/fff.png',
+    style: 'width:150px;height:150px;',
+    width: 500,
+    height: 500,
+    beforeUpload: (file) => { fnBeforeUpload(self, file) },
+    cropperKey: 0,
+    cropperVisible: false
+  }, options || {})
+
+  params.default = `https://dummyimage.com/${params.width}x${params.height}/EE82EE/fff.png`
+
+  return params
+}
+
+async function fnBeforeUpload(self) {
+  const { view_url, form } = await fGet('/api/file/upload-credentials?type=form&file=image')
+  self.uploadProps.viewUrl = view_url
+  self.uploadProps.uploadUrl = form.upload_url
+  self.uploadProps.uploadParams = form.fields
+  return true
+}
+
+async function fnUpload() {
+  const { view_url, form } = await fGet('/api/file/upload-credentials?type=form&file=image')
+  this.self.uploadProps.viewUrl = view_url
+  this.self.uploadProps.uploadUrl = form.upload_url
+  this.self.uploadProps.uploadParams = form.fields
+  this.self.uploadProps.cropperVisible = true
+}
+
+function fnCloseUploader() {
+  this.self.uploadProps.cropperVisible = false
+}
+
+export function M(self) {
+  this.self = self
+  this.list = fnList
+  this.filter = fnFilter
+  this.reset = fnReset
+  this.create = fnCreate
+  this.edit = fnEdit
+  this.delete = fnDelete
+  this.save = fnSave
+  this.update = fnUpdate
+  this.multiSelect = fnMultiSelect
+  this.getSelected = fnGetSelected
+  this.batchOp = fnBatchOp
+  this.loadAreaList = fnLoadAreaList
+  this.getUploadProps = fnGetUploadProps
+  this.upload = fnUpload
+  this.beforeUpload = fnBeforeUpload
+  this.closeUploader = fnCloseUploader
+}
+
+// --- 以下为工具类
+
+// 加载远程下拉列表
+export function fRemoteMethod(obj, key, render) {
+  const params = obj.query || {}
+  const renderRow = render || obj.render || ((v) => v)
+  params[obj.key || 'key'] = key
+  obj.loading = true
+  fGet(obj.url, params).then(res => {
+    const data = res.data || res
+    const fields = obj.fields || []
+    obj.options = data.map(v => {
+      const node = {
+        label: v[obj.labelField || 'label'] || v['name'],
+        value: v[obj.valueField || 'value'] || v['id']
+      }
+      fields.forEach(field => {
+        node[field] = v[field]
+      })
+      return renderRow(node)
+    })
+  }).finally(() => {
+    obj.loading = false
+  })
+}

+ 78 - 0
src/api/rest.js

@@ -0,0 +1,78 @@
+import request from '@/utils/request2'
+
+export function fGet(uri, data) {
+  data = data || {}
+  return request({
+    url: uri,
+    method: 'get',
+    params: data
+  })
+}
+
+export function fUrl(uri, data) {
+  const param = []
+  for (const k in data) {
+    const val = data[k]
+    if (val === undefined || val === null) {
+      param.push(`${k}=`)
+    } else {
+      param.push(`${k}=${val}`)
+    }
+  }
+  const queryStr = param.join('&')
+  return `${uri}?${queryStr}`
+}
+
+export function fPost(uri, data) {
+  data = data || {}
+  return request({
+    url: uri,
+    method: 'post',
+    data
+  })
+}
+
+export function fPut(uri, data) {
+  data = data || {}
+  return request({
+    url: uri,
+    method: 'put',
+    data
+  })
+}
+
+export function fPatch(uri, data) {
+  data = data || {}
+  return request({
+    url: uri,
+    method: 'patch',
+    data
+  })
+}
+
+export function fDelete(uri, data) {
+  data = data || {}
+  const url = data.id !== undefined && data.id > 0 ? `${uri}/${data.id}` : uri
+  const params = data.id !== undefined && data.id > 0 ? {} : data
+  return request({
+    url: url,
+    method: 'delete',
+    params
+  })
+}
+
+export function fSave(uri, data) {
+  data = data || {}
+  if (data.id !== undefined && !!(data.id)) {
+    return fPut(`${uri}/${data.id}`, data)
+  }
+  return fPost(uri, data)
+}
+
+export function fUpdate(uri, data) {
+  data = data || {}
+  if (data.id !== undefined && data.id > 0) {
+    return fPatch(`${uri}/${data.id}`, data)
+  }
+  return fPatch(uri, data)
+}

+ 1 - 1
src/components/Pagination/index.vue

@@ -93,7 +93,7 @@ export default {
 <style scoped>
   .pagination-container {
     background: #fff;
-    padding: 32px 16px;
+    padding: 0;
   }
   .pagination-container.hidden {
     display: none;

+ 83 - 0
src/utils/request2.js

@@ -0,0 +1,83 @@
+import axios from 'axios'
+import {
+  Message
+} from 'element-ui' // MessageBox
+import router from '@/router'
+import store from '@/store'
+import {
+  getToken
+} from '@/utils/auth'
+
+// create an axios instance
+const service = axios.create({
+  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+  // withCredentials: true, // send cookies when cross-domain requests
+  timeout: 60000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+  config => {
+    // do something before request is sent
+
+    if (store.getters.token) {
+      // let each request carry token
+      // ['X-Token'] is a custom headers key
+      // please modify it according to the actual situation
+      config.headers['X-Token'] = getToken()
+      config.headers['Authorization'] = getToken()
+    }
+    return config
+  },
+  error => {
+    // do something with request error
+    console.log(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+service.interceptors.response.use(
+  /**
+   * If you want to get http information such as headers or status
+   * Please return  response => response
+   */
+
+  /**
+   * Determine the request status by custom code
+   * Here is just an example
+   * You can also judge the status by HTTP Status Code
+   */
+  response => {
+    const res = response.data
+    return res
+  },
+  error => {
+    // 如果为401则返回登录页
+    if (error.response.status === 401) {
+      Message({
+        message: '请重新登录',
+        type: 'error',
+        duration: 5 * 1000
+      })
+      store.dispatch('user/resetToken').then(() => {
+        router.replace({
+          path: '/login'
+        })
+      })
+    }
+    // 自行处理400+ 服务端定义的异常
+    if (error.response.status < 200 || error.response.status >= 300) {
+      const response = error.response
+      const errorMessage = response.data && response.data.message ? response.data.message : error.message
+      Message({
+        message: errorMessage,
+        type: 'error',
+        duration: 5 * 1000
+      })
+    }
+    return Promise.reject(error.response)
+  }
+)
+
+export default service

+ 256 - 0
src/views/unit/index-bak.vue

@@ -0,0 +1,256 @@
+<template>
+  <div class="app-container">
+    <el-input
+      v-model="listQuery.query"
+      placeholder="请输入检索词"
+      style="width: 60%;"
+      class="filter-item"
+      @keyup.enter.native="fetchData()"
+    />
+    <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-search" @click="fetchData">
+      检索
+    </el-button>
+    <el-button
+      class="filter-item"
+      style="margin: 0 10px 20px 0; float: right;"
+      type="success"
+      icon="el-icon-circle-plus-outline"
+      @click="handleCreateOrUpdate('CREATE')"
+    >
+      新建
+    </el-button>
+
+    <el-table
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+    >
+      <el-table-column label="自定义ID" align="center" fixed width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.id2 }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="名称" align="center" fixed width="200">
+        <template slot-scope="{row}">
+          <span>{{ row.name }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="英文名称" align="center" width="200">
+        <template slot-scope="{row}">
+          <span>{{ row.enName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="编码" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.code }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="是否为基础单位" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.isBaseUnit | booleanFilter }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="基础单位" align="center" width="200">
+        <template slot-scope="{row}">
+          <span>{{ row.baseUnit }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="转换关系(1 * unit = ratio * baseUnit)" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.ratio }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="最小刻度" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.minScale }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="最大刻度" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.maxScale }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="卡尺每格刻度" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.stepScale }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="90">
+        <template slot-scope="{row}">
+          <el-dropdown @command="handleCommand">
+            <el-button size="small" type="primary" @click.stop="{}">
+              操作<i class="el-icon-arrow-down el-icon--right" />
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item :command="{row: row, command: 'update'}">更新</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveUp'}">上移</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveDown'}">下移</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveTop'}">置顶</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveBottom'}">置底</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="fetchData" />
+
+    <el-dialog :title="textMapping[dialogStatus]" :visible.sync="dialogFormVisible">
+      <el-form ref="dataForm" :rules="rules" :model="params" label-position="left" label-width="150px" style="width: 400px; margin-left:50px;">
+        <el-form-item label="自定义ID" prop="id2">
+          <el-input v-model="params.id2" placeholder="请输入自定义ID" />
+        </el-form-item>
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="params.name" placeholder="请输入名称" :disabled="dialogStatus === 'UPDATE'" />
+        </el-form-item>
+        <el-form-item label="英文名称" prop="enName">
+          <el-input v-model="params.enName" placeholder="请输入英文名称" />
+        </el-form-item>
+        <el-form-item label="编码" prop="code">
+          <el-input v-model="params.code" placeholder="请输入编码" />
+        </el-form-item>
+        <el-form-item label="是否为基础单位" prop="code">
+          <el-radio v-model="params.isBaseUnit" :label="true" :disabled="dialogStatus === 'UPDATE'">是</el-radio>
+          <el-radio v-model="params.isBaseUnit" :label="false" :disabled="dialogStatus === 'UPDATE'">否</el-radio>
+        </el-form-item>
+        <el-form-item v-if="!params.isBaseUnit" label="基础单位" prop="code">
+          <el-select v-model="params.baseUnitId" :disabled="dialogStatus === 'UPDATE'">
+            <el-option v-for="item in baseUnits" :key="item.id" :value="item.id" :label="item.name" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="!params.isBaseUnit" label="转换关系(1 * unit = ratio * baseUnit)" prop="ratio">
+          <el-input v-model="params.ratio" placeholder="请输入转换关系" :disabled="dialogStatus === 'UPDATE'" />
+        </el-form-item>
+        <el-form-item label="最小刻度" prop="minScale">
+          <el-input v-model="params.minScale" placeholder="请输入最小刻度" />
+        </el-form-item>
+        <el-form-item label="最大刻度" prop="maxScale">
+          <el-input v-model="params.maxScale" placeholder="请输入最大刻度" />
+        </el-form-item>
+        <el-form-item label="卡尺每格刻度" prop="stepScale">
+          <el-input v-model="params.stepScale" placeholder="请输入卡尺每格刻度" />
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">
+          取消
+        </el-button>
+        <el-button type="primary" @click="createOrUpdateData">
+          提交
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getList, updateSortOrder, getBaseUnits, createUnit, updateUnit } from '@/api/unit'
+import Pagination from '@/components/Pagination'
+
+export default {
+  name: 'UnitList',
+  components: { Pagination },
+  filters: {
+    booleanFilter(value) {
+      return value ? '是' : '否'
+    }
+  },
+  data() {
+    return {
+      list: [],
+      listLoading: false,
+      listQuery: {
+        query: '',
+        pageNum: 1,
+        pageSize: 50
+      },
+      total: 0,
+      textMapping: { 'CREATE': '创建', 'UPDATE': '更新' },
+      dialogStatus: '',
+      dialogFormVisible: false,
+      rules: {
+        name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
+      },
+      params: {},
+      baseUnits: []
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      getList(this.listQuery).then(res => {
+        this.list = res.data.list
+        this.total = res.data.count
+        this.listLoading = false
+      }).catch(res => {
+        this.$message('获取数据失败')
+        this.listLoading = false
+      })
+    },
+    handleCreateOrUpdate(dialogStatus, row) {
+      if (this.baseUnits <= 0) {
+        this.fetchBaseUnits()
+      }
+      this.dialogStatus = dialogStatus
+      this.params = dialogStatus === 'CREATE' ? {} : Object.assign(row, {})
+      this.dialogFormVisible = true
+    },
+    updateSort(row, type) {
+      updateSortOrder(row.id, { type }).then(res => {
+        this.fetchData()
+        this.$notify.success('提交成功')
+      }).catch(res => {
+        this.fetchData()
+        this.$message.error(res.data.message)
+      })
+    },
+    fetchBaseUnits() {
+      getBaseUnits().then(res => {
+        this.baseUnits = res.data.list
+      })
+    },
+    createOrUpdateData() {
+      const resultPromise = this.dialogStatus === 'CREATE' ? createUnit(this.params) : updateUnit(this.params.id,
+        this.params)
+      resultPromise.then(res => {
+        this.fetchData()
+        this.$notify.success('提交成功')
+        this.dialogFormVisible = false
+      }).catch(res => {
+        this.fetchData()
+        this.$message.error(res.data.message)
+        this.dialogFormVisible = false
+      })
+    },
+    handleCommand(data) {
+      switch (data.command) {
+        case 'update':
+          this.handleCreateOrUpdate('UPDATE', data.row)
+          break
+        case 'moveUp':
+          this.updateSort(data.row, 0)
+          break
+        case 'moveDown':
+          this.updateSort(data.row, 1)
+          break
+        case 'moveTop':
+          this.updateSort(data.row, 2)
+          break
+        case 'moveBottom':
+          this.updateSort(data.row, 3)
+          break
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 138 - 175
src/views/unit/index.vue

@@ -1,145 +1,140 @@
 <template>
   <div class="app-container">
-    <el-input
-      v-model="listQuery.query"
-      placeholder="请输入检索词"
-      style="width: 60%;"
-      class="filter-item"
-      @keyup.enter.native="fetchData()"
-    />
-    <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-search" @click="fetchData">
-      检索
-    </el-button>
-    <el-button
-      class="filter-item"
-      style="margin: 0 10px 20px 0; float: right;"
-      type="success"
-      icon="el-icon-circle-plus-outline"
-      @click="handleCreateOrUpdate('CREATE')"
-    >
-      新建
-    </el-button>
+    <div class="filter-container">
+      <el-button class="filter-item" size="mini" type="success" style="margin-right:15px;" @click="create">新增</el-button>
 
-    <el-table
-      :data="list"
-      border
-      fit
-      highlight-current-row
-      style="width: 100%;"
-    >
-      <el-table-column label="自定义ID" align="center" fixed width="100">
+      <el-input v-model="list.main.query.category" class="filter-item" size="mini" placeholder="类别" style="width:150px;margin-right:5px;" clearable />
+      <el-input v-model="list.main.query.name" class="filter-item" size="mini" placeholder="名称" style="width:150px;margin-right:5px;" clearable />
+      <el-input v-model="list.main.query.code" class="filter-item" size="mini" placeholder="编码" style="width:150px;margin-right:5px;" clearable />
+      <el-input v-model="list.main.query.enName" class="filter-item" size="mini" placeholder="英文名称" style="width:150px;margin-right:5px;" clearable />
+      <el-select v-model="list.main.query.baseUnit" class="filter-item" size="mini" placeholder="是否基础单位" style="width:150px;margin-right:5px;" clearable>
+        <el-option label="是" :value="true" />
+        <el-option label="否" :value="false" />
+      </el-select>
+      <el-select v-model="list.main.query.baseUnitId" class="filter-item" size="mini" placeholder="引用的基础单位" style="width:150px;margin-right:5px;" filterable clearable>
+        <el-option v-for="item in baseUnitOptions" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+      <el-button class="filter-item" size="mini" type="primary" @click="M.list()">查询</el-button>
+      <el-button class="filter-item" size="mini" @click="() => {M.reset({ data: { page: 1, limit: list.main.query.limit } })}">重置</el-button>
+    </div>
+    <el-table v-loading="list.main.loading" :data="list.main.list" border fit highlight-current-row style="width:100%" :max-height="maxHeight">
+      <el-table-column width="100" header-align="center" label="类别">
         <template slot-scope="{row}">
-          <span>{{ row.id2 }}</span>
+          <span>{{ row.category }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="名称" align="center" fixed width="200">
+      <el-table-column width="150" header-align="center" label="名称">
         <template slot-scope="{row}">
           <span>{{ row.name }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="英文名称" align="center" width="200">
-        <template slot-scope="{row}">
-          <span>{{ row.enName }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="编码" align="center" width="100">
+      <el-table-column width="150" header-align="center" label="编码">
         <template slot-scope="{row}">
           <span>{{ row.code }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="是否为基础单位" align="center" width="100">
+      <el-table-column width="100" header-align="center" label="英文名称">
         <template slot-scope="{row}">
-          <span>{{ row.isBaseUnit | booleanFilter }}</span>
+          <span>{{ row.enName }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="基础单位" align="center" width="200">
+      <el-table-column width="100" align="center" label="是否基础单位">
         <template slot-scope="{row}">
-          <span>{{ row.baseUnit }}</span>
+          <span v-if="row.baseUnit" style="color:green;">是</span>
+          <span v-else style="color:gray;">否</span>
         </template>
       </el-table-column>
-      <el-table-column label="转换关系(1 * unit = ratio * baseUnit)" align="center" width="100">
+      <el-table-column width="180" header-align="center" align="right" label="转换比例">
+        <span slot="header">
+          转换关系
+          <el-tooltip effect="dark" content="1 * unit = ratio * baseUnit" placement="top">
+            <i class="el-icon-question" />
+          </el-tooltip>
+        </span>
         <template slot-scope="{row}">
-          <span>{{ row.ratio }}</span>
+          <span v-if="row.baseUnit">-</span>
+          <span v-else>
+            <span style="color:blue;font-weight:700;">1</span>({{ row.name }})
+            <span style="color:red;font-weight:700;">&nbsp;=&nbsp;</span>
+            <span style="color:purple;font-weight:700;">{{ row.ratio }}</span>
+            <span style="color:red;font-weight:700;">*</span>
+            <span style="color:green;font-weight:700;">1</span>{{ row.baseUnitName || '基础单位' }}
+          </span>
         </template>
       </el-table-column>
-      <el-table-column label="最小刻度" align="center" width="100">
+      <el-table-column label="最小刻度" header-align="center" align="right" width="100">
         <template slot-scope="{row}">
-          <span>{{ row.minScale }}</span>
+          <span>{{ row.minScale || '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="最大刻度" align="center" width="100">
+      <el-table-column label="最大刻度" header-align="center" align="right" width="100">
         <template slot-scope="{row}">
-          <span>{{ row.maxScale }}</span>
+          <span>{{ row.maxScale || '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="卡尺每格刻度" align="center" width="100">
+      <el-table-column label="卡尺每格刻度" header-align="center" align="right" width="100">
         <template slot-scope="{row}">
-          <span>{{ row.stepScale }}</span>
+          <span>{{ row.stepScale || '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="90">
+      <el-table-column width="160" align="center" label="操作">
         <template slot-scope="{row}">
-          <el-dropdown @command="handleCommand">
-            <el-button size="small" type="primary" @click.stop="{}">
-              操作<i class="el-icon-arrow-down el-icon--right" />
-            </el-button>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item :command="{row: row, command: 'update'}">更新</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveUp'}">上移</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveDown'}">下移</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveTop'}">置顶</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveBottom'}">置底</el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
+          <i class="el-icon-edit" style="cursor:pointer;color:#999999;margin-right:5px;" @click="edit(row)"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-delete" style="cursor:pointer;color:#999999;margin-right:5px;"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-top" style="cursor:pointer;color:#999999;margin-right:5px;"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-bottom" style="cursor:pointer;color:#999999;"></i>
         </template>
       </el-table-column>
     </el-table>
+    <pagination v-show="list.main.total>0" :total="list.main.total" :page.sync="list.main.query.page" :limit.sync="list.main.query.limit" @pagination="M.list()" />
 
-    <pagination v-show="total>0" :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="fetchData" />
-
-    <el-dialog :title="textMapping[dialogStatus]" :visible.sync="dialogFormVisible">
-      <el-form ref="dataForm" :rules="rules" :model="params" label-position="left" label-width="150px" style="width: 400px; margin-left:50px;">
-        <el-form-item label="自定义ID" prop="id2">
-          <el-input v-model="params.id2" placeholder="请输入自定义ID" />
+    <el-dialog :title="dlg.form.title" :visible.sync="dlg.form.visible" width="500px">
+      <el-form ref="form" :rules="dlg.form.rules" :model="dlg.form.data" size="mini" label-position="right" label-width="100px">
+        <el-form-item label="类别" prop="category">
+          <el-input v-model="dlg.form.data.category" placeholder="请输入单位类别" />
         </el-form-item>
         <el-form-item label="名称" prop="name">
-          <el-input v-model="params.name" placeholder="请输入名称" :disabled="dialogStatus === 'UPDATE'" />
+          <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
         </el-form-item>
-        <el-form-item label="英文名称" prop="enName">
-          <el-input v-model="params.enName" placeholder="请输入英文名称" />
+        <el-form-item label="编码">
+          <el-input v-model="dlg.form.data.code" placeholder="请输入单位编码,可为空,但不能重复" />
         </el-form-item>
-        <el-form-item label="编码" prop="code">
-          <el-input v-model="params.code" placeholder="请输入编码" />
+        <el-form-item label="英文名称">
+          <el-input v-model="dlg.form.data.enName" placeholder="请输入单位英文名称" />
         </el-form-item>
-        <el-form-item label="是否为基础单位" prop="code">
-          <el-radio v-model="params.isBaseUnit" :label="true" :disabled="dialogStatus === 'UPDATE'">是</el-radio>
-          <el-radio v-model="params.isBaseUnit" :label="false" :disabled="dialogStatus === 'UPDATE'">否</el-radio>
+        <el-form-item label="是否基础单位">
+          <el-checkbox v-model="dlg.form.data.baseUnit" />
         </el-form-item>
-        <el-form-item v-if="!params.isBaseUnit" label="基础单位" prop="code">
-          <el-select v-model="params.baseUnitId" :disabled="dialogStatus === 'UPDATE'">
-            <el-option v-for="item in baseUnits" :key="item.id" :value="item.id" :label="item.name" />
+        <el-form-item v-if="!dlg.form.data.baseUnit" label="基础单位" required>
+          <el-select v-model="dlg.form.data.baseUnitId" class="filter-item" size="mini" placeholder="基础单位" style="width:100%;" @change="baseUnitChange" filterable>
+            <el-option v-for="item in baseUnitOptions" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
-        <el-form-item v-if="!params.isBaseUnit" label="转换关系(1 * unit = ratio * baseUnit)" prop="ratio">
-          <el-input v-model="params.ratio" placeholder="请输入转换关系" :disabled="dialogStatus === 'UPDATE'" />
+        <el-form-item v-if="!dlg.form.data.baseUnit" label="转换关系" required>
+          <el-input v-model="dlg.form.data.ratio" placeholder="转换比例">
+            <span slot="prepend">1 {{ dlg.form.data.name }} = </span>
+            <span slot="append"> * {{ dlg.form.data.baseUnitName || '基础单位' }}</span>
+          </el-input>
         </el-form-item>
-        <el-form-item label="最小刻度" prop="minScale">
-          <el-input v-model="params.minScale" placeholder="请输入最小刻度" />
+        <el-form-item label="最小刻度">
+          <el-input v-model="dlg.form.data.minScale" placeholder="请输入卡尺最小刻度" />
         </el-form-item>
-        <el-form-item label="最大刻度" prop="maxScale">
-          <el-input v-model="params.maxScale" placeholder="请输入最大刻度" />
+        <el-form-item label="最大刻度">
+          <el-input v-model="dlg.form.data.maxScale" placeholder="请输入卡尺最大刻度" />
         </el-form-item>
-        <el-form-item label="卡尺每格刻度" prop="stepScale">
-          <el-input v-model="params.stepScale" placeholder="请输入卡尺每格刻度" />
+        <el-form-item label="卡尺每格刻度">
+          <el-input v-model="dlg.form.data.stepScale" placeholder="请输入卡尺每格刻度" />
         </el-form-item>
       </el-form>
-
       <div slot="footer" class="dialog-footer">
-        <el-button @click="dialogFormVisible = false">
+        <el-button size="mini" @click="dlg.form.visible = false">
           取消
         </el-button>
-        <el-button type="primary" @click="createOrUpdateData">
-          提交
+        <el-button :loading="dlg.form.saveLoading" size="mini" type="primary" @click="M.save()">
+          确认
         </el-button>
       </div>
     </el-dialog>
@@ -147,110 +142,78 @@
 </template>
 
 <script>
-import { getList, updateSortOrder, getBaseUnits, createUnit, updateUnit } from '@/api/unit'
 import Pagination from '@/components/Pagination'
+import { fGet, fPost } from '@/api/rest'
+import { M } from '@/api/op'
 
 export default {
-  name: 'UnitList',
   components: { Pagination },
-  filters: {
-    booleanFilter(value) {
-      return value ? '是' : '否'
-    }
-  },
   data() {
     return {
-      list: [],
-      listLoading: false,
-      listQuery: {
-        query: '',
-        pageNum: 1,
-        pageSize: 50
-      },
-      total: 0,
-      textMapping: { 'CREATE': '创建', 'UPDATE': '更新' },
-      dialogStatus: '',
-      dialogFormVisible: false,
-      rules: {
-        name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
+      M: new M(this),
+      URI: '/api/units',
+      maxHeight: 600,
+      baseUnitOptions: [],
+      list: {
+        main: {
+          loading: true,
+          total: 0,
+          list: [],
+          render(row) {
+            return row
+          },
+          query: {
+            page: 1,
+            limit: 50
+          }
+        }
       },
-      params: {},
-      baseUnits: []
+      dlg: {
+        form: {
+          visible: false,
+          title: '',
+          data: {},
+          saveLoading: false,
+          rules: {
+            category: [{ required: true, message: '类别不能为空', trigger: 'blur' }],
+            name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
+          }
+        }
+      }
     }
   },
   created() {
-    this.fetchData()
+    this.maxHeight = document.documentElement.clientHeight - 135
+    this.M.list()
+    this.loadBaseUnit()
   },
   methods: {
-    fetchData() {
-      this.listLoading = true
-      getList(this.listQuery).then(res => {
-        this.list = res.data.list
-        this.total = res.data.count
-        this.listLoading = false
-      }).catch(res => {
-        this.$message('获取数据失败')
-        this.listLoading = false
+    loadBaseUnit() {
+      fGet(this.URI, { baseUnit: true, size: 10000 }).then(res => {
+        this.baseUnitOptions = res.data.map(v => {
+          return {
+            label: v.name,
+            value: v.id
+          }
+        })
       })
     },
-    handleCreateOrUpdate(dialogStatus, row) {
-      if (this.baseUnits <= 0) {
-        this.fetchBaseUnits()
-      }
-      this.dialogStatus = dialogStatus
-      this.params = dialogStatus === 'CREATE' ? {} : Object.assign(row, {})
-      this.dialogFormVisible = true
+    create() {
+      this.M.create({ form: 'form', title: '新增', data: {} })
     },
-    updateSort(row, type) {
-      updateSortOrder(row.id, { type }).then(res => {
-        this.fetchData()
-        this.$notify.success('提交成功')
-      }).catch(res => {
-        this.fetchData()
-        this.$message.error(res.data.message)
-      })
-    },
-    fetchBaseUnits() {
-      getBaseUnits().then(res => {
-        this.baseUnits = res.data.list
-      })
+    edit(row) {
+      this.M.edit({ form: 'form', title: 'name', row: row })
     },
-    createOrUpdateData() {
-      const resultPromise = this.dialogStatus === 'CREATE' ? createUnit(this.params) : updateUnit(this.params.id,
-        this.params)
-      resultPromise.then(res => {
-        this.fetchData()
-        this.$notify.success('提交成功')
-        this.dialogFormVisible = false
-      }).catch(res => {
-        this.fetchData()
-        this.$message.error(res.data.message)
-        this.dialogFormVisible = false
-      })
-    },
-    handleCommand(data) {
-      switch (data.command) {
-        case 'update':
-          this.handleCreateOrUpdate('UPDATE', data.row)
-          break
-        case 'moveUp':
-          this.updateSort(data.row, 0)
-          break
-        case 'moveDown':
-          this.updateSort(data.row, 1)
-          break
-        case 'moveTop':
-          this.updateSort(data.row, 2)
-          break
-        case 'moveBottom':
-          this.updateSort(data.row, 3)
-          break
+    baseUnitChange() {
+      if (!!this.dlg.form.data.baseUnitId) {
+        const baseUnit = this.baseUnitOptions.find(v => v.value == this.dlg.form.data.baseUnitId)
+        if (!!baseUnit) {
+          this.dlg.form.data.baseUnitName = baseUnit.label
+        }
+      } else {
+        this.dlg.form.data.baseUnitName = undefined
       }
     }
   }
 }
 </script>
-
-<style scoped>
-
-</style>